LmCast :: Stay tuned in

How to Use Three.js's New Native Gaussian Splats

Recorded: Sept. 13, 2026, 11:09 a.m.

Original Summarized

How to Use Three.JS's new Native Gaussian SplatsSkip to contentBen is currently available for contract work for 3D & web solutions — reach out.Ben HoustonPortfolioAboutContact Back to Blog ListingHow to Use Three.JS's new Native Gaussian SplatsHow to use Three.js's native Gaussian Splatting support (GaussianSplat, SPZLoader). Load .spz/.ksplat/.splat/glTF splats, pick a file format, and run a capture-to-render workflow with Polycam, Luma AI, Scaniverse, and SuperSplat.Ben Houston • September 7, 2026 • 8 min readSharegraphicscodingwebgpugltfthree.jsJump to Section:Gaussian SplatsLoading an SPZ filePicking a file formatLoading PLY splatsLoading glTF splatsLoading SPLAT and KSPLAT files (legacy formats)Capture, clean up, convert, render1. Capture2. Clean up3. Convert to SPZ4. RenderThe newly released Three.js r186 release adds native 3D Gaussian Splatting support, and it's a big deal: splats have been usable in Three.js for a while through community add-ons, but now they're a first-class citizen of the engine, with a built-in mesh type and loaders for the major formats.
I covered the underlying technical details in an earlier post, Adding Native Gaussian Splatting Support to Three.js. This one is the practical companion: 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. Render thousands to millions of them, sorted back-to-front, and they blend into a photorealistic image, without any of the meshing, UV unwrapping, or material baking that traditional surface reconstruction needs.
That makes splats a great fit for capturing real-world objects and scenes and showing them in high fidelity, especially subjects that are hard to model by hand: foliage, fur, reflective or translucent surfaces, cluttered rooms, museum artifacts. Because a splat is built directly from photos rather than a hand-authored mesh, the result looks like the source material with a fraction of the traditional reconstruction work, and once it's loaded you treat it like any other object in your Three.js scene: sorted and shaded fresh each frame.

There is a scale limit worth knowing up front, though: GaussianSplat is built for a single captured object or a room-scale scene, not an entire city block. It has no level-of-detail (LOD) streaming and no spatial segmentation or culling, so a city-scale capture or a multi-gigabyte splat cloud needs tiling or reduction by hand before it will run smoothly. Large-scene tooling can sit on top of this foundation later, and I go into that groundwork in the implementation post.
Loading an SPZ file#
Here's the whole pipeline, start to finish: load a .spz file, wrap it in a mesh, and render it.
import * as THREE from 'three/webgpu';
import { SPZLoader } from 'three/addons/loaders/SPZLoader.js';
import { GaussianSplat } from 'three/addons/objects/GaussianSplat.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 GaussianSplat( splatGeometry );
scene.add( splats );

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

renderer.render( scene, camera );

} );

That's really all there is to it: one loader call, one new GaussianSplat( geometry ), and a scene.add(). Because GaussianSplat extends THREE.Mesh, it composes with the rest of the scene graph just like any other object, so transforms, visible, and raycasting groups all work the way you'd expect.
One requirement to keep in mind: GaussianSplat needs WebGPURenderer. The renderer is built from TSL nodes plus compute shaders for the depth sort, so make sure both three/webgpu and three/tsl resolve in your import map.
Picking a file format#
Splats come in several file formats depending on where they were captured or exported, and Three.js ships five loaders to cover them. All five produce the same internal BufferGeometry shape (position, covariance, color, and optional packed sphericalHarmonics1..3 attributes), so whichever loader you use, GaussianSplat consumes the result identically:
LoaderExtensionNotesSPZLoader.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).GaussianSplatPLYLoader.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.
If you get to choose the format, use SPZ version 4 for viewers: it gives you the smallest transfer size and the fastest parse.
Loading PLY splats#
Splats also often arrive as .ply files, since that's the native output of the original 3D Gaussian Splatting research code and of many training and cleanup tools. GaussianSplatPLYLoader handles them, following the same pattern as SPZLoader and SPLATLoader:
import { GaussianSplatPLYLoader } from 'three/addons/loaders/GaussianSplatPLYLoader.js';
import { GaussianSplat } from 'three/addons/objects/GaussianSplat.js';

const splatGeometry = await new GaussianSplatPLYLoader().loadAsync( 'point_cloud.ply' );
scene.add( new GaussianSplat( splatGeometry ) );

This is the right loader to reach for when a splat only exists as a raw .ply export.
Loading glTF splats#
If your splat is embedded in a glTF file, there's one extra setup step. Because GaussianSplat needs WebGPURenderer, GLTFLoader doesn't register the glTF splat plugin for you automatically, so 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 GaussianSplat instances

With that registered, a mesh primitive using KHR_gaussian_splatting loads as a GaussianSplat (or a Group of them, for multi-primitive meshes) and lands in the returned scene graph like any other glTF node, mixed in alongside regular meshes, cameras, and animations if the file has them.
Loading SPLAT and KSPLAT files (legacy formats)#
SPLATLoader and KSPLATLoader exist mainly for legacy compatibility, covering assets and pipelines built around antimatter15/splat and the GaussianSplats3D viewer.
The API matches SPZLoader closely, so swapping between them is just a matter of picking the right loader class and pointing it at the matching extension:
import { SPLATLoader } from 'three/addons/loaders/SPLATLoader.js';
import { GaussianSplat } from 'three/addons/objects/GaussianSplat.js';

