01 / Physical simulation Research in progress
WaveGratingA research rendererfor iridescent colors in Blender.
A CD, a banknote hologram, and a holographic Pokémon card show iridescent colors, which change as you tilt them. Cycles, the renderer in the free 3D program Blender, treats light as rays and can't compute these colors. I'm building WaveGrating, a research renderer that treats light as waves, to compute them. The code stays private while the research is in progress.
- Role
- Project lead
- Built
- September 2026 to now
- Software
- Python, PyTorch, CUDA, Blender
- Runs on
- Windows 11 with an RTX 3090 graphics card

The problem
A CD's colors come from light waves adding up, which ray renderers don't compute.
From 29°, one spot on the CD sends blue light.
From 25°, the same spot sends green light.
From 21°, it's red. The color depends on the angle.
A ray renderer computes only the mirror reflection: no color.
WaveGrating is built to compute these colors by treating light as waves.
Drawn for this page with the grating equation, with the tracks enlarged and first order only.
Why rays miss these colors, in technical terms
Path tracers such as Cycles treat light as rays that carry brightness. That works for most scenes. A path tracer can't compute the colors of a CD, a banknote hologram, a holographic card, or a Morpho butterfly's wing. Those colors come from interference: waves add with their phase before anything becomes bright.
A grating shader can imitate the look. WaveGrating tries to compute the colors and to state how accurate the result is. The drawing labels each color with its wavelength in nanometers (nm): blue at 450 nm, green at 549 nm, and red at 652 nm.
Concept diagram.
The approach
The physics runs in a separate program, outside Blender.
Blender
Builds and previews the scene
No physics here
Worker program
- Exact referencecareful math, used to check
- Fast methodson the graphics card
- Detectoradds the waves, then measures
- Resultsraw numbers, apart from the preview
Blender is for building and previewing. No physics runs there.
Scenes leave Blender as strict files in metric units.
A separate worker program runs all the physics methods.
A detector adds the light waves first, then measures brightness.
Blender only describes the scene, and every physics step runs in the worker.
The numerical core, in technical terms
Blender is only the authoring tool. The scene leaves Blender as a strict, versioned file in SI units, and unknown keys are rejected. An isolated Python worker runs the numerical core, so no physics runs inside Blender's own Python.
The worker has a NumPy reference core that uses double-precision complex numbers. GPU operators use PyTorch and CUDA, and TORCWA handles periodic structures such as gratings. pySCATMECH and Meep give independent checks. A written physics contract sets the units and phase conventions, and it fixes the order of operations. The worker saves raw results apart from the preview.
Decisions
Five design choices keep every result measured and every change reviewed.
Order of steps
–
Waves firstthen brightness
|a1 + a2|2right|a1|2 + |a2|2wrong
Speed claims
–
Same erroras the exact reference
- Error matched first
- Reference kept in the code
What decides a pass
–
Measured errorno adjustments afterward
- Error under the limit
- Every run kept on record
How AI agents work
–
Contractswritten work packages
- Files it may change
- Checks it must pass
- Nothing merges automatically
What leaves Blender
–
The scene onlyas plain text, no physics
Waves add up before brightness is measured, or colors vanish.
A faster method counts only at the exact method's error.
Only measured error decides a pass. Every run stays recorded.
AI agents get tasks listing allowed files and required checks.
Only plain scene text, with exact numbers, reaches the worker.
Every result is measured against a reference. Every change comes with its evidence, and nothing merges automatically.
How each choice works, in technical terms
Measure only after adding fields. Coherent paths are combined as complex fields before any power is measured, so the power is |a₁ + a₂|². The cross term makes the fringes and the colors. A test guards this order. Light that is mutually incoherent is added as power, and only after each part travels on its own.
Compare speed only at matched error. A faster route counts only when it reaches the same measurement error as the accurate reference, which always stays in the code. GPU work and caches need measured evidence before they are added.
Pass on measured error alone. A run passes only on its measured error. Phase fitting, energy normalization, clipping signed data, and threshold changes never count toward a pass, and every run stays in the record.
Put AI models under written contracts. One AI model leads the numerical design and review. Other agents, including a local model on my DGX Spark, get bounded work packages. Each package lists the files the agent may change and the checks it must pass. Every change is a pull request that cites its specification and evidence, and nothing merges automatically.
Keep Blender's Python out of the physics. Only a standard-library schema crosses into the worker. Exact decimal strings are the authority, and a float copy in Blender is for display only.
Evidence
Independent references agree with WaveGrating.
Tests and speed
- 23regression checks, all passing
- 2.2–4.5×faster at the same error1024 × 1024 grid
- 3.9–7.7×faster at the same error2048 × 2048 grid
Difference from two independent references
- SCATMECH2.6 × 10−11
- Rayleigh–Sommerfeld9.1 × 10−5 to 1.6 × 10−3
Smaller means closer agreement. Each labeled step on the scale is 1,000 times larger than the one before it.
All 23 checks pass. Each guards a result verified earlier.
At equal error, graphics-card methods ran 2.2–7.7 times faster.
Two independent methods agree. Smaller differences mean closer agreement.
All 23 checks pass, the fast methods match the reference's error, and two independent methods agree.
Measurement details, in technical terms
The regression suite of 23 checks passes. The speed tests ran on an RTX 3090. At matched measurement error, the windowed GPU routes ran 2.2 to 4.5 times faster than the full-grid reference on a 1024 × 1024 grid. On a 2048 × 2048 grid, they ran 3.9 to 7.7 times faster. An independent Rayleigh–Sommerfeld reference agrees at the detector to within 9.1 × 10⁻⁵ to 1.6 × 10⁻³.
The test grating is one-dimensional, with 200 Fourier harmonics on each side of zero. On it, WaveGrating and an independent SCATMECH reference agree to 2.6 × 10⁻¹¹. The reflected powers are stable to 6 × 10⁻⁸.
Next
The roadmap leads to complete renders in Blender.
- An independent finite-element check of the light field at a repeating surface.
- Light that arrives at an angle to the grooves, called conical incidence, and patterns that repeat in two directions.
- Complete renders of CDs, banknote holograms, and holographic cards.
- WaveGrating objects inside ordinary Cycles scenes.