1 points par GN⁺ 2026-03-29 | 1 commentaires | Partager sur WhatsApp
  • Expérience de rendu 3D de DOOM uniquement en CSS, un projet où tous les murs et objets sont construits avec des <div> et des transformations 3D (transform)
  • La logique du jeu est gérée par JavaScript, mais le rendu est entièrement assuré par CSS, afin d’explorer les limites du navigateur et du CSS moderne
  • Utilise des fonctionnalités CSS récentes comme la trigonométrie, clip-path, @property, les filtres SVG et le positionnement par ancres pour réaliser murs, sols, éclairage, sprites et même les effets d’explosion
  • Comme CSS ne dispose pas de concept de caméra, le point de vue est géré en déplaçant le monde au lieu du joueur, avec tous les mouvements contrôlés par mise à jour de propriétés personnalisées
  • Les performances n’atteignent pas celles de WebGL, mais cela démontre la capacité d’expression de CSS et son potentiel d’extension côté calcul

Rendu 3D de DOOM réalisé en CSS

  • Projet expérimental de rendu de DOOM uniquement en CSS, où tous les murs, sols et objets sont composés de <div> et placés via des transformations 3D (transform)
    • La logique du jeu s’exécute en JavaScript, mais le rendu est entièrement pris en charge par CSS
    • L’objectif du projet est d’explorer les limites du navigateur et du CSS moderne

Retour aux maths du lycée

  • Les données des fichiers WAD du DOOM original (vertices, linedefs, sidedefs, sectors) sont extraites pour construire une scène statique composée de milliers de <div>
  • Chaque mur reçoit ses coordonnées de début et de fin, ainsi que les hauteurs de sol et de plafond, via des propriétés personnalisées CSS
  • Les fonctions CSS hypot() et atan2() servent à calculer la longueur des murs et leur angle de rotation
  • JavaScript transmet les données brutes, puis CSS effectue les calculs trigonométriques pour le rendu
  • La boucle de jeu et le moteur de rendu sont séparés : JS ne s’occupe que de l’état et de la mise à jour des coordonnées

Le problème de transformation des coordonnées

  • DOOM utilise un repère 2D où Y augmente vers le nord, alors que la 3D CSS a un axe Y vers le haut et un axe Z orienté vers l’observateur
  • Lors de la conversion, la forme translate3d(x,-z,-y) est utilisée pour faire correspondre les repères
  • Particularité notable : le calcul rotateY(atan2(var(--delta-y), var(--delta-x))) fonctionne sans transformation supplémentaire

Déplacer le monde plutôt qu’utiliser une caméra

  • CSS n’ayant pas de notion de caméra, le choix a été fait de déplacer le monde en sens inverse plutôt que le joueur
  • Seules quatre propriétés personnalisées sont mises à jour côté JS : --player-x/y/z/angle
  • translate: 0 0 var(--perspective) corrige le point de vue, tandis que rotateY et translate3d gèrent la rotation de la vue et le déplacement dans l’espace
  • Tous les déplacements sont gérés uniquement par mise à jour de propriétés

Le sol, c’est juste un div couché

  • Les éléments DOM étant verticaux par défaut, le sol est couché à l’horizontale avec rotateX(90deg)
  • clip-path, polygon() et path() permettent de représenter des zones polygonales complexes et des trous
  • La fonction CSS récente shape() permet aussi d’utiliser des chemins en pourcentage avec la règle evenodd

Alignement des textures

  • Pour éviter les cassures entre textures de secteurs adjacents, le projet utilise un background-position basé sur les coordonnées du monde
  • Tous les secteurs partagent la même grille de texture, ce qui permet des jonctions de bord fluides

Portes, ascenseurs et animations avec @property

  • L’ouverture des portes consiste à relever le plafond d’un secteur, géré via le transform du conteneur <div> avec une transition CSS (transition)
  • Les ascenseurs déplacent aussi le joueur, donc JS synchronise --player-z
  • Avec @property, les propriétés personnalisées sont déclarées comme numériques afin d’obtenir des chutes et des déplacements fluides