const splatGeometry = await new SPLATLoader().loadAsync( 'model.splat' );
scene.add( new GaussianSplat( splatGeometry ) );

import { KSPLATLoader } from 'three/addons/loaders/KSPLATLoader.js';
import { GaussianSplat } from 'three/addons/objects/GaussianSplat.js';

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

Both loaders produce the same BufferGeometry shape as SPZLoader, so GaussianSplat and everything downstream of it (sorting, rendering, glTF export) behaves identically regardless of which loader you used to get there.
Capture, clean up, convert, render#
Getting from a real-world subject to a splat in your Three.js scene takes four steps.
1. Capture#
Start by walking around your subject with a mobile scanning app. Overlapping photos or video go to the app (or its cloud backend), which reconstructs a splat from them. A few apps cover this well:

Polycam: Gaussian Splat capture in the mobile app, cloud processing.
Scaniverse: Niantic's mobile scanning app, with on-device Gaussian Splat capture and export straight to .spz.
Luma AI: consumer splat-capture app, cloud-processed.

Any of the three will reconstruct a usable splat from your capture.
2. Clean up#
Raw reconstructions tend to come out with stray floater splats, background clutter, and rough edges, so it's worth trimming those before you ship.

Polycam has its own cropping and cleanup tools, handy if you captured with it and want to stay in one app.
SuperSplat (PlayCanvas's free web-based editor) is purpose-built for splat editing: cropping, erasing floaters, re-exporting. It works with splats from any source, so reach for 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 as .ply or .splat), convert it to .spz v4 before loading it in Three.js:

Niantic's online SPZ converter: upload .ply/.splat, download .spz.
If you captured with Scaniverse, you can skip this step entirely, since it exports .spz v4 directly.

4. Render#
With a .spz file in hand, drop it into your project and load it with SPZLoader and GaussianSplat, exactly as in the example earlier in this post.Found this useful?Share it with someone who would too.Share© © 1998 - 2026 Ben Houston.

The integration of native Gaussian Splatting support into Three.js provides a first-class implementation for rendering splats, utilizing built-in mesh types and loaders for major file formats. A Gaussian Splat is fundamentally a point cloud composed of fuzzy, oriented, colored three-dimensional ellipsoids, or splats, rather than traditional hard vertices. This method renders thousands to millions of these splats, sorted front-to-back, to produce photorealistic images without requiring the traditional workflow of meshing, UV unwrapping, or material baking. This approach is highly effective for capturing real-world objects and scenes, particularly those with complex surfaces like foliage, fur, reflective or translucent materials, or cluttered interiors, as the result maintains high fidelity directly from photographic input.

A key limitation of Gaussian Splats is their intended scope; they are designed for single captured objects or room-scale scenes and lack inherent level-of-detail streaming, spatial segmentation, or culling mechanisms. Consequently, large-scale captures, such as city blocks, necessitate manual tiling or reduction prior to processing to ensure smooth performance.

The practical workflow for integrating Gaussian Splats into a Three.js scene involves loading the data, wrapping it in a mesh, and rendering it. The loading process begins with loading a file, such as a .spz file, using the appropriate loader, which is then wrapped by a GaussianSplat object before being added to the scene. The implementation requires the use of the WebGPURenderer, as the rendering process relies on TSL nodes and compute shaders for depth sorting. The complexity of loading is abstracted through various loaders, as all formats ultimately produce the same internal BufferGeometry shape that the GaussianSplat class consumes identically.

Several loaders are available to handle different file formats. The SPZLoader is recommended, as it uses Niantic's compact format, which is zstd-compressed and streamed section-by-section, resulting in the smallest file sizes and fastest loading times, while also supporting legacy formats such as gzip compressed v1 through v3. The PLYLoader handles .ply files, which frequently serve as the native output from original Gaussian Splatting research code and associated tools. Other loaders include KSPLATLoader for the format used by the GaussianSplats3D viewer, and SPLATLoader, designed for the fixed 32-byte-per-splat format. For embedding splats within a standard glTF asset, the GLTFLoader must be extended to register the KHR_gaussian_splatting extension, allowing splats to be loaded directly alongside other scene elements.

A common input format is the PLY file, which warrants the use of GaussianSplatPLYLoader when dealing with raw point cloud exports. For legacy compatibility, SPLATLoader and KSPLATLoader can be employed interchangeably with SPZLoader, as they function analogously in providing the necessary geometry data to the GaussianSplat object.

The overall process of transforming a real-world capture into a scene-ready splat involves four distinct phases. First is the capture phase, which utilizes mobile applications like Polycam, Scaniverse, or Luma AI to reconstruct a splat from overlapping photos or video. Second is the cleanup phase, where raw reconstructions are refined, often using tools like Polycam’s cropping features or SuperSplat for editing and removing extraneous elements. Third is the conversion phase, where the cleaned data, typically in .ply or .splat format, must be converted to the .spz v4 format, often via an online converter. Finally, the render phase involves loading the resulting .spz file using the appropriate loader and adding the instance of GaussianSplat to the Three.js scene, followed by standard rendering.