Skip to content
Ben is currently available for contract work for 3D & web solutions — reach out.

Back to Blog Listing

How to Use Three.JS's new Native Gaussian Splats

How to use Three.js's native Gaussian Splatting support (GaussianSplatMesh, SPZLoader). Load .spz/.ksplat/.splat/glTF splats, pick a file format, and run a capture-to-render workflow with Polycam, Luma AI, and SuperSplat.

Ben Houston7 min read

Three.js updcoming release r186 adds native 3D Gaussian Splatting.

Previously, I've covered in Adding Native Gaussian Splatting Support to Three.js the underlying technical details.

This post is the practical companion to that one: what Gaussian Splats are good for, how to load and render one in a few lines of code, which file format to pick, and how to go from a real-world capture to a splat you can drop into a Three.js scene.

Gaussian Splats#

A Gaussian Splat is a point cloud where every point is a fuzzy, oriented, colored 3D ellipsoid (a "splat") instead of a hard vertex. Sorted, thousands to millions of them blend into a photorealistic image. You skip surface reconstruction: meshing, UVs, baked materials.

Splats fit capturing real-world objects and scenes and showing them in high fidelity, including subjects that are hard to model by hand: foliage, fur, reflective or translucent surfaces, cluttered rooms, museum artifacts. The result looks like the source photos, with a fraction of traditional 3D-reconstruction work, and you treat it as a normal object in your Three.js scene, sorted and shaded each frame.

Tomatoes Gaussian Splat rendered in Three.js

Scale: GaussianSplatMesh fits a single captured object or a room-scale scene. It has no level-of-detail (LOD) streaming and no spatial segmentation or culling, so city-scale captures and multi-gigabyte splat clouds need tiling or reduction by hand. Large-scene tooling can sit on this later (implementation post).

Loading an SPZ file#

import * as THREE from 'three/webgpu';
import { SPZLoader } from 'three/addons/loaders/SPZLoader.js';
import { GaussianSplatMesh } from 'three/addons/objects/GaussianSplatMesh.js';

const renderer = new THREE.WebGPURenderer();
await renderer.init();

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 100 );
camera.position.set( 0, 0.3, 2 );

// 1. Load the splat data
const splatGeometry = await new SPZLoader().loadAsync( 'model.spz' );

// 2. Wrap it in a mesh and add it to the scene
const splats = new GaussianSplatMesh( splatGeometry );
scene.add( splats );

// 3. Render as usual. The mesh sorts itself every frame by default.
renderer.setAnimationLoop( () => {

	renderer.render( scene, camera );

} );

One loader call, one new GaussianSplatMesh( geometry ), scene.add(). GaussianSplatMesh extends THREE.Mesh, so it composes with the rest of the scene graph (transforms, visible, raycasting groups) like any other object.

GaussianSplatMesh requires WebGPURenderer. The renderer is TSL nodes plus compute shaders for the depth sort. Resolve both three/webgpu and three/tsl in your import map.

Picking a file format#

Three.js ships five loaders for Gaussian Splat data. All produce the same internal BufferGeometry shape (position, covariance, color, and optional packed sphericalHarmonics1..3 attributes) that GaussianSplatMesh consumes:

LoaderExtensionNotes
SPZLoader.spzRecommended. Niantic's compact format. v4 is zstd-compressed and streamed section-by-section: smallest files and fastest to load. Also reads legacy v1–v3 (gzip).
PLYGaussianSplatLoader.plyMost interoperable. Native output of the original 3D Gaussian Splatting research code and most training/cleanup tools, so this is the format you receive most often. Uncompressed and per-vertex text/binary, large on disk and slow to load compared to .spz.
KSPLATLoader.ksplatFormat used by the GaussianSplats3D viewer. Useful if you already have assets from that pipeline.
SPLATLoader.splatOriginal fixed 32-byte-per-splat format (antimatter15/splat). Uncompressed, easy to generate, large on disk.
GLTFGaussianSplatLoaderExtension.gltf / .glbImplements the KHR_gaussian_splatting glTF extension, so splats can travel inside a normal glTF asset alongside meshes, cameras, and animations.

