- Ein Experiment, das 3D-DOOM nur mit CSS rendert: Alle Wände und Objekte bestehen aus
<div>-Elementen und 3D-Transformationen (transform)
- Die Spiellogik übernimmt JavaScript, aber das Rendering erfolgt vollständig in CSS und lotet die Grenzen von Browsern und modernem CSS aus
- Mit modernen CSS-Funktionen wie Trigonometrie,
clip-path, @property, SVG-Filtern und Anchor Positioning werden Wände, Böden, Beleuchtung, Sprites und sogar Explosionseffekte umgesetzt
- Da CSS kein Kamerakonzept besitzt, wird die Welt statt des Spielers bewegt, wobei die Perspektive über Updates benutzerdefinierter Eigenschaften gesteuert wird
- Die Performance erreicht zwar nicht WebGL-Niveau, zeigt aber die Erweiterbarkeit von Ausdrucksstärke und Rechenfähigkeit von CSS
3D-DOOM-Rendering mit CSS umgesetzt
- Ein experimentelles Projekt, das DOOM ausschließlich mit CSS rendert; alle Wände, Böden und Objekte bestehen aus
<div>-Elementen und werden mit 3D-Transformationen (transform) platziert
- Die Spiellogik läuft in JavaScript, das Rendering wird jedoch vollständig von CSS übernommen
- Ziel des Projekts ist es, die Grenzen von Browsern und modernem CSS auszuloten
Zurück zur Schulmathematik
- Aus den WAD-Dateien des originalen DOOM extrahierte Daten (vertices, linedefs, sidedefs, sectors) werden genutzt, um mit Tausenden von
<div>-Elementen statische Szenen aufzubauen
- Jede Wand erhält Start- und Endkoordinaten sowie Boden- und Deckenhöhe über CSS Custom Properties
- Mit den CSS-Funktionen
hypot() und atan2() werden Wandlänge und Rotationswinkel berechnet
- JavaScript übergibt die Rohdaten, und CSS berechnet die Trigonometrie für das Rendering
- Game-Loop und Renderer sind getrennt; JS übernimmt nur Zustandsverwaltung und Koordinaten-Updates
Problem der Koordinatentransformation
- DOOM verwendet ein 2D-Koordinatensystem, in dem Y nach Norden zunimmt, während CSS 3D Y nach oben und Z zum Betrachter hin ausrichtet
- Für die Transformation wird die Form
translate3d(x,-z,-y) verwendet, um die Koordinatensysteme anzugleichen
- Auffällig ist, dass die Berechnung
rotateY(atan2(var(--delta-y), var(--delta-x))) ohne zusätzliche Transformationen funktioniert
Die Welt statt einer Kamera bewegen
- CSS kennt kein Kamerakonzept, daher wird die Welt entgegengesetzt statt des Spielers bewegt
- In JS werden nur vier Custom Properties aktualisiert:
--player-x/y/z/angle
- Mit
translate: 0 0 var(--perspective) wird die Perspektive korrigiert, rotateY und translate3d übernehmen Blickrotation und Positionsverschiebung
- Sämtliche Bewegung wird allein über Property-Updates verarbeitet
Der Boden ist ein hingelegtes div
- Da DOM-Elemente standardmäßig vertikale Flächen sind, wird der Boden mit
rotateX(90deg) gekippt und horizontal angeordnet
- Mit
clip-path, polygon() und path() lassen sich komplexe polygonale Bereiche und Löcher darstellen
- Die moderne CSS-Funktion
shape() erlaubt zudem prozentbasierte Pfade zusammen mit der evenodd-Regel
Texturausrichtung
- Damit Texturen zwischen benachbarten Sektoren nicht reißen, wird
background-position auf Basis von Weltkoordinaten verwendet
- Alle Sektoren teilen sich dasselbe Texturraster, wodurch nahtlose Übergänge an den Grenzen entstehen
Türen, Lifte und @property-Animationen
- Das Öffnen von Türen wird als Anheben der Sektordecke umgesetzt; das
transform des Container-<div> wird per CSS-Transition (transition) animiert
- Bei Liften bewegt sich der Spieler mit, daher synchronisiert JS
--player-z
- Mit
@property werden Custom Properties als numerische Werte registriert, um weiche Fall- und Bewegungseffekte zu ermöglichen
Sprites und Spiegelung
- Gegnersprites verwenden Billboarding und richten sich immer zur Kamera aus
- Von acht Richtungen liegen nur fünf Bildsets real vor; der Rest wird per horizontaler Spiegelung (
scaleX) erzeugt
- Mit
steps()-Animationen werden Geh-, Angriffs- und Todesframes umgeschaltet
- Dass alle Gegner gleichzeitig laufen, wird in JS mit zufälligem
animation-delay verhindert
Geschosse, Explosionen und Kugel-Effekte
- Raketen, Feuerbälle und Ähnliches werden per CSS-Animation automatisch von A nach B bewegt
- JS setzt nur Start- und Endkoordinaten sowie die Dauer; bei einer Kollision wird das Element entfernt und ein Explosionssprite erzeugt
- Explosionen und Mündungs- beziehungsweise Einschlagsrauch werden nach einer 3-Frame-Animation auf Basis von
steps() automatisch gelöscht
Beleuchtung und Filter
- Für jeden Sektor wird ein Helligkeitswert über die Property
--light festgelegt; innere Elemente erben ihn über filter: brightness()
- Flackernde Lichter ändern ihren
--light-Wert periodisch über @keyframes
- Der transparente Gegner Spectre wird mit SVG-Filtern (
feColorMatrix, feTurbulence, feDisplacementMap) als verzerrte Silhouette dargestellt
Responsives UI und Anchor Positioning
- Das Spiel ist für Mobilgeräte geeignet, und das HUD kann mit
flex-wrap umbrechen
- Waffensprites werden passend zur HUD-Höhe automatisch über
anchor-name / position-anchor positioniert
- Touch-Bedienelemente werden mit demselben Anchor-Ansatz platziert
Beobachtermodus
- Unterstützt werden sowohl eine Gesamtansicht der Karte als auch eine Third-Person-Verfolgungsperspektive
- Mit den CSS-Funktionen
sin() und cos() wird die Kameraposition hinter dem Spieler berechnet
- Durch getrennte Nutzung von
rotate und translate werden weiche Perspektivwechsel umgesetzt
- JS aktualisiert nur Position und Winkel; die Kameramathematik übernimmt CSS
Culling und Performance
- Tausende von 3D-Elementen verursachen Last im Browser-Compositor
- JS-basiertes Culling blendet Elemente außerhalb des Sichtfelds mit
hidden aus
- In einem CSS-basierten Culling-Experiment wird
visibility über berechnete Werte gesteuert, unter Nutzung des Type-Grinding-Tricks
- Falls die Funktion
if() standardisiert wird, ließe sich dies durch kompaktere Bedingungen ersetzen
Tiefensortierung
- Der Browser übernimmt die Tiefensortierung (z-order) automatisch
- Objekte in derselben Ebene erhalten minimale Offsets, um Flackern zu vermeiden
DOOMs „Tricks“ und die Himmelsdarstellung
- Das originale DOOM nutzt für den Himmel einen Projektions-Trick, bei dem eine 2D-Textur als „Wand“ über anderen Wänden gezeichnet wird
- Der CSS-Renderer muss den Himmel in einem echten 3D-Raum platzieren, wodurch in manchen Szenen der Bereich hinter der Karte sichtbar wird
- Gelöst wird das, indem in der Culling-Phase Elemente hinter den Himmelswänden vom Rendering ausgeschlossen werden
Fazit — Grenzen und Möglichkeiten von CSS
- Die gesamte Game-Loop läuft in JS, während das Rendering auf reinem CSS basiert
- Moderne CSS-Funktionen wie Trigonometrie,
@property, clip-path, SVG-Filter und Anchor Positioning werden bis ans Limit ausgereizt
- Zwar nicht auf WebGL-Niveau, aber ein überzeugender Nachweis für die erweiterbare Ausdrucksstärke von CSS
- Dabei wurden zahlreiche 3D-Bugs und Performance-Probleme in Safari und Chrome entdeckt
- Schlussfolgerung: „Kann CSS DOOM ausführen?“
→ Ja, es kann.
1 Kommentare
Hacker-News-Kommentare
Ich finde, Leute aus der Kategorie „Das habe ich auf DOOM zum Laufen gebracht“ sollten in der Abteilung für Weltraumantriebssysteme der Regierung angestellt werden
Das sind Menschen, die für bloßes Fingerdrehen viel zu ungewöhnliche Aufgaben brauchen
Das wirkt wie ein Projekt nach dem Motto „weil man es kann“
CSS war ursprünglich eine deklarative Sprache fürs Styling, entwickelt sich aber mit Bedingungen, mathematischen Funktionen und Rendering-Tricks zunehmend zu einem programmierbaren System
Die wichtige Frage ist nicht „Kann man DOOM in CSS ausführen?“, sondern wie viel Logik wir in eine Schicht pressen, die dafür nie gedacht war
CSS verbirgt zwar den Wunsch, eine Programmiersprache zu werden, ist am Ende aber zu einer völlig falschen Abstraktion geworden
Früher brauchte man JS für Dropdowns, Tooltips und Layouts, heute lassen sich mit CSS-Eigenschaften sogar Anchor-Positionen oder Bedingungen wie
if()festlegenSelbst Animationen, Detail-Toggles und Effekte rund um Accessibility können inzwischen mit CSS umgesetzt werden
3D-Szenen in CSS zu bauen, war schon früher möglich, für Interaktion brauchte man aber JS
Jetzt kann man, wie beim x86CSS-Projekt, sogar eine CPU ausschließlich mit CSS emulieren, ganz ohne JS
Deshalb stellt sich die Frage, ob sich auch DOOM in Echtzeit mit reinem CSS umsetzen lässt
Dieses Beispiel zeigt sehr gut, warum Leute TypeScript-basiertes CSS wollen
Wegen Features wie
if(), die nur in Chrome funktionieren, greifen Entwickler zu solchen TricksZum Beispiel wird mit
animation-delayund@keyframesein Sichtbarkeits-Toggle nachgebautWenn CSS-
if()standardisiert wird, könnte man Bedingungen sauber lösen, ganz ohne solche HacksDie DOOM-Cheat-Codes IDDQD und IDKFA funktionierten leider nicht
Das erinnert mich an die Zeit, als man für abgerundete Ecken an einem div vier GIFs brauchte
Wirklich beeindruckend! Wenn man nur ein div löscht, ist schon ein Wallhack möglich
.walleinfachopacity: 0.7, bekommt man das klassische Gefühl eines transparenten Wallhacks perfekt nachgestelltIch habe mich gefragt: „Wo kann man das selbst ausprobieren?“ — auf cssdoom.wtf
In Chromium ruckelte es eher noch mehr, und ich konnte keine Strafing-Tasten finden
Trotzdem insgesamt eine erstaunliche Umsetzung
CSS ist ein Paradebeispiel für die Grenzen von Committee-Design
Zusammen mit SVG konkurriert es um den Titel der „hässlichsten Spezifikation überhaupt“
Noch eine Anmerkung zu dieser tollen Umsetzung:
Tatsächlich bewegt sich nicht der Spieler, sondern die Welt
Die Kamera ist nur ein konzeptionelles Hilfsmittel zur Berechnung des Sichtfelds (
frustum)