virgo_loader.mjs, virgo_terrain.mjs, virgo_grove.mjs, etc.) that construct ~456 Object3D nodes. The original ~200 materials have been consolidated to ~6 shared materials to reduce draw call overhead. Each module creates meshes, sets transforms, and attaches them to the scene tree. Three.js handles the rest.
| Stage | What Happens | Cost |
|---|---|---|
| Module Load | 26 ES modules imported, each builds geometry and attaches to scene tree | ~200KB network, ~500ms init |
| Scene Build | Object3D nodes created with position, rotation, scale. BufferGeometry filled with vertices. | ~120MB memory |
| Material Setup | ~200 logical materials consolidated to ~6 shared. Shader programs compiled on GPU. | Amortized at init |
| Per-Frame: Tree Walk | Three.js traverses entire Object3D tree, updates world matrices | O(n) with 456 nodes |
| Per-Frame: Frustum Cull | Bounding sphere test against 6 frustum planes per object | ~0.5ms (456 tests) |
| Per-Frame: Sort | Visible objects sorted by material, then by depth for transparency | ~0.3ms |
| Per-Frame: Draw Calls | Each visible geometry+material = 1 WebGL draw call. JS→WebGL binding overhead per call. | ~4ms for ~200 calls |
| Per-Frame: Rasterize | GPU fills triangles, runs vertex + fragment shaders, depth test, blend | ~2ms GPU time |
| Background: GC | V8 garbage collector pauses all JS. Unpredictable timing. | 2-5ms every ~500ms |
| Metric | Value | Notes |
|---|---|---|
| Frame time (p50) | ~12ms | 83 fps nominal, but GC jitter degrades perceived smoothness |
| Frame time (p99) | ~20ms | GC pauses push worst frames to 50fps territory |
| GC pauses | 2-5ms / 500ms | V8 minor GC. Unavoidable in JS. Correlates with allocation rate. |
| Draw calls per frame | ~200 | After material consolidation. Was ~456 before sharing. |
| Memory footprint | ~120MB | Three.js runtime + BufferGeometry + textures + module closures |
| Three.js framework size | ~300k lines | Loaded for ~25k lines of application code (12:1 ratio) |
| Scene objects | ~456 | Each requires matrix update, frustum test, potential draw call |
| Init time | ~500ms | Module load + geometry build + shader compile |
| Fixed Cost | Why It Exists | Impact |
|---|---|---|
| GC pauses | JavaScript is garbage-collected. V8 must pause to reclaim. Three.js allocates vectors/matrices per frame. | 2-5ms stalls, visible as micro-stutter |
| Draw call overhead | WebGL requires one JS→native call per geometry+material pair. Cannot batch heterogeneous meshes. | ~4ms/frame in binding overhead alone |
| Tree traversal | Object3D.updateMatrixWorld() walks every node. Cannot skip nodes without breaking child transforms. | O(n) per frame, n = total scene nodes |
The polygon pipeline maintains a tree of objects, each with geometry, material, and transform.
Lithos replaces all of it with a single function: float sceneSDF(vec3 p). This function
takes a point in space and returns the signed distance to the nearest surface. No vertices, no triangles,
no Object3D tree, no draw calls, no GC.
What goes away: Object3D tree (456 nodes), BufferGeometry arrays, material system, frustum culling, draw call sorting, JS→WebGL binding overhead, per-frame matrix updates, and the 300k-line Three.js dependency.
What replaces it: One fullscreen quad. One fragment shader. One draw call.
The GPU evaluates sceneSDF(p) per pixel via raymarching. All geometry is mathematical.
All lighting is computed from the distance field gradient. The entire scene fits in a single shader program.
/poly to any sign URL to render via the Three.js scene graph pipeline.