Fable created novel 4D splat format

adamraudonis1 pts1 comments

.splat4d

Interact

Live preview, streamed and decoded in your browser: a 2-second dynamic scene as one 7.4 MB<br>.splat4d file — 58&times; smaller than its 427 MB of raw .splat frames.<br>Press Interact to take the camera: drag to orbit, ctrl-scroll to zoom.<br>Needs WebGPU (Chrome 113+, Safari 26+, Firefox 141+ on Windows) —<br>open the full demo for more scenes and live encoding controls.

How it works

Static / dynamic split

In a typical dynamic capture, most splats are background that never moves beyond the bound.<br>They are stored once — the entire background of a 1.6 GB sequence costs a few MB.<br>Classification is exact: a splat is static iff a single quantized value satisfies the bound<br>against its min and max over the whole clip.

Deadband &ldquo;hold&rdquo; tracks

A dynamic splat&rsquo;s stored value changes only when the true value would violate the bound against it.<br>This kills quantization flicker, makes temporal deltas mostly zero, and the check itself enforces<br>the guarantee before every emitted symbol.

H.265-style closed GOPs

Keyframe (absolute quantized values) every N frames, then P-frames of exact integer deltas.<br>Every GOP chunk decodes independently &rarr; seeking never touches other chunks.<br>Key streams are laid out before delta streams inside each chunk, so a scrub can fetch<br>~10% of a chunk and show the keyframe instantly.

Entropy stack

Morton-ordered splats, zigzag-coded integer deltas, byte-plane shuffle (Blosc-style), zstd per<br>stream. Output lands at &asymp;100% of the order-0 entropy of its own symbol streams.

Inside the file

A .splat4d file has three parts. A small header carries the bounds, the quantization<br>steps, and a chunk index with absolute byte ranges — everything a client needs to plan its<br>fetches. The static section holds the per-splat masks and base values: fetch it once and the complete<br>scene is on screen. The rest is one self-contained GOP chunk per ~1 s of video, with key streams<br>laid out before delta streams.

"SP4D" + header JSON<br>STATIC section &rarr; full first view<br>GOP chunk 0 [keys][deltas]<br>GOP chunk 1 …

Error bounds

Every attribute of every splat in every decoded frame is within a user-chosen bound of the source —<br>not on average, not in PSNR: pointwise and deterministic.

attributebounddefault<br>position&plusmn; millimeters, L&infin; per axis&plusmn;2 mm<br>color RGB&plusmn; 8-bit levels per channel&plusmn;4/255<br>opacity&plusmn; 8-bit levels&plusmn;4/255<br>rotation&plusmn; quaternion component (units of 1/128, up to sign)exact (&plusmn;0)<br>scale&plusmn; relative %, per axis&plusmn;2%

Mechanism: SZ/ZFP-style error-bounded quantization (step = 2&times;bound &rArr; error &le; bound by<br>construction). After quantization everything is integer math — temporal deltas can never drift, and the<br>Rust and JavaScript decoders reconstruct bit-identical values.

Stream from object store

The format is designed for plain HTTP Range requests against S3 / GCS / R2 / any static host —<br>no server logic, no manifest files, no video container. A client needs exactly:

bytes=0-262143 &rarr; magic + header JSON (all byte offsets are absolute)

one range for the STATIC section (a few MB) &rarr; complete first view on screen

one range per GOP chunk during playback / prefetch

on seek: the chunk-prefix range (TOC + keys) first &rarr; keyframe on screen in ~100–150 ms,<br>then the rest of the chunk &rarr; exact frame

Object stores support this natively. For browser clients, set CORS to allow the<br>Range header and expose Content-Range:

[{ "AllowedMethods": ["GET", "HEAD"],<br>"AllowedOrigins": ["https://your-site"],<br>"AllowedHeaders": ["Range"],<br>"ExposeHeaders": ["Content-Range", "Content-Length", "Accept-Ranges"] }]<br>Payloads are already zstd-compressed inside the container, so store objects with no<br>Content-Encoding — range math stays byte-exact and nothing double-compresses.

Benchmarks

Eight sequences from three independent capture pipelines:<br>Dynamic 3D Gaussians (CMU Panoptic dome —<br>juggle, boxes, softball, tennis), Neu3D cooking scenes via<br>SpacetimeGaussians/splaTV (flame = backyard<br>BBQ, sear = kitchen chef), and Technicolor (birthday party, 659k splats) — all converted to<br>per-frame antimatter15 .splat files (32 B/splat), 20 fps. splat4d encodes use default<br>bounds (&plusmn;2 mm / &plusmn;4 color / exact rot / &plusmn;2% scale); gzip is per-frame -9.<br>For context, the best generic lossless baseline (zstd-19 --long over the whole series)<br>reaches only 2.5&times;. Full methodology and more baselines:<br>BENCHMARKS.md.

loading benchmarks.json…

Viewer

Raw WebGPU, a line-by-line port of the<br>antimatter15/splat renderer, pixel-verified against it.

metriclocalthrottled 50 Mbps<br>full first view (header + static section)141–157 ms791 ms<br>scrub into unbuffered region &rarr; keyframe visible—145 ms<br>playback60 fps @ 336k splats &middot; worker decode 2.5–27 ms/frame &middot; sort 1–25 ms

Using it

A time series of antimatter15 .splat frames &rarr; one small, seekable file:

# Python (pip install splats4d)<br>splat4d encode -i...

plusmn splat chunk rarr range static

Related Articles