Sprites et effet miroir

  • Les sprites des ennemis utilisent une technique de billboard pour toujours faire face à la caméra
  • Parmi les 8 directions, seules 5 séries d’images existent réellement, les autres étant obtenues par symétrie horizontale (scaleX)
  • Les changements d’images de marche, d’attaque et de mort sont gérés via des animations steps()
  • Le problème de tous les ennemis qui marchent en même temps est résolu avec un animation-delay aléatoire côté JS

Projectiles, explosions et effets de balle

  • Les roquettes, boules de feu, etc. se déplacent automatiquement de A vers B via des animations CSS
  • JS ne définit que les coordonnées de départ, d’arrivée et la durée ; en cas de collision, l’élément est supprimé et un sprite d’explosion est créé
  • Les explosions et la fumée des impacts sont supprimées automatiquement après une animation en 3 images basée sur steps()

Éclairage et filtres

  • La luminosité de chaque secteur est définie avec la propriété --light, puis les éléments internes l’héritent via filter: brightness()
  • Les lumières clignotantes modifient périodiquement --light au moyen de @keyframes
  • L’ennemi transparent (Spectre) est rendu sous forme de silhouette déformée grâce à des filtres SVG (feColorMatrix, feTurbulence, feDisplacementMap)

Interface responsive et positionnement par ancres

  • Le jeu est adapté au mobile, avec un HUD qui passe à la ligne via flex-wrap
  • Les sprites des armes s’ajustent automatiquement à la hauteur du HUD grâce à anchor-name / position-anchor
  • Les boutons de contrôle tactile utilisent le même système d’ancrage

Mode spectateur

  • Prise en charge d’une vue d’ensemble de toute la carte et d’une vue de poursuite à la troisième personne
  • Les fonctions CSS sin() et cos() servent à calculer la position de la caméra derrière le joueur
  • La séparation des propriétés rotate et translate permet des transitions de point de vue fluides
  • JS ne met à jour que la position et l’angle, et c’est CSS qui gère les calculs de caméra

Culling et performances

  • Des milliers d’éléments 3D entraînent une charge importante sur le compositeur du navigateur
  • Culling côté JS : les éléments hors champ sont passés en hidden
  • Expérimentation d’un culling côté CSS : contrôle de visibility via des valeurs calculées, avec une astuce de type grinding
  • Si la fonction if() est standardisée, elle pourra remplacer cela par des conditions plus simples

Tri par profondeur

  • Le navigateur gère automatiquement le tri de profondeur (z-order)
  • Les objets sur un même plan reçoivent un léger décalage pour éviter le scintillement

Les « astuces » de DOOM et le rendu du ciel

  • Le DOOM original utilise une astuce de projection consistant à dessiner le ciel comme une texture 2D au-dessus d’un « mur »
  • Le moteur CSS doit placer le ciel dans un véritable espace 3D, ce qui fait apparaître l’arrière de la carte dans certaines scènes
  • La solution consiste à exclure du rendu les éléments situés derrière les murs du ciel lors de l’étape de culling

Conclusion — limites et potentiel de CSS

  • La boucle de jeu complète est gérée en JS, tandis que le rendu est séparé en pur CSS
  • Des fonctionnalités CSS modernes comme la trigonométrie, @property, clip-path, les filtres SVG et le positionnement par ancres sont poussées à l’extrême
  • Les performances ne rivalisent pas avec WebGL, mais le projet prouve le potentiel d’extension de l’expressivité de CSS
  • De nombreux bugs 3D et problèmes de performances ont été observés dans Safari et Chrome
  • Conclusion finale : « Peut-on faire tourner DOOM en CSS ? » → Oui, c’est possible. Yes, it can.

