Polygon Rendering Mode

Three.js Scene Graph · Object3D · BufferGeometry · WebGL Rasterization

What It Is

Traditional scene graph rendering via Three.js. Every visible object is a node in an Object3D tree. Geometry is stored as BufferGeometry — vertex positions, normals, UVs packed into typed arrays. Each distinct surface gets a Material (MeshStandardMaterial, ShaderMaterial, etc.). The renderer walks the tree each frame, frustum-culls invisible nodes, sorts by material, and issues one WebGL draw call per unique geometry+material pair. The GPU rasterizes triangles into pixels.
~456
Scene Objects
~6
Shared Materials
~26
JS Modules
~25k
Lines of JS
Virgo polygon mode loads ~26 JavaScript modules (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.

How It Works

Rendering Pipeline

JS modules Three.js scene Object3D tree frustum cull WebGL draw calls GPU rasterize pixels

Detailed Breakdown

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

Performance Characteristics

~12ms
Frame Time p50
±8ms
p99 Jitter
2-5ms
GC Pause
~500ms
GC Interval
~120MB
Memory
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

What It Does Well

Strengths

  • Mature ecosystem — Three.js is well-debugged across browsers and devices
  • Complex scene management: LOD switching, instanced meshes, post-processing chains
  • Easy to add new objects — create a Mesh, set position, add to scene. Done.
  • Good mobile support — WebGL 1/2 fallbacks, responsive to device capability
  • Rich material system — PBR, custom shaders, environment maps, shadow maps
  • Large community — examples, extensions, and debugging tools readily available
  • Incremental complexity — start simple, add features without rewriting

Limitations

  • GC pauses are the performance ceiling. No amount of optimization eliminates V8 garbage collection stalls. 2-5ms pauses every 500ms are structural, not bugs.
  • Three.js abstraction tax. 300k lines of framework to support ~25k lines of application code. 12:1 overhead ratio.
  • JS→WebGL binding overhead per draw call. Each call crosses the JS/native boundary, sets uniforms, binds buffers.
  • Object3D tree traversal cost scales linearly with scene complexity. 456 nodes = 456 matrix updates every frame.
  • Cannot inline/fuse shader operations across materials. Each material is a separate shader program. No cross-material optimization.
  • Version locked. Cosmos uses Three.js r160, home uses r175. Cannot upgrade to WebGPU renderer without breaking both.
  • Memory overhead. Every mesh duplicates geometry data in JS heap and GPU buffer. ~120MB for a scene that SDF renders in ~40MB.

Why the Ceiling is Structural

The polygon pipeline has three fixed costs that cannot be optimized away:
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

Lithos Migration Path

sceneSDF(vec3 p) Replaces the Entire Scene Graph

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.

456 objects + 200 draw calls + GC sceneSDF(vec3 p) — 1 function, 1 draw call, 0 GC

Available /sign/poly Routes

Every zodiac sign supports polygon mode. Append /poly to any sign URL to render via the Three.js scene graph pipeline.
/aries/poly
Aries
/taurus/poly
Taurus
/gemini/poly
Gemini
/cancer/poly
Cancer
/leo/poly
Leo
/virgo/poly
Virgo
/libra/poly
Libra
/scorpio/poly
Scorpio
/sagittarius/poly
Sagittarius
/capricorn/poly
Capricorn
/aquarius/poly
Aquarius
/pisces/poly
Pisces