← all work

real-time & offline rendering

Gaussian Renderer

featured

CPU software rasterizer that splats 2D Gaussians into an HDR buffer and composites them with the over operator. Phase 1 of a planned progression toward GPU and WebGPU splatting.

C++Gaussian SplattingSoftware RasterizationHDR Compositing
Gaussian Renderer

A renderer built from scratch to understand Gaussian splatting one layer at a time, starting at the bottom. Phase 1 is pure CPU: evaluate a 2D Gaussian falloff per pixel, accumulate colour and alpha into separate HDR buffers, and composite to display range only at write time. No GPU, no shaders, no dependencies — just the maths and a PPM file. Getting the compositing right by hand is the point; the GPU and WebGPU phases build on the same splat function.

techniques

  • Per-pixel Gaussian falloff — exp(-0.5 · r² / σ²) evaluated against each splat centre
  • Separate colour and alpha accumulation buffers so alpha stays unbounded through the pipeline
  • Over-compositing operator applied at write time, not baked into the splat
  • HDR values clamped to display range only at the final unsigned-char cast

renders & comparisons

Before — premultiplied write

Before — premultiplied write

σ = 100, α = 0.5 (original buggy params)

pColor = color × alpha written as RGB → centre appears dim and washed out. Index bug accumAlpha[i×w+i] corrupts the alpha buffer.

After — composite at write time

After — composite at write time

σ = 100, α = 0.5 (fixed)

Straight colour stored, alpha kept separate. writePPM composites colour × alpha over black. Index bug fixed.

Eye-candy — HDR clamp core

Eye-candy — HDR clamp core

σ = 80, α = 25

α = 25 drives G/B to 1.25 → clamping creates a white core. A red halo emerges as R stays saturated further out than G/B.

gallery

Three overlapping Gaussians — σ = 50, α = 18 each, equilateral triangle d = 180, 900 × 506. Overlapping primaries sum to secondaries: R+G→yellow, R+B→magenta, G+B→cyan, R+G+B→white. Values above 1.0 clamp to display range at write time.
Three overlapping Gaussians — σ = 50, α = 18 each, equilateral triangle d = 180, 900 × 506. Overlapping primaries sum to secondaries: R+G→yellow, R+B→magenta, G+B→cyan, R+G+B→white. Values above 1.0 clamp to display range at write time.

phase 1 bug fixes · main.cpp

// Bug 1 — splat(): straight colour, no premultiplication
- pColor = gauss.color * pAlpha;
+ pColor = gauss.color;

// Bug 2 — index typo
- accumAlpha[i*width + i] += pixelAlpha;
+ accumAlpha[j*width + i] = pixelAlpha;

// Bug 3 — writePPM: HDR alpha through, clamp only at uchar cast
- float a = clamp(alphaBuf[i], 0, 1);   // killed HDR intent
+ float a = alphaBuf[i];

// Bug 4 — writePPM: file-open error check
+ if (!f) { cerr << "cannot open " << filename; return; }

// Bug 5 — multi-Gaussian: additive accumulation (renderGaussians)
+ for (const auto& g : gaussians)
+     accumRGB[j*W + i] += pColor * float3(pAlpha);  // was = (overwrite)

roadmap

  • doneSoftware rasterizer — single Gaussian on the CPU
  • doneExtended software rasterizer — multi-Gaussian scene, over operator
  • nextGPU rasterizer — splatting via shaders
  • nextWebGPU rasterizer — real-time in the browser
  • nextBonus — radiance field rendering on the CPU

more in real-time & offline rendering