Use SPZ version 4 for viewers when you can choose: smallest transfer, fastest parse.

Loading glTF splats#

GaussianSplatMesh needs WebGPURenderer, so GLTFLoader does not register the glTF plugin for you. Register it yourself:

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { GLTFGaussianSplatLoaderExtension } from 'three/addons/loaders/GLTFGaussianSplatLoaderExtension.js';

const loader = new GLTFLoader();
loader.register( ( parser ) => new GLTFGaussianSplatLoaderExtension( parser ) );

const gltf = await loader.loadAsync( 'scene.gltf' );
scene.add( gltf.scene ); // splat primitives arrive as GaussianSplatMesh instances

A mesh primitive that uses KHR_gaussian_splatting loads as a GaussianSplatMesh (or a Group of them, for multi-primitive meshes) and lands in the returned scene graph like any other glTF node, mixed with regular meshes, cameras, and animations if the file has them.

Loading PLY splats#

Splats often arrive as .ply files: native output of the original 3D Gaussian Splatting research code and many training/cleanup tools. PLYGaussianSplatLoader follows the same convention as SPZLoader and SPLATLoader:

import { PLYGaussianSplatLoader } from 'three/addons/loaders/PLYGaussianSplatLoader.js';
import { GaussianSplatMesh } from 'three/addons/objects/GaussianSplatMesh.js';

const splatGeometry = await new PLYGaussianSplatLoader().loadAsync( 'point_cloud.ply' );
scene.add( new GaussianSplatMesh( splatGeometry ) );

Use this when a splat only exists as a raw .ply export. Otherwise convert to .spz once up front rather than paying PLY's larger, uncompressed size on every load.

Loading SPLAT and KSPLAT files (legacy formats)#

SPLATLoader and KSPLATLoader exist for legacy compatibility: assets and pipelines built around antimatter15/splat and the GaussianSplats3D viewer. Neither format compresses well compared to .spz (both are raw, fixed-size-per-splat binary dumps), so files are larger and slower to download and parse. Keep them for assets you already have. For new work, convert to .spz v4.

The API matches SPZLoader. Swap the loader class and point it at the matching extension:

import { SPLATLoader } from 'three/addons/loaders/SPLATLoader.js';
import { GaussianSplatMesh } from 'three/addons/objects/GaussianSplatMesh.js';

const splatGeometry = await new SPLATLoader().loadAsync( 'model.splat' );
scene.add( new GaussianSplatMesh( splatGeometry ) );
import { KSPLATLoader } from 'three/addons/loaders/KSPLATLoader.js';
import { GaussianSplatMesh } from 'three/addons/objects/GaussianSplatMesh.js';

const splatGeometry = await new KSPLATLoader().loadAsync( 'model.ksplat' );
scene.add( new GaussianSplatMesh( splatGeometry ) );

Both loaders produce the same BufferGeometry shape as SPZLoader, so GaussianSplatMesh and everything downstream (sorting, rendering, glTF export) behaves the same regardless of which loader you used. File size and load time are the difference, so produce .spz v4 yourself.

Capture, clean up, convert, render#

Four steps from a real-world subject to a splat in your Three.js scene:

1. Capture#

Walk around your subject with a mobile scanning app. Overlapping photos or video go to the app (or its cloud backend), which reconstructs a splat.

  • Polycam: Gaussian Splat capture in the mobile app, cloud processing.
  • Luma AI: consumer splat-capture app, cloud-processed.

Both reconstruct the splat from your capture.

2. Clean up#

Raw reconstructions have stray floater splats, background clutter, and rough edges. Trim those before you ship.

  • Polycam has cropping and cleanup tools if you captured with it and want to stay in one app.
  • SuperSplat (PlayCanvas's free web-based editor) is built for splat editing: cropping, erasing floaters, re-exporting. It works with splats from any source. Use it when you want more control than a capture app gives, or when the splat came from somewhere else.

3. Convert to SPZ#

Once you have a clean splat (usually .ply or .splat), convert it to .spz v4:

4. Render#

Put the .spz file in your project and load it with SPZLoader and GaussianSplatMesh, as in the example above.