Part XIV — 3D Vision, Neural Fields & Gaussian Splatting · Chapter 6

Tile-based differentiable rasterization in 3DGS

Hook

A Gaussian's spread lives in world units — meters, say. A screen is measured in pixels. Before any of the alpha-compositing from the last chapter can run, every Gaussian has to be projected onto the image plane first, in the right order.

Intuition
screen-space blob
world spread stays fixed at 1 — only depth changes. Projected spread ≈ 10.00

Drag the depth slider. The Gaussian's world-space spread never changes, but its footprint on screen shrinks the farther away it gets pushed — the same perspective shrink a camera applies to a point's position, just applied to a blob's size instead.

Formalize

Approximating the camera as looking straight at each Gaussian, its projected screen-space spread is world spread scaled by focal length over depth — the exact same ratio that turns a 3D point's position into a pixel:

σ2D=σworldfdepth\sigma_{\text{2D}} = \sigma_{\text{world}} \cdot \frac{f}{\text{depth}}

Gaussians must then be alpha-composited nearest-first, exactly like NeRF's ray samples. Once rendered, 3DGS drops the neural network entirely: every Gaussian's own parameters — opacity, position, color, covariance — get updated directly by gradient descent against a rendering loss:

L=renderedtarget2,θθηLθL = \|\,\text{rendered} - \text{target}\,\|^2, \qquad \theta \leftarrow \theta - \eta \cdot \frac{\partial L}{\partial \theta}
  • σworld,σ2D\sigma_{\text{world}}, \sigma_{\text{2D}} — a Gaussian's spread before and after projection.
  • ff, depth — focal length and the Gaussian's distance from the camera.
  • θ\theta — any one of a Gaussian's own parameters (here, opacity); η\eta — the learning rate.
  • LL — squared-error loss between the rendered pixel and its target color.
  1. Two Gaussians, same screen position, different depths

    A near, half-opaque red Gaussian (depth 5) and a far, mostly-opaque blue one (depth 10), both centered exactly on the query pixel with the same projected spread. Composited nearest-first: red claims 0.50.5 of the pixel, blue claims 0.5×0.9=0.450.5 \times 0.9 = 0.45 of what's left — result (0.5,0,0.45)(0.5, 0, 0.45).

  2. The same two Gaussians, composited in the wrong order

    Farthest first instead: blue claims 0.90.9 of the pixel outright, red only gets 0.1×0.5=0.050.1 \times 0.5 = 0.05 of the leftover — result (0.05,0,0.9)(0.05, 0, 0.9). Same scene, same Gaussians, a completely different pixel — because "nearest first" isn't a stylistic choice, it's what makes occlusion correct.

Play
step 1 of 6 — opacity = 0.500, loss = 0.1800
rendered
target

Step through a single Gaussian's opacity being optimized by gradient descent, starting far from the target color. No MLP, no ray march — just this one parameter (and, in a real scene, thousands like it) nudged directly against the rendering loss until the rendered pixel matches the photograph it's being fit to.

Worked example

Two more depths for the same world spread (σworld=0.5\sigma_{\text{world}} = 0.5, f=100f = 100):

  1. depth = 10

    σ2D=0.5×100/10=5\sigma_{\text{2D}} = 0.5 \times 100 / 10 = 5.

  2. depth = 20 — twice as far

    σ2D=0.5×100/20=2.5\sigma_{\text{2D}} = 0.5 \times 100 / 20 = 2.5 — exactly half the previous spread. Doubling depth always halves projected spread, for any Gaussian.

And the first gradient step on opacity, starting at 0.50.5 (color red, target (1,0.2,0.2)(1, 0.2, 0.2), learning rate 0.10.1):

  1. Loss at the start

    Rendered =(1,0.5,0.5)= (1, 0.5, 0.5). Loss =(0.50.2)2+(0.50.2)2=0.18= (0.5-0.2)^2 + (0.5-0.2)^2 = 0.18 (red channel matches exactly, so it contributes nothing).

  2. Gradient and one step

    Since rendered =opacitycolor+(1opacity)background=\text{opacity}\cdot\text{color}+(1-\text{opacity})\cdot\text{background}, the chain rule gives L/opacity=c2(renderedctargetc)(colorcbackgroundc)\partial L/\partial\text{opacity} = \sum_c 2(\text{rendered}_c-\text{target}_c)(\text{color}_c-\text{background}_c) per channel:

    • Red: 2(11)(11)=02(1-1)(1-1) = 0
    • Green: 2(0.50.2)(01)=0.62(0.5-0.2)(0-1) = -0.6
    • Blue: 2(0.50.2)(01)=0.62(0.5-0.2)(0-1) = -0.6

    Summing: 00.60.6=1.20-0.6-0.6=-1.2 exactly. One step: 0.50.1×(1.2)=0.620.5 - 0.1 \times (-1.2) = 0.62 — closer to the target, and the loss at 0.620.62 is lower than at 0.50.5.

Checkpoint

Drag the learning rate until one gradient step from the starting opacity (0.5) lands on 0.8.

gradient = -1.200, next opacity = 0.560
Drag the learning rate to try it
Summary
σ2D=σworldfdepth,θθηLθ\sigma_{\text{2D}} = \sigma_{\text{world}} \cdot \frac{f}{\text{depth}}, \qquad \theta \leftarrow \theta - \eta \frac{\partial L}{\partial \theta}

Rendering a 3DGS scene is projection (world spread shrinks with depth) plus depth-ordered compositing (the same math as the last chapter); training it is nothing more exotic than gradient descent on each Gaussian's own parameters. Neither of those Gaussians has to come from a real 3D scan, though — the next chapter looks at generating one from scratch, out of nothing but a text prompt or a single photo.