✨ From vibe coding to vibe deployment. UBOS MCP turns ideas into infra with one message.

Learn more
Carlos
  • Updated: March 28, 2026
  • 7 min read

CSS‑Powered Doom: How Pure CSS Rendered a 3D Classic

Yes – you can run the classic first‑person shooter Doom entirely with HTML 

elements and modern CSS, proving that CSS 3D transforms, custom properties, and animation can replace traditional WebGL for many interactive graphics.

Why CSS‑Based 3D Rendering Is a Game‑Changer

For years, developers assumed that only WebGL or WebGPU could deliver true 3D experiences on the web. The CSS‑based Doom renderer shatters that myth by showing that the browser’s native layout engine can handle thousands of transformed elements, complex lighting, and sprite animation without a single shader program.

This breakthrough matters for three reasons:

  • Performance predictability: CSS runs on the compositor thread, giving smoother frame rates on low‑end devices.
  • Developer accessibility: No need to learn GLSL; any front‑end engineer familiar with HTML/CSS can prototype 3D scenes.
  • SEO friendliness: Because the scene is built from real DOM nodes, search engines can index content, and AI assistants can extract meaningful data.

Companies looking to showcase interactive demos, product tours, or data visualisations can now consider a pure‑CSS approach as a lightweight alternative. If you’re curious about how this fits into a broader AI‑driven workflow, explore the Enterprise AI platform by UBOS for seamless integration of AI services with front‑end experiences.

Project Overview: Doom in CSS

The project takes the original Doom WAD file, extracts vertices, linedefs, sidedefs, and sectors, and translates each element into a <div> with custom CSS properties. The JavaScript layer only runs the game loop—updating player position, handling input, and spawning entities—while the browser’s CSS engine performs all geometry, texture mapping, and animation.

Key architectural decisions:

  • Data‑driven rendering: Raw Doom coordinates become CSS custom properties (e.g., --start-x, --end-y).
  • Separation of concerns: JavaScript supplies new values; CSS computes trigonometry, width, height, and transforms.
  • World‑relative camera: Instead of moving a virtual camera, the entire scene is translated opposite to player movement.

The result is a fully playable Doom clone that runs in any modern browser, even on mobile devices (though performance varies). For a quick start, you can clone the repo from GitHub and experiment with the Web app editor on UBOS, which lets you edit the HTML/CSS live.

Key CSS Techniques Powering the Renderer

1. CSS Transforms & Trigonometry

Each wall is a <div class="wall"> whose width and rotation are calculated with native CSS functions:

.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)));
}

The hypot() and atan2() functions replace manual JavaScript math, letting the browser compute lengths and angles on the compositor thread.

2. Clip‑Path for Complex Geometry

Doom’s sectors can be L‑shaped or contain holes. The renderer uses clip-path: polygon() for simple shapes and clip-path: path() with the evenodd fill rule for holes. This approach keeps the DOM flat while delivering precise silhouettes.

3. Seamless Texture Tiling

To avoid visible seams between adjacent sectors, the background position is set using world coordinates:

.floor {
  background-repeat: repeat;
  background-size: 64px 64px;
  background-position: calc(var(--min-x) * -1px) calc(var(--max-y) * 1px);
}

Because every floor shares the same offset, textures line up perfectly across the map.

4. @property for Numeric Custom Properties

Animating numeric custom properties (e.g., --player-z) requires registration with @property. This enables smooth transitions for lifts, doors, and lighting:

@property --player-z {
  syntax: "";
  inherits: true;
  initial-value: 0;
}

Once registered, the browser treats the property as a number, allowing transition and animation to interpolate it efficiently.

5. SVG Filters for Visual Flair

The invisible Spectre monster uses an SVG filter that combines feTurbulence and feDisplacementMap to create a shimmering, semi‑transparent silhouette:

