DOOM en el browser sin WebGL: lo que CSS ahora puede calcular solo

Share

El desarrollador Niels Leenheer publicó hace unos días algo que sacudió a la comunidad frontend: cssDOOM, una versión completamente jugable del DOOM original donde cada pared, techo, barril y enemigo es un <div>. Sin Canvas. Sin WebGL. Sin framework de renderizado 3D. Solo CSS.

No es un truco. Es un FPS (juego en primera persona) completamente funcional que se ejecuta en el navegador, con movimiento, enemigos, sprites animados y colisiones. Y todo el renderizado visual lo hace el motor CSS del navegador.

Que esto sea posible en 2026 dice algo importante sobre el estado de CSS moderno: ya no es un lenguaje para pintar botones. Es un motor de cómputo con funciones matemáticas reales que puede calcular geometría trigonométrica en tiempo real.

Aprende IA con nosotros

Únete gratis a mi comunidad en Skool, donde compartimos noticias, tutoriales y recursos para seguir aprendiendo juntos.

👥 Únete gratis 🚀

¿Qué hace el CSS y qué hace el JavaScript?

La división de responsabilidades en cssDOOM es lo que hace al proyecto interesante desde una perspectiva de arquitectura. JavaScript corre el loop del juego: detecta input del usuario, actualiza la posición del jugador, maneja la física y la lógica de los enemigos. Lo que no hace es calcular ni aplicar los transforms 3D.

Leenheer extrae los datos raw del archivo WAD original de DOOM (el formato de mapas del juego), y los pasa como CSS custom properties a cada elemento <div>:

<div class="wall" style="
  --start-x: 2560;
  --start-y: -2112;
  --end-x: 2560;
  --end-y: -2496;
  --floor-z: 32;
  --ceiling-z: 88;
">

El CSS recibe esas coordenadas brutas de DOOM y calcula todo lo demás: el ancho de la pared (usando hypot() sobre los deltas de coordenadas), su rotación (usando atan2()), su posición en el espacio 3D. Matemáticamente es el teorema de Pitágoras y trigonometría básica. Lo nuevo es que CSS tiene funciones nativas para hacer ese cálculo:

.wall {
  --delta-x: calc(var(--end-x) - var(--start-x));
  --delta-y: calc(var(--end-y) - var(--start-y));

  width: calc(hypot(var(--delta-x), var(--delta-y)) * 1px);
  height: calc((var(--ceiling-z) - var(--floor-z)) * 1px);

  transform:
    translate3d(
      calc(var(--start-x) * 1px),
      calc(var(--ceiling-z) * -1px),
      calc(var(--start-y) * -1px)
    )
    rotateY(atan2(var(--delta-y), var(--delta-x)));
}

Esas funciones (hypot(), atan2()) no eran parte de CSS hasta hace relativamente poco. Fueron incorporadas deliberadamente al estándar precisamente para habilitar este tipo de cómputo geométrico dentro del motor del navegador.

El truco de la cámara: mover el mundo, no el observador

CSS 3D no tiene concepto de cámara. En un motor 3D convencional como Three.js o Babylon.js, movés una cámara virtual a través de la escena. Aquí eso no existe.

La solución de Leenheer es un truco clásico del renderizado: mover el mundo entero en dirección contraria al movimiento del jugador. Si el jugador avanza, la escena se mueve hacia atrás. Si gira a la derecha, todo el escenario rota a la izquierda. JavaScript solo necesita pasar cuatro custom properties al elemento contenedor:

#scene {
  translate: 0 0 var(--perspective);
  transform:
    rotateY(calc(var(--player-angle) * -1rad))
    translate3d(
      calc(var(--player-x) * -1px),
      calc(var(--player-z) * 1px),
      calc(var(--player-y) * 1px)
    );
}

Que esto funcione fluidamente en un browser, con miles de <div> recalculando sus transforms en cada frame, es un testimonio de cuánto ha optimizado el motor de composición del navegador en la última década.

El problema real: clip-path, sprites y la trampa del @property

