How Serialization and Deserialization of .splat Files Works in Supersplat

Supersplat serializes Gaussian splat data into a compact 32-byte binary format using the serializeSplat function in src/splat-serialize.ts, while deserialization is delegated to the external @playcanvas/splat-transform library which parses the same little-endian layout back into renderable geometry.

Supersplat is a PlayCanvas-based editor and viewer for 3D Gaussian Splatting models. Understanding the serialization and deserialization of .splat files is essential for developers integrating this compact binary format into custom pipelines or extending the editor's import/export functionality. The format stores transformed Gaussian data in a fixed-structure binary layout optimized for web delivery and real-time rendering.

The 32-Byte Binary Layout of .splat Files

Each Gaussian (splat) occupies exactly 32 bytes stored in little-endian format. The serializeSplat function in src/splat-serialize.ts writes these records sequentially to a Uint8Array buffer, with the SingleSplat helper preparing per-Gaussian data transformations before writing.

Field Structure and Byte Offsets

The binary record follows this strict memory layout:

Byte Offset Field Type Description
0-11 x, y, z float32 World-space position (pre-transformed by palette and 180° Z-flip)
12-23 scale_0, scale_1, scale_2 float32 Log-scale values converted to linear space
24-26 f_dc_0, f_dc_1, f_dc_2 uint8 Base color after SH-C₀ conversion
27 opacity uint8 Alpha after sigmoid transformation
28-31 rot_0, rot_1, rot_2, rot_3 uint8 Quaternion components packed to [0, 255] range

Data Transformations During Serialization

The writer applies specific mathematical transforms to optimize storage and rendering performance:

  • Position: Values are already transformed by the palette matrix and the 180° Z-flip applied by the PLY exporter.
  • Scale: Log-scale values are converted to linear space using Math.exp() before writing to maximize precision for small values.
  • Color: Spherical Harmonics C₀ coefficients are converted via (0.5 + SH_C0 * value) * 255 and clamped to the [0, 255] range.
  • Opacity: The sigmoid function 1 / (1 + Math.exp(-opacity)) maps the value to linear alpha, then scales to 8-bit.
  • Rotation: Quaternion components are packed into unsigned bytes using value * 128 + 128 to map [-1, 1] to [0, 255].

Implementing Serialization in src/splat-serialize.ts

The serializeSplat function constructs a Uint8Array of length totalGaussians * 32 and uses a DataView to write little-endian values. The function iterates over splat data, applying the transformations above to each Gaussian before writing to the buffer at the correct byte offset.

// From src/splat-serialize.ts (lines 110-147)
const result = new Uint8Array(totalGaussians * 32);
const dataView = new DataView(result.buffer);

// Position (little-endian float32)
dataView.setFloat32(off + 0, data.x, true);
dataView.setFloat32(off + 4, data.y, true);
dataView.setFloat32(off + 8, data.z, true);

// Scale (converted from log to linear)
dataView.setFloat32(off + 12, Math.exp(data.scale_0), true);
dataView.setFloat32(off + 16, Math.exp(data.scale_1), true);
dataView.setFloat32(off + 20, Math.exp(data.scale_2), true);

// Color (SH-C₀ conversion)
dataView.setUint8(off + 24, clamp((0.5 + SH_C0 * data.f_dc_0) * 255));
dataView.setUint8(off + 25, clamp((0.5 + SH_C0 * data.f_dc_1) * 255));
dataView.setUint8(off + 26, clamp((0.5 + SH_C0 * data.f_dc_2) * 255));

// Opacity (sigmoid)
dataView.setUint8(off + 27, clamp((1 / (1 + Math.exp(-data.opacity))) * 255));

// Rotation (quaternion packing)
dataView.setUint8(off + 28, clamp(data.rot_0 * 128 + 128));
dataView.setUint8(off + 29, clamp(data.rot_1 * 128 + 128));
dataView.setUint8(off + 30, clamp(data.rot_2 * 128 + 128));
dataView.setUint8(off + 31, clamp(data.rot_3 * 128 + 128));

The clamp helper ensures all uint8 values remain within the 0-255 range. The filesystem abstraction in src/io/write/writer.ts and src/io/write/index.ts handles the actual byte stream output.

Deserialization via @playcanvas/splat-transform

Supersplat does not implement its own binary parser. Instead, it delegates deserialization of .splat files to the external @playcanvas/splat-transform package. This library provides the Splat class that reconstructs GSplatData structures from the binary buffer.

When loading a file, the viewer instantiates Splat directly from the Uint8Array:

import { Splat } from '@playcanvas/splat-transform';

// Load binary buffer (e.g., from FileSystem abstraction)
const data = await fs.readFile('model.splat');
const splat = new Splat(data);

The splat-transform library reads the buffer in 32-byte strides, extracting fields in the same order defined by the Supersplat writer. It automatically applies the inverse of the 180° Z-flip to restore correct world-space orientation, and reverses the color, opacity, and quaternion transformations to recover the original floating-point values.

Complete Export-Import Cycle Example

The following demonstrates a round-trip using both the Supersplat serializer and the external deserializer:

import { serializeSplat } from './splat-serialize';
import { Splat } from '@playcanvas/splat-transform';

// Export: Write binary .splat file
await serializeSplat(splats, { /* SerializeSettings */ }, fileSystem);

// Import: Read back into GSplatData structure
const binary = await fileSystem.readFile('output.splat');
const loaded = new Splat(binary);

This round-trip is lossless because both sides use identical field ordering, little-endian byte alignment, and symmetric mathematical transformations for color, opacity, and rotation encoding.

Summary

  • Binary Format: Each Gaussian occupies 32 bytes containing position (12 bytes), scale (12 bytes), color (3 bytes), opacity (1 byte), and rotation (4 bytes) in little-endian format.
  • Serialization: The serializeSplat function in src/splat-serialize.ts writes data using DataView, applying Math.exp() to scales, sigmoid to opacity, and 8-bit packing to quaternions.
  • Deserialization: The @playcanvas/splat-transform package parses the binary layout, automatically reversing transformations and the 180° Z-flip to restore original geometry.
  • Key Files: Writer logic resides in src/splat-serialize.ts and src/io/write/, while parsing is handled by the external dependency imported in viewer code such as src/pc-app.ts.

Frequently Asked Questions

What is the exact byte size per Gaussian in a .splat file?

Each Gaussian requires exactly 32 bytes. This includes 12 bytes for position (three float32), 12 bytes for scale (three float32), 3 bytes for base color (three uint8), 1 byte for opacity (uint8), and 4 bytes for rotation quaternion (four uint8).

Why does Supersplat use an external library for deserialization?

Supersplat delegates parsing to @playcanvas/splat-transform to maintain compatibility with the broader PlayCanvas ecosystem. This separation allows any PlayCanvas-based viewer using the same library to load files exported from Supersplat without implementing custom binary parsers.

How are rotations stored in the .splat binary format?

Rotations are stored as four unsigned 8-bit integers representing quaternion components. The serializer applies the transformation value * 128 + 128 to map the [-1, 1] range to [0, 255], while the deserializer reverses this calculation to recover the original floating-point quaternion values.

Is the .splat format lossless compared to PLY?

The format is designed for efficient rendering rather than archival precision. While position and scale use full float32 precision, color, opacity, and rotation are quantized to 8-bit values. This introduces minor quantization error compared to PLY's floating-point storage, but enables significantly smaller file sizes and faster GPU upload.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →