Why the Torus Still Looked Broken

Published:

← Back to Blog


After adding a depth buffer, removing texture seams, and fixing sphere poles, one ring-shaped surface in a deterministic music-video render still looked rough. It filled or crossed the frame, its outline was visibly polygonal, and some camera angles reduced it to a giant rotating slab. Two remaining shader defects looked suspicious, but neither explained the shape.

The object was not the ordinary Torus display mode. At 45 seconds the playlist entered NearRing, an eight-turn spiral tube whose coils overlap closely enough to read as a torus from a distance. We have a separate geometric tour of that surface; this post is about how tessellation and camera assumptions made it fail on screen.

BEFORE
Oversized faceted NearRing extending beyond the video frame
AFTER
Smooth, fully framed NearRing after denser tessellation
Matching frames at 0:45.5 from the same audio and playlist. The original 16-sided outline reaches beyond the vertical field of view. The revised mesh is smooth and the complete ring remains visible.

Eliminating the Shader Suspects

The HeightField vertex shader had an out-of-bounds fetch at uv = 1, but NearRing sets the height-displacement uniform to zero, so that branch never executes here. The palette lookup also shared the simulation buffer's linear sampler. Giving the palette its required nearest sampler removed invented colors at index boundaries, but a sampling filter cannot add missing mesh vertices or change a camera's field of view.

Both were real defects worth fixing. A frame-for-frame comparison simply showed that they were not the causes of this geometry problem. See One Texture, Two Kinds of Smoothing for that part of the work.

Eight Turns Sharing 128 Columns

The original mesh used 128 longitudinal segments. That sounds adequate until the parameterization is considered: the centerline completes eight revolutions while u moves from zero to one. Each visible revolution therefore received only 128 / 8 = 16 segments. A face-on outline was literally a 16-sided polygon.

const NEAR_RING_TURNS: u16 = 8;
const NEAR_RING_SEGMENTS_PER_TURN: u16 = 64;
const NEAR_RING_TUBE_SEGMENTS: u16 = 64;

let longitudinal_segments =
    NEAR_RING_TURNS * NEAR_RING_SEGMENTS_PER_TURN;

The revised mesh uses 512 longitudinal segments, or 64 per revolution, and doubles the tube cross-section from 32 to 64 segments. This yields 33,345 vertices and 65,536 triangles: inexpensive for contemporary GPUs but dense enough that the largest silhouette has sub-pixel faceting at 720p.

The renderer uses 16-bit mesh indices, so increasing density has a hard safety boundary. The maximum vertex index is now 33,344, comfortably below 65,535. A regression test locks together the segment counts, vertex count, index count, and maximum referenced vertex so a later quality increase cannot silently overflow the index format.

One Camera Distance Did Not Fit Every Mesh

Most bounded ShoggothOx meshes reach roughly 0.8 to 1.0 units from the origin. NearRing reaches 1.5 units and its tube extends half a unit toward the camera. Nevertheless every display used the same orbit radius of 3.0 and a 45-degree vertical field of view.

At distance 3, that field of view covers only 3 tan(22.5°) ≈ 1.24 units above and below the center plane. A 1.5-unit face-on ring cannot fit even before perspective makes the nearer tube surface larger. The more varied camera orbit merely made the old sizing assumption visible.

NearRing now selects an orbit radius of 4.5. Other modes retain radius 3.0, so their established framing and deterministic output do not change. The display-mode match is exhaustive: adding a new surface requires an explicit framing decision at compile time rather than inheriting an accidental default.

BEFORE
NearRing edge view enlarged into a clipped vertical slab
AFTER
Fully visible NearRing edge view with a smooth silhouette
Matching frames at 0:48.5. A wider orbit keeps the edge-on view recognizable and gives the rotating surface room to breathe.

A Separate Material Problem

The active configuration uses the hippy palette, whose index zero is intentionally (252, 252, 252). Large quiet regions of the simulation buffer contain zero, so the first corrected render still produced a mostly white surface with colored activity around its edges. That observation was accurate but the conclusion was incomplete: a palette lookup supplies color, not a complete 3D material model. The subsequent surface-material and texture-coverage investigation adds lighting, a palette-derived base coat, and one texture cycle per spiral turn.

Verification

The original video was preserved, then the same existing playlist was rendered with the repository's normal command:

nix develop --command cargo xtask video \
  "Aces High" --reuse-playlist

The playlist file remained unchanged. Focused tests cover camera selection and 16-bit mesh-index safety, while matching 0:45 and 0:48.5 frames verify the visible result. The comparison also reinforces a useful debugging lesson: a renderer can contain several genuine bugs, but reachability, geometry budgets, and projection math still determine which one explains a particular frame.