1 commentaires

 
GN⁺ 2026-03-29
Réactions sur Hacker News
  • Je pense que les gens du genre « j’ai réussi à faire tourner DOOM là-dessus » devraient être embauchés par le département des systèmes de propulsion spatiale
    ce sont clairement des gens qui ont besoin de défis hors norme, pas de simples tâches à faire bouger du doigt

    • Mais au final, ils finiraient probablement aussi par faire en sorte que leurs systèmes de propulsion puissent lancer DOOM
  • On dirait un projet du genre « on l’a fait parce qu’on le pouvait »
    À l’origine, CSS était un langage de style déclaratif, mais avec l’arrivée des conditions, des fonctions mathématiques et des astuces de rendu, il devient de plus en plus un système programmable
    La vraie question n’est pas « peut-on faire tourner DOOM en CSS ? », mais plutôt jusqu’à quel point on est en train d’empiler de la logique dans une couche qui n’était pas faite pour ça

    • C’est un cas typique d’inversion d’abstraction (abstraction inversion)
      CSS cache son envie de devenir un langage de programmation, mais finit par se transformer en une abstraction complètement inadaptée
    • Le point central, c’est de savoir où se situe la frontière entre la présentation (CSS) et l’interaction (JavaScript)
      Avant, il fallait du JS pour les menus déroulants, les infobulles ou la mise en page, mais aujourd’hui on peut même définir le positionnement par ancre ou des conditions if() via des propriétés CSS
      Les animations, le basculement de détails et même certains effets liés à l’accessibilité peuvent désormais être gérés en CSS
  • Construire des scènes 3D en CSS est possible depuis longtemps, mais l’interactivité nécessitait du JS
    Désormais, avec des projets comme x86CSS, on peut même émuler un CPU uniquement en CSS, sans JS
    Du coup, on peut se demander s’il serait possible de faire tourner DOOM en temps réel en pur CSS

    • Mais le CPU x86 en CSS est beaucoup trop lent pour gérer une boucle de jeu. Au final, il faut quand même du JS
    • Cette évolution de CSS était un résultat prévisible, et certains pensent que le camp HTML aurait dû adopter DSSSL dès le départ
  • Cet exemple montre bien pourquoi certains en viennent à vouloir du CSS basé sur TypeScript
    À cause de fonctionnalités comme if() qui ne marchent que dans Chrome, les développeurs en sont réduits à ce genre de bidouilles
    Par exemple, utiliser animation-delay et @keyframes pour simuler un basculement de visibilité
    Si if() en CSS est standardisé, ce type de hack pourra laisser place à une logique conditionnelle propre

  • Les codes de triche de DOOM, IDDQD et IDKFA, n’ont malheureusement pas fonctionné

  • Ça rappelle l’époque où il fallait quatre GIF pour faire des coins arrondis sur une div

    • Une div ? Il y a même eu une époque où tout se faisait en mise en page par tableaux
  • Vraiment impressionnant ! Il suffit de supprimer une seule div pour activer un wall hack

    • Et en allant plus loin, il suffit d’ajouter opacity: 0.7 à .wall pour recréer parfaitement le vieux style de mur transparent façon wallhack
  • Je me demandais « où est-ce qu’on peut essayer ça soi-même ? », et c’est possible sur cssdoom.wtf

    • Mon téléphone a commencé à chauffer dès le lancement
    • C’est la première fois que je vois DOOM tourner aussi fluidement sur mobile
    • Ça fonctionne parfaitement même dans Safari — ce qui est rarissime
    • Dans Firefox, ça tournait bien, mais le mappage de la touche Alt ouvrait et fermait le menu, ce qui était pénible
      Dans Chromium, c’était encore plus saccadé, et je n’ai pas trouvé les touches de strafe
      Malgré ça, dans l’ensemble, c’est une implémentation étonnante
  • CSS est une spécification emblématique des limites du design par comité
    Avec SVG, il est en compétition pour le titre de « spécification la plus hideuse à regarder »

    • Quelqu’un a aussi réagi en demandant si ce commentaire n’avait pas été posté en ne lisant que le titre
  • Une précision supplémentaire sur cette implémentation remarquable :
    en réalité, ce n’est pas le joueur qui se déplace, c’est le monde qui bouge
    La caméra n’est qu’un outil conceptuel servant à calculer le champ de vision (frustum)