null-graph
v1.1.0
Published
A direct-to-GPU rendering framework for massive web worlds. Zero scene graph. Zero copy. Infinite scale.
Downloads
321
Readme
NullGraph
Zero scene graph. Zero copy. Infinite scale.
A Data-Oriented WebGPU rendering framework for massive web worlds.
NullGraph is a brutalist, high-performance rendering library designed specifically for Web Workers and Data-Oriented Design (DOD).
It completely abandons the traditional Object-Oriented Scene Graph (Root -> Node -> Mesh -> Geometry) in favor of mapping raw, contiguous ArrayBuffers directly to WebGPU Storage Buffers.
If you are building an MMO, a voxel engine, or a multiverse with tens of thousands of dynamic entities, NullGraph keeps rendering off your main thread and out of the garbage collector.
Why NullGraph?
A retained scene graph costs you a traversal, a matrix update, and a draw call per object, per frame — on the main thread, allocating as it goes. That is fine at a thousand objects and fatal at a hundred thousand: the frame time becomes a function of how much exists rather than how much is visible, and the garbage collector starts showing up in your frame graph.
NullGraph takes the opposite approach — it does less, and moves what remains to the GPU:
Zero Scene Graph: No
.traverse(), no.updateMatrixWorld(). The GPU reads your flat array directly.Zero-Copy Streaming: Calculate your ECS layout in a Web Worker, pass the
Float32Arrayto the main thread, and blast it straight to VRAM.Render Queues (Batches): Render thousands of unique object types simultaneously with minimal GPU state changes.
No GC Spikes: Memory is pre-allocated. No runtime object creation or destruction.
Compute-Driven Indirect Drawing: Offload culling entirely to the GPU. NullGraph supports WebGPU Compute Shaders that dynamically build
IndirectDrawArgs, resulting in zero CPU overhead for visibility checks.GPU-Driven Visibility (v1.0.5): Batteries-included culling on the GPU — frustum, Hi-Z occlusion, meshlet normal-cone (backface cluster) rejection, and screen-space-error LOD. Draw-call count becomes proportional to material count, not object count, so 100,000 instances still cost a handful of draws.
Shadows That Inherit the Culling: A shadow cascade is just another camera, so caster culling reuses the same GPU cull chain rather than duplicating it — 4 cascades over 100,000 instances is 4 indirect draws per caster mesh. Cached atlas tiles skip both their draw and their cull.
Dynamic Global Illumination: Not just an environment map — an irradiance volume filled by GPU probe capture, where each capture shades against the previous volume so bounce light accumulates into multi-bounce GI. Colour bleeding, indoor/outdoor transitions, and lighting that responds when you open a door — no ray tracing, no lightmap bake, no offline step.
Multi-Pass Architecture: Seamlessly chain offscreen render passes into screen-space post-processing pipelines (Bloom, CRT, HUD effects) by attaching textures directly to subsequent batches.
What's In The Box
A complete GPU-driven pipeline: geometry goes in as meshlets, the GPU decides what's visible, and the CPU never iterates the scene.
| Subsystem | What ships |
|---|---|
| Geometry | 13 primitives · one unified vertex/index pool · meshlet clusters (≤64v/≤124t) with bounds, normal cones, and LOD chains |
| Visibility | Two-tier GPU culling (instance + meshlet) · Hi-Z occlusion from an r32float max-pyramid · normal-cone rejection · screen-space-error LOD · per-view culling for any camera |
| Shadows | Cascaded shadow maps (sphere-fit, texel-snapped, caster-extruded) · shadow atlas with importance tiles, per-tile caching, and eviction · spot + point (6-face) shadows · light-view Hi-Z |
| Lighting | 7 light types · clustered-forward froxel binning (subgroup-accelerated when available) · tiled · standard |
| Indirect lighting | Split-sum IBL: GGX-prefiltered cube, BRDF LUT, SH-L2 irradiance · procedural or HDR sky · skybox pass |
| Global illumination | SH-L1 irradiance volumes · GPU probe capture · multi-bounce convergence · parallax-corrected reflection probes |
| Materials | PBR (Cook-Torrance), Lambert, Toon, Matcap, Emissive, Basic — one portable definition per family, shared by every draw path |
| Post | 16 effects · lifetime-aliased render targets · zero-allocation param arena |
| Platform | Worker rendering via OffscreenCanvas · capability negotiation · zero-copy buffer contracts throughout |
Three properties hold across all of it: draw calls scale with material count, not object count; the frame path allocates nothing (measured, not assumed); and the baseline runs on core WebGPU with no optional features required.
Verification status
Every subsystem is pinned by headless tests — 349 checks across five suites, covering the pure
math, every generated WGSL module, and system wiring through mock devices. Run with
npm run test:visibility · test:shadows · test:environment · test:geometry · test:post.
The GPU integration harnesses have not been run in a browser yet, and there is currently one only
for shadows (test/shadows-gpu/). The maths is verified; the pixels are not. That is the next
milestone, not a footnote.
Not in the box (yet)
Deferred / visibility-buffer shading · TAA and motion blur · volumetric fog · virtualized geometry (cluster-LOD streaming) · virtual shadow maps · leak-free DDGI probe visibility · compute skinning. All planned and specced — see the roadmap and architecture tree.
Installation and Setup
NullGraph is distributed as a modular ESM package. To maintain its "Zero-Copy" philosophy, it requires gl-matrix as a peer dependency to ensure your application and the engine share the same math structures.
1. Install via NPM
# Install the core engine
npm install null-graph
# Install required peer dependencies
npm install gl-matrix
# Recommended: Install WebGPU types for IDE autocomplete
npm install @webgpu/types --save-dev2. Module Architecture
NullGraph uses Subpath Exports to keep your production bundles lean. You only pay for the features you import.
null-graph
The core engine: device and capability negotiation, buffer/texture management, and the linear
executor. You hand render() a flat array of ComputeStageNode | RenderPassNode and it walks it
in order — consecutive compute stages batch into one WebGPU pass, and a render pass closes it,
which is where the compute→draw memory barrier comes from for free. Camera state is one 432-byte
uniform (view/proj/their inverses + eye, near/far/fov/aspect) shared by every batch, and indirect
draw is wired through: a batch flagged isIndirect is drawn with drawIndexedIndirect from a
buffer a compute stage filled.
null-graph/geometry
13 primitive generators, declarative vertex layouts, and the two-layer buffer story:
MegabufferBuilder packs every mesh into one vertex + index pool and records per-mesh
{indexCount, firstIndex, baseVertex}; MeshletBuilder consumes that pool and partitions each
mesh into ≤64-vertex / ≤124-triangle clusters, each with a bounding sphere and a normal cone, plus
discrete LOD chains. Meshlets store global indices into the mega pool, so one vertex allocation
backs both the coarse and fine draw paths.
null-graph/visibility
GPU-driven culling. Two tiers over the same substrate: instance-tier (one indirect draw per unique
mesh, independent of instance count) and meshlet-tier (per-cluster, drawn by vertex pulling over a
static index buffer, since WebGPU has no mesh shaders). A compute chain builds an r32float Hi-Z
max-reduction pyramid from last frame's depth, then culls by frustum, normal cone, and occlusion,
selects LOD by screen-space error, and compacts survivors into the indirect draw args. Nothing is
read back to the CPU — dispatch sizes come from CPU-known caps with idle threads early-outing. The
cull math has a CPU mirror (cullMath.ts) pinned by headless tests.
null-graph/shadows
Cascaded shadow maps with GPU caster culling. A cascade fills the same 108-float camera layout
the cull and draw shaders already consume, so caster culling is the visibility chain pointed at a
different uniform rather than new machinery. Cascades are fitted with a rotation-invariant bounding
sphere and snapped to whole shadow texels (a box fit shimmers on camera yaw; an unsnapped centre
crawls on translation), and the ortho near plane is extruded so off-screen casters still land in
the map. Bias is applied on the caster in the depth-only vertex shader. Receiver-side it
decorates the light system's WGSL — wrapping getIncidentLight and folding the shadow term
into attenuation — so Lambert, Toon and PBR all gain shadows with no material edits.
null-graph/environment
Indirect lighting — the half of the lighting equation that isn't a light. Phase 1 replaces the hardcoded ambient constant with the split-sum IBL model: a GGX-prefiltered environment cube (with solid-angle mip selection, so a sun disc doesn't shatter into fireflies at mid roughness), a split-sum BRDF LUT, and an SH-L2 irradiance projection that fits in 27 floats instead of a cubemap — which is what leaves binding budget for Phase 2. Sources are an equirect HDR, a procedural sky (zero assets), or precomputed coefficients from a Worker.
Phase 2 makes it vary through space: an SH-L1 irradiance volume sampled by hardware
trilinear filtering (the 8-probe blend is one textureSampleLevel), filled by GPU probe capture
— the shadow atlas architecture rendered in colour, amortized at probesPerFrame. Because each
capture shades against the previous volume, bounce light accumulates: multi-bounce global
illumination that converges over a few refresh cycles, with no ray tracing and no second data
structure. Local reflection probes with parallax-corrected box proxies handle specular, blending
to the global cube so a scene with no probes degrades exactly to Phase 1.
Both phases implement the same two WGSL functions, so swapping global IBL for spatial probes costs zero material edits.
null-graph/loaders
GLBParser with resource unpacking, SkeletonManager, and an Animator for keyframe sampling and
interpolation, feeding the vertex-shader skinning path in the material builders.
null-graph/materials
Basic, Lambert, Toon, Emissive, Matcap, and Cook-Torrance PBR. Each family is a portable pair —
WGSL bindings plus an fs_main body, parameterized by bind-group index — from one source of truth,
so the same material drops into a normal batch or a GPU-culled visibility batch unchanged. Lighting
arrives as an injected WGSL layout (getVisibleLightCount / getIncidentLight), which is what lets
the light system swap clustered for standard without any material knowing.
null-graph/lights
Data-oriented lighting: 7 proxy types writing into a flat 16-float-per-light array a Worker can
fill, adopted by reference. Three techniques — standard, tiled-forward, and compute-driven
clustered-forward, which bins lights into a 3D froxel grid (logarithmic depth slices) in a
compute stage, using subgroup ops when the device negotiated them and an atomic baseline otherwise.
null-graph/post
An explicit, pre-allocated post chain. TransientPool aliases render targets by lifetime analysis
so a 12-effect chain reuses a handful of textures, with history: 2 double-buffering reserved for
temporal effects. ParamArena keeps every effect's uniforms in one arena with zero per-frame
allocation, and ShaderComposer generates each pass's WGSL — 16 effects including Bloom, SSAO,
Bokeh, FXAA, and tonemapping.
null-graph/cameras
Orbital, Fly, Follow, and Path controllers as pure state objects — spherical-coordinate math with no DOM dependency — plus optional event proxies, so the same controller runs on the main thread or inside a Worker.
null-graph/debug-ui & null-graph/profiler
Real-time telemetry widgets and WebGPU timestamp-query GPU profiling.
The Architecture Demo Suite
Play the Live Demo
Github Source Code [v1.0.0]
Documentation
For comprehensive guides and API references, please check our documentation:
Guides
- Quick Start Guide — get a lit cube on screen
- Scene Composition — how to assemble any scene, end to end
- Engine Roadmap — target architecture, release plan, and the WebGPU realities that shape both
- Architecture Tree — every module and planned feature in one tree, plus what lands in each release
- Module Briefs — per-module goal, integration surface, and non-goals (incl. Environment + the physics plugin)
- Design Principles — the rules every module follows, and the case behind each
- Deprecated & Breaking API Changes — migration guide
Technical Designs
Implementation-ready specs. Each states its non-goals, cites the engine facts it depends on, and lists the acceptance tests up front.
- Meshlet Geometry — clusters, bounds, normal cones, LOD chains
- Visibility — two-tier GPU culling, Hi-Z, indirect draw
- Shadows · Phase 1 — cascade fitting, caster bias, the lighting decorator
- Shadows · Phase 2 — atlas, punctual lights, tile caching, light-view Hi-Z
- Lighting · Phase 1 — IBL: prefiltered env, BRDF LUT, SH irradiance
- Lighting · Phase 2 — irradiance volumes, probe capture, reflection probes
- Post-Processing — how the
PostChainis architected - Post-Processing Presets — ready-made Bloom / SSAO / Tonemap bundles
- Transient Pool — render-target recycling internals
API References
- Core (
null-graph) · Core Reference - Cameras (
null-graph/cameras) - Debug UI (
null-graph/debug-ui) - Environment (
null-graph/environment) · Shader & Buffer Reference - Geometry (
null-graph/geometry) - Lights (
null-graph/lights) · Shader & Compute Reference - Loaders (
null-graph/loaders) - Materials (
null-graph/materials) - Post-Processing (
null-graph/post) - Probes (
null-graph/environment, Phase 2) · Shader & Buffer Reference - Profiler (
null-graph/profiler) - Shadows (
null-graph/shadows) · Shader & Buffer Reference - Visibility (
null-graph/visibility) · Shader & Buffer Reference
Roadmap
NullGraph is the high-performance rendering backbone for the Axion Engine.
Core Architecture
[x] Multi-Object Render Queue / Batching
[x] Depth / Z-Buffer Integration (Proper 3D occlusion)
[x] VBO/IBO Geometry Buffer Manager
[x] Multi-Pass Rendering & Texture Attachments
[x] GPU Compute Frustum Culling & Indirect Drawing
[x] Geometry Builder &
null-graph/geometryextras[x] Megabuffer (unified vertex/index pool) & Meshlet Builder (bounds, cones, LOD)
[x] GPU-Driven Visibility — two-tier culling (instance + meshlet), Hi-Z occlusion, screen-space-error LOD
[ ] Deferred / Visibility-Buffer Shading
[ ] Virtual Geometry (Nanite-style cluster-LOD DAG)
Materials & Assets
[x] Physically Based Rendering (Cook-Torrance BRDF)
[x] Integrated PBR Material System (Albedo, Normal, ARM maps)
[x] Native GLB/GLTF Parsing & Resource Unpacking
[x] Alpha Blending & Additive Transparency States
Animation & Logic
[x] Hardware-Accelerated Skeletal Animation (GPU Skinning)
[x] Animation Timeline & Keyframe Interpolation (Animator)
[ ] Morph Targets / Shape Keys
[ ] GPU-Driven Particle Systems (Compute-based)
Lighting & Post-Processing
[x] Dynamic Light System (Point / Directional / Spot) via Zero-Copy proxies
[x] Real-time Light Culling — Tiled (Forward+) & Compute-Driven Clustered Forward
[x] Post-Processing Pipeline (Bloom, SSAO, Tonemap, Custom Effects)
[x] Directional Shadows / Cascaded Shadow Maps (CSM) — sphere-fit cascades, texel snapping, GPU caster culling, hardware PCF
[x] Shadow Atlas — punctual (spot + point) shadows, importance-driven tiles, per-tile caching, light-view Hi-Z
[x] Image-Based Lighting (IBL) & Environment Mapping — GGX prefilter, split-sum BRDF LUT, SH-L2 irradiance, procedural + HDR sky
[x] Dynamic Global Illumination — SH-L1 irradiance volumes, GPU probe capture, multi-bounce convergence, parallax-corrected reflection probes
[ ] DDGI per-probe visibility (leak-free probes) — needs bind-group budget reclaimed
[ ] Volumetric fog & atmospheric scattering
Showcase
Architecture Demos
| AoS | SoA | AoSoA | |:---------------------------------------------------:|:---------------------------------------------------:|:-----------------------------------------------------:| | | | |
GPU Compute & Post-Processing
| GPU Culling | Space Fleet | CRT Effect | |:----------------------------------------------------------:|:----------------------------------------------------------:|:----------:| | | |
PBR Materials & Animation
| Rusty Metal | Skeletal Animation | Morphogenesis | |:-----------------------------------------------------------:|:-------------------------------------------------------------------------:|:-------------------------------------------------------------:| | | | |
📦 Package Stats
| Metric | Value |
|--------|-------|
| Weekly Downloads | |
| Version |
|
| License |
|
| Minified + GZip |
|
🔗 Related Repositories
| Repository | Description | |--------------------------------------------------------------------------------------|-------------| | NullGraph Test Engine | Interactive demo suite & documentation hub | | Axion Engine | Full game engine built on NullGraph |
