Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pawtraced

Status: Complete License: WTFPUP No JavaScript

A real ray tracer where every pixel is an identical empty <div> and the CSS engine does all the math. No JavaScript, no canvas, no SVG, no images, no per-pixel data. The stylesheet is the shader.

Open index.html → the sun orbits live, with shadows and reflections.

Ray-traced scene: coral sphere and mirror sphere on a checkerboard floor under a pink-to-indigo sky


Why this exists

Since CSS got sin() and sqrt(), people have noted that "in theory you could write a raytracer in pure CSS." Then Chrome 138 (June 2025) shipped sibling-index() — a div can now ask which child am I from inside the stylesheet. That's a fragment coordinate. That's gl_FragCoord with its tail wagging.

The markup here carries no information. 5,184 identical empty divs in a grid. Each derives its pixel coordinate from its own index, builds a camera ray, intersects two spheres and a floor plane, casts real shadow rays at the sun, bounces one reflection off the mirror sphere, and paints itself with a single rgb(). One CSS animation drives the sun; the engine re-raytraces the whole scene every frame. ~460,000 calc() evaluations per frame, because I asked nicely.

Run it

git clone https://github.com/Metrix187/pawtraced

Open index.html. That's it.

Requires Chrome/Edge 138+ or Safari 26.2+ (sibling-index() is the whole trick). Firefox hasn't shipped it in stable yet (expected ~145); the page shows a soft warning there instead of a black rectangle. Expect mild CPU warmth — it's a raytracer doing its best.

How the tricks work

  • sibling-index() is the fragment coordinate. mod() + a divide turn one integer into (x, y). No attributes, no inline styles, no nth-child spaghetti.
  • Registered custom properties are float registers. @property … syntax: "<number>" — load-bearing. Registered properties resolve to an actual number at each step; unregistered ones double as raw token soup until the substitution cap eats your homework.
  • No branches, only masks. clamp(0, x * 100000, 1) is a step function. Hit-or-miss, nearest-surface, shadow occlusion — all arithmetic on 0/1 masks.
  • One animated number is the scene uniform. @keyframes drives --T; sun azimuth, light vector, shadows, speculars, and sky glow all re-derive from it per frame.

Rebuild

generate.ps1 and mandelbrot.ps1 emit the HTML files (they exist to scaffold the divs — nothing runs at page load except the CSS engine). Tweak scene constants at the top of the shader block and re-run:

.\generate.ps1
.\mandelbrot.ps1

Bonus: CSS Mandelbrot

mandelbrot.html — 14,400 divs, 50 unrolled iterations of z = z² + c in calc(), colored by escape time in oklch. Same rules: zero JS, zero per-pixel data.

Mandelbrot set rendered as chunky pixels

Prior art

The closest is U2RE-Dev's CSS-ray-tracer — genuine calc-chain math, worth reading. But pixels there carry x/y SVG attributes, CSS is SCSS-prebuilt and JS-injected, light is static, and there are no shadow rays. This version uses dumb uniform markup, an animated light, real shadows, and only portable math. Full notes and paper trail in notes.md.

Status

Complete. The scene is fixed; extend it by editing scene constants and re-running generate.ps1.

License

WTFPUP — do what the fuck you want to, pup ♡

About

a real ray tracer with zero javascript - every pixel is an empty div and the css engine does all the math ♡

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages