A visual exploration of Gaussian splatting
A scene made of nothing but ellipsoids of coloured fog. Sort them by depth, blend them in order, and every parameter — where they sit, how they lean, how solid they are — falls out of a gradient.
The primitive
One splat is five numbers and a colour
A splat is a Gaussian with a colour and an opacity. Its shape never comes from the covariance directly — that would let the optimizer wander into matrices that are not positive semi-definite. It is built instead from a rotation and two scales, which cannot.
Parameterisation
Rotation and scale, never Σ itself
The two handles are the columns of R, stretched by s₁ and s₂. Whatever you do to them, the matrix below stays a valid covariance: it is a product that cannot come out indefinite.
- Anisotropy
- —
- Orientation
- —
- 1σ footprint
- —
- Peak opacity
- —
Squash one axis and the splat becomes a needle that can trace an edge. This anisotropy is the whole reason a few hundred thousand Gaussians can stand in for a scene: each one is allowed to be the wrong shape everywhere except where it is needed.
The renderer
Blending is a walk from front to back
There is no ray marching and no network here. Each pixel visits the splats that cover it in depth order, spends a little of its remaining transmittance on each one, and stops when nothing is left. That is the entire renderer.
One pixel, opened up
Each splat spends what is left
Switch to arbitrary order and the picture changes although not one splat moved. Alpha compositing does not commute, which is why a real renderer sorts every splat by view depth for every frame — and why the sort, not the maths, is the expensive part.
The optimizer
Where the splats come from
Nobody places them. A few hundred random Gaussians are dropped on the image and every one of the nine numbers describing them is pushed downhill by the same gradient that flows back through the blend above. This runs live in your browser — analytic gradients, Adam, and the clone / split / prune rule that decides how many splats the picture deserves. Or open the sandbox and paint your own target: the optimizer never stops, so the population reorganises under the brush.
The backward pass
Nine gradients per splat
The loss is plain ℓ₂ between render and target. Because the renderer is a product of transmittances, the derivative for splat i needs everything composited behind it — so the backward pass walks the same list in reverse, rebuilding each Tᵢ by division.
Every 60 steps the splats with the largest positional gradient are duplicated: small ones are cloned into the gap they are being pulled towards, large ones are split in two with their scale divided by 1.6. Splats that fade below 2 % opacity are deleted. Detail appears where the loss asks for it — watch the dashed count climb on the hard-edged target.
The projection
Lifting the ellipse into a camera
A perspective camera is not a linear map, so a projected Gaussian is not exactly a Gaussian. EWA splatting linearises the projection at each splat's centre and keeps the quadratic form — one Jacobian per splat, one 2×2 covariance in pixels, and the renderer from chapter 02 does the rest. Everything below is rasterised on the CPU, one pixel at a time.
drag to orbit
EWA splatting
Σ′ = J W Σ Wᵀ Jᵀ
W rotates the covariance into camera space; J is the Jacobian of the perspective divide evaluated at the splat centre. Drop the third row and column and a 3-D ellipsoid has become a 2-D ellipse in pixels.
Turn off the depth sort — the splats then arrive in the arbitrary order they sit in memory — and the far side of the surface starts breaking through the near side in speckles: with opaque splats the order is the occlusion. Turn off the low-pass filter and distant splats thin below one pixel and start to shimmer; the reference implementation simply adds 0.3 px² to the diagonal of Σ′ so that nothing is ever narrower than a sample.