Text in Ray-Marched Scenes: Distance Fields vs. Triangles
A ray-marched 3D scene has two honest ways to render text: triangulate each glyph into an extruded mesh and traverse it through a bounding volume hierarchy (BVH), or sample a per-character signed distance function (SDF) baked into an atlas texture. In the Software 4 Senses 3D Spatial Web Studio's measured A/B, the field wins 1.31–1.71× against the shipped triangle recipe, the margin grows with every light-transport feature — and it still failed one of its own pre-registered success criteria. This page documents both mechanisms and the complete result, including the loss.
Text in a 3D Spatial Web Interface is Scene Document Model content. In the distance-field form, each glyph is a solid element of the scene in its own right, and the word a single recipe node in the saved document. Its letters cast shadows and refract like any other solid, and the word is addressed like any other 3D element: its root is a plain transform group carrying the word’s name and placement, so it moves, is removed, or is re-parented as one thing, while the letters under it are derived from the recipe and never named.
Why 3D text must be a solid
Text in a scene with real refraction must be a closed solid, because refraction needs a second interface to bend the ray back out of — and a flat sheet has none. The Studio's text was once a single flat sheet at z = 0, which is why it rendered opaque at any alpha: a ray entering glass with no back face never exits, so no transparency effect can occur. Extruding each glyph into a closed, watertight solid lets text refract by exactly the mechanism any mesh uses — winding-classified entry and exit, with the exit facet found by the BVH — with no text-specific path in the triangle shader pipeline.
Solidity alone is not enough; the solid must also be thicker than the renderer's own march machinery. When the floor was measured, the interior march for these solids began by pushing 0.04 world units along the inverted normal to get off the entry face, against an interior step floor of 0.05 world units. Both constants are absolute world units, so a shallow enough solid is literally thinner than the machinery that has to walk through it. A GPU pixel sweep (; a glass "M" over a green body, ray depth clamped to 1 so recursion cannot mask the difference; reproducible 3-of-3 at each step) measured the consequence directly: thicknesses 0, 0.01, 0.03, and 0.035 pass straight through with no refraction, while 0.02, 0.04, and everything above refract. Below 0.04 world units the result is deterministic but erratic — it flips between adjacent thicknesses — and from 0.04 up it is stable. Text has since taken a smaller 0.005 entry push (triangle text from , distance-field glyphs from ), which removed black rims on thin strokes; the 0.05 step floor is unchanged.
TEXT_DEFAULT_THICKNESS= 0.12 world units — the default extrusion depth.TEXT_MIN_THICKNESS= 0.06 — the floor, 1.5× the measured 0.04; any lower requested thickness silently clamps up to it, with no flat-sheet opt-out, opaque text included.INTERNAL_MARCH_MIN_STEP= 0.05 world units — the interior step floor that, with the 0.04 entry-face push then in force, produced the measured floor.- Measured refraction floor: 0.04 world units (GPU pixel sweep, ).
The default of 0.12 is three times the measured floor and the enforced minimum of 0.06 is one and a half times it; the two were one number until , when they were separated so controls carrying lettering could be shallower. Text that should look thinner wants a smaller em size, not a solid the marcher cannot walk. The cost of the always-solid rule is also unconditional: extrusion multiplies a glyph's triangle count roughly 4× (front cap, back cap, and one quad per boundary edge), and in a march-bound renderer text pays for every one of those triangles on every ray that considers it.
Glyphs as distance fields
The distance-field path renders each character as one record that samples a pre-baked SDF atlas — baked once when a font is first added, then stored and reused — so no font parser, no tessellation, and no per-word BVH ever reaches a visitor's browser. Where the triangle path turns a word into an extruded triangulation plus a private spatial hierarchy, the field path turns it into one flat-cost primitive per character: a texture fetch and a distance decode, the same for "i" as for "W".
Wide-range SDF glyph — a text primitive that stores, per atlas cell, the signed distance to the glyph outline encoded over a range equal to that cell's own half-diagonal, so the field never saturates anywhere in the cell and the ray marcher always receives a full-size step toward the surface.
The encoded range is the design's load-bearing choice. A conventional narrow-band SDF clamps outside a thin shell around the outline, which forces a marcher approaching from far away to creep in fixed-size steps until it enters the band. Encoding each cell over its own half-diagonal means no sample in the cell ever clamps; a CPU march of the real atlas field measured a mean of 2.0 steps to converge on a hit, across seven test glyphs (overall mean 8.3–9.5 steps, median 7; gate probe, ). The price is precision: one byte spans the whole cell (about 0.0067 em per code step) and corners round by roughly one texel. It is deliberately a single-channel wide-range SDF, not a multi-channel MSDF — the format reserves the multi-channel upgrade, but what shipped is one channel.
Each font is one atlas pair — glyph metrics plus a roughly 3.2 MB distance-field binary —
and a scene's document names the pair rather than embedding it. When a scene is exported, the
package carries each pair beside the document under fonts/atlas/, rewritten
package-relative, so the folder plays from any static host with no request back to a font service
or to the Studio. This is also what made text a player capability: nothing in the field path
triangulates, so the playback entry ships zero font-engine and zero tessellation code and lays
glyphs out from the atlas metrics.
The honest A/B — including the loss
On the pre-registered rig, the field beats the shipped triangle recipe in every arm — 1.31× inert, 1.70× with shadows, 1.71× with reflections, 1.64× with refraction — and still failed one of its own kill criteria. The rig: eight lines of "Handgloves" filling a 700×700 frame, both arms interleaved in one session, medians of driven frames timed to GPU completion, reproduced across three runs within 2 ms; numbers are post analytic-exit-face ().
| Arm | Field vs. shipped triangle recipe (fontSize 96) |
Field vs. halved-resolution triangles (fontSize 48) |
|---|---|---|
| Inert (no secondary rays) | 1.31× | 1.05× |
| Shadows on | 1.70× | 1.19× |
| Reflections on | 1.71× | 1.16× |
| Real-glass refraction | 1.64× | 1.09× |
Handgloves rig, 700×700, arms interleaved in one session, medians of GPU-completion-timed frames, three runs within 2 ms; post analytic-exit-face record, . Ratios are within-session; the pre-registered bar for the right column was ≥1.3×.
On that rig the scene's record count collapsed from 8,548 to 92 — one record per character instead of a roughly 4×-extruded triangulation plus its private hierarchy. The shape of the table is the interesting part: the field's advantage widens as text participates in light transport. Shadow, reflection, and refraction rays re-traverse the triangle hierarchy per ray, per bounce; the same secondary rays cost a distance field the same few texture fetches they always cost. The inert row — text that casts nothing and reflects in nothing — is the one case where a field looks worst.
The refraction row needed one further piece of engineering to win at all. Sphere tracing inside a 0.12-thick glyph is step-limited by construction — the field's magnitude inside the solid can never exceed half the thickness — but the extrusion's z-faces are planes, so the exit distance along the ray is exact. That analytic exit face (with the exact 2D contour bound, both provably no smaller than the steps they replace) lets a refracted ray cross a thin glyph in one step. Against the halved-resolution control it flipped the refraction arm from losing (0.98×) to winning (1.09×); against the shipped recipe it widened a margin that already existed.
And the loss: the experiment pre-registered a second criterion — beat simply halving the triangle recipe's font resolution (fontSize 96 → 48) by at least 1.3×, since halving resolution is the cheapest single lever available and requires no new machinery. That bar was never met: the field wins only 1.05–1.19× against the halved-resolution arm. The cheap lever has a documented legibility cost — that recipe ate roughly 17% of the em and dropped strokes, while the field's quality is resolution-independent — but the number is the number. The field shipped as the default text door by explicit decision on this evidence, not on a claim the bar was met, and the triangle path was retained, unadvertised, rather than deleted. An A/B that only reports the arms it won is marketing, not measurement; publishing the loss column is the point.
Shipping it
Converting the Studio's flagship scene's one word from triangles to a field collapsed 843 records to 24 and improved whole-frame time by a net +15–23% across runs — with the conversion's own shader-tier cost already paid inside that number. At the time, the glyph primitive moved the scene from a slimmer pre-baked shader tier onto the full one — a tier step measured at +5.5–6.3% on this exact scene — so the conversion was measured end to end with that cost paid, as a deployed page would actually run it. The tier cost has since reversed: the glyph primitive joined the slim tier on and triangle text left it on , so today the field version is the one that boots on the slimmer shader. Rig: two scene documents each booted through the player's own boot path, 15 alternating blocks, median of per-rep ratios — +15.27% and +23.17% across two runs, 13-of-15 reps favoring the field in each, with an image intersection-over-union of 0.954 confirming both arms rendered the same scene ().
Text also serializes as a recipe, not as records — the Scene Document Model's round-tripping contract, applied to glyphs. A twelve-letter word is twelve glyph records in the live scene but one node in the scene document, carrying the string, the atlas path, the em size, letter spacing, thickness, material, and placement — never the runtime texture slot its atlas occupies. On reload, the engine re-resolves the atlas and rebuilds the records; derived geometry never touches the document. This is the same principle the Studio applies to meshes and composites: documents name what to rebuild, and the machinery that rebuilds it stays out of the file.
Related reading
- Measuring WebGPU Compute Honestly: an A/B Discipline — the standing A/B discipline: mirrored slot order, noise floors, and pre-registered kill criteria, this page's failed one among them.
- The Scene Document Model: Document Structure for 3D Elements — the document structure scene text belongs to: the element tree, stable addresses, per-element state, and the round-tripping serialization whose recipe nodes appear on this page.
- What Is a 3D Spatial Web Interface? — the category definition this text machinery serves.
- Mapping CSS Box Models and Grid Layouts to 3D Coordinates — how real CSS grid layout places cards in 3D space.