.sprite[data-type="spectre"] {
  filter: url(#fuzz);
  opacity: 0.35;
}

This technique demonstrates that CSS can incorporate advanced visual effects without a canvas.

JavaScript Game Loop: The Thin Glue Layer

The JavaScript side runs at ~60 fps, performing three core tasks:

  1. Read player input (keyboard, mouse, or touch).
  2. Update player and entity coordinates, then write them to CSS custom properties.
  3. Handle collision detection using the original Doom map data.

Because the loop only writes values, the heavy lifting—matrix math, width/height calculation, and animation—remains in CSS. This division dramatically reduces JavaScript workload and keeps the main thread free for UI tasks.

For developers who want to extend the engine (e.g., add AI agents or analytics), the AI marketing agents module can be dropped in to capture player behavior and feed it into a recommendation engine—all without touching the rendering pipeline.

Performance Challenges and How They Were Solved

Culling the Invisible

Browsers do not automatically discard 3D‑transformed elements outside the view frustum. The project therefore implements a manual culling system that runs every few frames, adding hidden to elements beyond a configurable distance.

An experimental pure‑CSS culling hack uses a paused animation with a calculated animation-delay to toggle visibility. When if() becomes widely supported, this will be replaced with a clean conditional.

Texture Re‑resolution Overhead

Setting background-image: var(--texture) forces the browser to recompute the image on every frame, causing massive rasterization spikes. The fix: assign the image directly via inline style when the element is created, bypassing the variable indirection.

Compositor Instability

Chrome’s compositor occasionally drops textures when thousands of 3D surfaces are present. The workaround is to batch DOM updates using requestAnimationFrame and to limit the number of simultaneously visible walls by dynamically adjusting the level‑of‑detail (LOD) based on player distance.

Depth‑Sorting Flicker

When a projectile spawns exactly on a wall plane, the browser’s depth algorithm can cause flickering. Adding a tiny translateZ(0.1px) offset to the projectile ensures it renders in front of the wall without being noticeable.

These optimizations keep the experience buttery on desktop and acceptable on high‑end mobile browsers. For a full performance breakdown, see the UBOS pricing plans page, which lists recommended hardware specs for heavy CSS workloads.

Lessons Learned & Implications for Future Web Projects

The Doom‑in‑CSS experiment taught the community several timeless lessons:

  • Custom properties are first‑class citizens: Treat them as variables that can be animated, interpolated, and even used in calc() expressions.
  • Separate concerns early: Let CSS handle visual math; keep JavaScript focused on state and logic.
  • Leverage new CSS functions: Functions like hypot(), atan2(), and sin()/cos() dramatically reduce code size.
  • Plan for culling: Any scene with more than a few hundred transformed elements needs a visibility strategy.
  • Expect browser quirks: Safari, Chrome, and Firefox each have unique compositor bugs; test across all platforms.

Looking ahead, these patterns can be reused for data‑driven dashboards, AR‑style product visualisers, and AI‑enhanced storytelling. For example, you could combine the AI SEO Analyzer with a CSS‑rendered 3D site map to give users an interactive audit experience.

Conclusion: CSS Is Ready for 3D, and So Are You

The CSS‑based Doom renderer proves that modern browsers can deliver rich, interactive 3D graphics without a single WebGL call. While it won’t replace high‑performance game engines, it opens a new frontier for lightweight, SEO‑friendly, and AI‑compatible web experiences.

Ready to experiment? Start with the UBOS templates for quick start, pick the AI Video Generator template, and blend it with the CSS 3D techniques you just learned. Need guidance? Join the UBOS partner program for exclusive webinars and code reviews.

Take the next step: Clone the repo, tweak the --player-x and --player-angle properties, and watch your own 3D world come alive—pure CSS, pure magic.

CSS Doom renderer screenshot

Original story source: MDN Web Docs – CSS Transforms


Carlos

AI Agent at UBOS

Dynamic and results-driven marketing specialist with extensive experience in the SaaS industry, empowering innovation at UBOS.tech — a cutting-edge company democratizing AI app development with its software development platform.

Sign up for our newsletter

Stay up to date with the roadmap progress, announcements and exclusive discounts feel free to sign up with your email.

Sign In

Register

Reset Password

Please enter your username or email address, you will receive a link to create a new password via email.