Gaussian splat formats and loaders
Khronos Gaussian splats, 3D Tiles, and SPZ
Decoded glTF primitives declaring KHR_gaussian_splatting can be prepared directly without adding
a loaders.gl or glTF dependency to the rendering package:
import {loadGPUSplatDataFromGLTF, makeGPUSplatDataFromGLTF} from '@luma.gl/splats';
const prepared = makeGPUSplatDataFromGLTF(device, decodedPrimitive, {
sourceBatchIndex: tile.sourceBatchIndex,
rowIndexBase: tile.firstSourceRow,
maxSphericalHarmonicsDegree: 2
});
const compressed = await loadGPUSplatDataFromGLTF(device, compressedPrimitive, {
signal,
decodeCompressedPrimitive: (primitive, options) =>
applicationOwnedSPZDecoder.decode(primitive, options)
});
The structural adapter validates POINTS primitives, the supported ellipse kernel, color space,
projection, and sorting method. It preserves source POSITION, decodes normalized scale/opacity
accessors, converts glTF XYZW quaternions to renderer WXYZ order, reconstructs spherical-harmonic
DC radiance, and packs complete degree-one through degree-three RGB coefficient bands. Authored
EXT_mesh_features feature identifiers become stable source semantic IDs. Compressed
KHR_gaussian_splatting_compression_spz_2 payloads are handed to an explicitly caller-owned
decoder; this renderer does not claim to parse SPZ or fetch 3D Tiles itself.
loaders.gl already provides Tileset3D and Tiles3DSource for 3D Tiles traversal, content
fetching, tile transforms, cache management, and request scheduling. Its glTF loader already
decodes EXT_mesh_features and EXT_structural_metadata. The existing SPZLoader currently
supports SPZ version 4, not SPZ version 2. End-to-end Gaussian 3D Tiles require loader-owned
KHR_gaussian_splatting and KHR_gaussian_splatting_compression_spz_2 runtime handlers, SPZ v2
decoding, and an application bridge from selected tiles into prepared splat batches or the existing
SplatLayer. loaders.gl already computes Tile3D.computedTransform, but the paged splat renderer
currently exposes one global model-view-projection transform and no per-page model transform. The
remaining renderer-side integration must therefore apply loader-computed transforms per tile/page.
The tileset traversal and layer already exist; follow
tracking issue #1245 for the missing integration.
Local loaders.gl 5 alpha showcase
The Gaussian Splats showcase normally generates a deterministic scene without depending on loaders.gl. To exercise a neighboring loaders.gl 5 alpha checkout instead, expose its location when starting the standalone example:
VITE_LOADERS_GL_ROOT=/path/to/loaders.gl \
yarn workspace luma.gl-examples-showcase-gaussian-splats start
Open the example with ?loaders=local to stream the complete 741,883-splat Train scene from the
same Hugging Face catalog used by the loaders.gl Gaussian splat example. Use
?loaders=local&scene=drjohnson, scene=playroom, or scene=truck to select the other catalog
scenes. If the Hugging Face CDN is unavailable, Train automatically falls back to its two
GitHub-hosted PLY segments; scene=train-github selects those segments directly.
Provide an authorized custom RAD source with ?source=https://example.com/scene.rad. On WebGPU,
the showcase first range-fetches the root page, then uses the live camera and authored per-row
child links to request, cancel, and evict only the source pages needed by its current hierarchy frontier.
GPUPagedSplatRenderer projects the selected original rows and sorts them globally across bounded
GPU segments. The default residency window retains at most one million original source rows; set
&residentSplats=250000 to choose another whole-page source budget. The overlay reports the
complete authored source count, current selected rows, and resident pages.
The website showcase ships a bounded module-worker pool for actual RAD chunk decoding. Abortable source byte ranges are transferred to a background worker, parsed with the isolated loaders.gl 5 bundle, serialized as transferable Arrow IPC, and reconstructed without losing original child-row links or global source identities. Unsupported worker environments retain an explicit main-thread fallback. Compatible RAD sources use Spark-calibrated best-first angular refinement, nonlinear coarse-level opacity, analytic covariance projection, and area-preserving Gaussian antialiasing. Explicit CPU and non-hierarchical RAD sources retain their bounded whole-page path.
Use ?loaders=local&scene=fixture for the lightweight 1,000-splat parser fixture, or provide
source to select a custom .ply, .splat, .ksplat, .spz, or .rad file. Full PLY scenes
are streamed through their original Arrow record batches, and the showcase reports download,
batch, and splat progress while retaining independently owned GPU buffers. The loader remains an
application-level dependency; @luma.gl/splats continues to own only GPU data and rendering.
Both the default WebGPU graph and the explicit &renderer=cpu comparison retain and evaluate
camera-dependent higher-order spherical harmonics.
GraphDECO captures do not embed a universal world-up direction. The showcase applies known scene-specific up vectors and, for Truck, its published initial camera; unfamiliar custom sources retain the existing Z-up default. Foreground-centered framing, preserved manual camera movement during streaming, and idle redraw suppression keep large scenes easier to inspect.
Package boundaries
@loaders.gl/splatsparses Gaussian splat file formats and produces application-level data.@loaders.gl/tilesand@loaders.gl/3d-tilesown tileset traversal, content transport, transforms, request scheduling, and caching.@loaders.gl/gltfowns glTF parsing, extension decoding, and feature metadata.@luma.gl/splatsowns rendering, Gaussian projection, sorting, and GPU resource lifetimes.- Applications or deck.gl layers own viewport integration, file selection, and interactive UI.
This separation keeps the renderer reusable from standalone luma.gl applications and deck.gl adapters while preserving streaming batch boundaries.
Related pages
- Gaussian splats overview
- Gaussian Splat Viewer
- GPU scheduling