Posicionar paredes en 3D es la parte elegante. Los problemas reales aparecieron con los sprites: los enemigos, items y decoraciones de DOOM son imágenes 2D que siempre miran al jugador (técnica de billboarding). Para que funcionen correctamente en CSS 3D, Leenheer necesitó hacer que cada sprite siempre enfrente la cámara, algo que tampoco tiene soporte nativo en CSS.

Otro cuello de botella: la visibilidad parcial de objetos detrás de paredes. DOOM original usa un algoritmo BSP (Binary Space Partition) para determinar qué segmentos son visibles. Aquí, CSS maneja algo de eso mediante z-index y clipping, pero no con la misma precisión de píxeles que el motor original. Los glitches de renderizado son visibles si buscás un ángulo específico.

Los filtros SVG también juegan un rol inesperado: los efectos visuales del juego (daño, power-ups) se implementan como filtros SVG aplicados vía CSS. El uso de @property para definir custom properties tipadas permite animar esos valores con interpolación correcta.

Qué dice esto sobre el CSS moderno

El proyecto de Leenheer no es solo un ejercicio de porque sí. Es una prueba de stress de lo que CSS moderno puede hacer cuando se lo empuja a su límite, y los resultados son más reveladores de lo que parece a primera vista.

Primero: CSS tiene funciones matemáticas completas. sin(), cos(), tan(), atan2(), hypot(), pow(), sqrt() ya son parte del estándar. No son curiosidades. Son herramientas que permiten cálculos geométricos reales en el motor del navegador, sin JavaScript.

Segundo: El sistema de transformaciones 3D de CSS, combinado con @property para custom properties tipadas, crea un pipeline de renderizado parametrizable. Pasás datos, CSS calcula la presentación visual. Esa separación de responsabilidades tiene aplicaciones reales más allá de los juegos.

Tercero: El motor del navegador está altamente optimizado para operaciones de composición. Mover miles de elementos en 3D con transforms CSS puede ser más performante de lo que intuitivamente parece, porque se ejecuta en la GPU mediante el compositor, no en el main thread.

Cuándo esto importa más allá del experimento

La pregunta obvia es: ¿para qué sirve esto fuera de un experimento viral? La respuesta no es “para hacer juegos en CSS” —WebGL y Canvas existen y son mejores herramientas para eso.

Sirve para entender qué tan capaz es CSS como lenguaje de layout visual complejo. Hay interfaces de usuario que requieren geometría: visualizaciones de datos 3D livianas, animaciones de transición complejas, prototipos de producto, experiencias inmersivas. En muchos de esos casos, usar CSS puro en lugar de cargar Three.js (que pesa ~160KB minificado) puede ser la opción correcta.

También cambia cómo pensar sobre los límites del frontend moderno: en la era donde generamos interfaces con IA y vibe coding, conocer a fondo las capacidades del browser tiene más valor, no menos. Un modelo de lenguaje puede generar código CSS con transforms 3D si el desarrollador entiende lo que es posible. Si no lo sabe, nunca se lo pide.

Hay algo más profundo aquí que conecta con la discusión sobre el craft de programar: los proyectos que empujan los límites de una tecnología producen conocimiento que eventualmente se filtra al mainstream. La forma en que Leenheer usó hypot() y atan2() para cálculo de geometría en CSS probablemente llegará a artículos técnicos, cursos y componentes de UI en los próximos años.

El problema que no se resolvió (y está bien)

cssDOOM tiene glitches. Hay situaciones donde la geometría se superpone incorrectamente, donde los sprites no siempre orientan bien, donde el rendimiento cae en escenas complejas. Leenheer lo documenta honestamente.

Eso no es un fracaso del proyecto. Es su resultado más valioso: mapear exactamente dónde están los límites actuales de CSS para renderizado 3D, qué funciones existen pero no son suficientes, y qué faltaría para llevarlo más lejos (probablemente mejor soporte para oclusión y manejo de profundidad por capas).

El código está publicado en GitHub y el juego es jugable en cssdoom.wtf.


Fuentes

Leer más

Otras noticias