PAJAMA / LEARN
← Course overview

Ship and explain the renderer / 12 OF 12

Train, export, render—and defend every step

After this lessonRun the repository’s end-to-end exercise and explain how to integrate and profile its Rust/wgpu render pass.

You are ready when you can explain a bad image, not only display a good one. This final lesson turns the chain into a reproducible exercise and a debugging conversation.

Build the idea, one step at a time

Need a math refresher?

Open only the tool you need; return here to use it in the next step.

Vectors & weighted sums

STEP 1 / 5

Run the smallest complete experiment

Use the repository commands below. The Python exercise creates a synthetic teacher, fits five Gaussians with Adam, evaluates separate synthetic views/times and exports a PLY plus cameras. It needs Python with PyTorch and NumPy. The native Rust example needs a working wgpu adapter. This exercise is distinct from the two-Gaussian browser SGD lab.

PUT NUMBERS IN

A decoded 32-float row is 128 bytes. At 108,317 splats this is about 13.22 MiB. Uploading only 108,317 32-bit indices costs about 0.413 MiB per frame, rather than resending the complete records. These numbers exclude duplicate CPU copies, GPU padding, render targets and other resources.

FORMULA → RUNNING CODE

Find the same operation in the code

These excerpts are read from the current repository when the site is built. Expand one to inspect it; the full snapshot opens in a separate tab.

public/learn/models.mjsL23–23

The exact arithmetic behind this lesson’s controls and worked example.

export function memoryBudget(count,visible,fps){return {records:count*128,indices:count*visible*4,perSecond:count*visible*4*fps};}
Open the deployed source snapshot ↗
src/stg_pass.rsL247–250

The existing pass adapts the host camera and updates ordering.

        let view = Mat4::from_cols_array_2d(&view_columns);
        let projection = Mat4::from_cols_array_2d(&projection_columns);
        let view_projection = projection * view;
        let [width, height] = viewport.map(|v| v as f32);
Open the deployed source snapshot ↗
src/splat.wgslL116–126

The GPU expands each projected splat into a quad.

  let pixel_offset = 3.0 * (
    major_axis * sigma_major * corner.x
    + minor_axis * sigma_minor * corner.y
  );
  let ndc_offset = pixel_offset * vec2<f32>(2.0 * camera.viewport.z, 2.0 * camera.viewport.w);

  var output: VertexOutput;
  output.position = center_clip + vec4<f32>(ndc_offset * center_clip.w, 0.0, 0.0);
  output.local = corner * 3.0;
  output.color_opacity = vec4<f32>(clamp(splat.r4.yzw, vec3<f32>(0.0), vec3<f32>(1.0)), opacity);
  return output;
Open the deployed source snapshot ↗

CAPSTONE

Run the complete exercise

Run from the repository root after installing its Rust/Node dependencies and Python with PyTorch and NumPy. CPU training is sufficient for this small exercise. Outputs go to artifacts/.

python scripts/train_toy_stg.py --device cpu --steps 400 --output artifacts/toy-training
cargo run --example native_smoke -- artifacts/toy-training/model.ply artifacts/toy-training/cameras.json artifacts/toy-native
cargo run --example mini_compute
npm run check
  • Read training-report.json: learned/fixed fields, initial/final MSE, held-out synthetic MSE and seed.
  • Inspect native output at multiple timestamps; verify time and camera conventions before judging image fidelity.
  • Run mini_compute and inspect its CPU/GPU pixel comparison; explain that it has no GPU tile bins or sorter.
  • Explain the full pipeline without opening the formulas, then use the code links to verify your explanation.

CHECK YOUR UNDERSTANDING

Can you predict the result?

GPU rendering is asynchronous. What is a useful timing measurement?

This is your own review mark. You can remove it at any time.

Go deeper with primary sources

WebGPU · specification ↗3D Gaussian Splatting · paper & implementation ↗Full mathematical derivation ↗