1 points par GN⁺ 2026-03-29 | Aucun commentaire pour le moment. | 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.

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.