{"slug": "how-to-use-three-js-s-new-native-gaussian-splats", "title": "How to Use Three.JS's new Native Gaussian Splats", "summary": "Three.js r186 adds native 3D Gaussian Splatting support with a built-in GaussianSplat mesh type and loaders for .spz, .ksplat, .splat, and glTF formats, enabling photorealistic rendering of captured objects and scenes directly in the engine. The feature requires WebGPURenderer and is designed for single objects or room-scale scenes, not city-scale captures, according to Ben Houston's tutorial.", "body_md": "# How to Use Three.JS's new Native Gaussian Splats\n\nHow 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.\n\n[Ben Houston](/about) • • 8 min read\n\nThe upcoming Three.js r186 release adds native [3D Gaussian Splatting](https://en.wikipedia.org/wiki/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.\n\nI covered the underlying technical details in an earlier post, [Adding Native Gaussian Splatting Support to Three.js](/blog/gaussian-splatting-for-threejs). 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.\n\n## Gaussian Splats[#](#gaussian-splats)\n\nA 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.\n\nThat 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.\n\nThere is a scale limit worth knowing up front, though: `GaussianSplat`\n\nis 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](/blog/gaussian-splatting-for-threejs).\n\n## Loading an SPZ file[#](#loading-an-spz-file)\n\nHere's the whole pipeline, start to finish: load a `.spz`\n\nfile, wrap it in a mesh, and render it.\n\n``` js\nimport * as THREE from 'three/webgpu';\nimport { SPZLoader } from 'three/addons/loaders/SPZLoader.js';\nimport { GaussianSplat } from 'three/addons/objects/GaussianSplat.js';\n\nconst renderer = new THREE.WebGPURenderer();\nawait renderer.init();\n\nconst scene = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 100 );\ncamera.position.set( 0, 0.3, 2 );\n\n// 1. Load the splat data\nconst splatGeometry = await new SPZLoader().loadAsync( 'model.spz' );\n\n// 2. Wrap it in a mesh and add it to the scene\nconst splats = new GaussianSplat( splatGeometry );\nscene.add( splats );\n\n// 3. Render as usual. The mesh sorts itself every frame by default.\nrenderer.setAnimationLoop( () => {\n\n\trenderer.render( scene, camera );\n\n} );\n```\n\nThat's really all there is to it: one loader call, one `new GaussianSplat( geometry )`\n\n, and a `scene.add()`\n\n. Because `GaussianSplat`\n\nextends `THREE.Mesh`\n\n, it composes with the rest of the scene graph just like any other object, so transforms, `visible`\n\n, and raycasting groups all work the way you'd expect.\n\nOne requirement to keep in mind: `GaussianSplat`\n\nneeds `WebGPURenderer`\n\n. The renderer is built from TSL nodes plus compute shaders for the depth sort, so make sure both `three/webgpu`\n\nand `three/tsl`\n\nresolve in your import map.\n\n## Picking a file format[#](#picking-a-file-format)\n\nSplats 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`\n\nshape (`position`\n\n, `covariance`\n\n, `color`\n\n, and optional packed `sphericalHarmonics1..3`\n\nattributes), so whichever loader you use, `GaussianSplat`\n\nconsumes the result identically:\n\n| Loader | Extension | Notes |\n|---|---|---|\n`SPZLoader` | `.spz` | Recommended. 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). |\n`GaussianSplatPLYLoader` | `.ply` | Most 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` . |\n`KSPLATLoader` | `.ksplat` | Format used by the `GaussianSplats3D` viewer. Useful if you already have assets from that pipeline. |\n`SPLATLoader` | `.splat` | Original fixed 32-byte-per-splat format (antimatter15/splat). Uncompressed, easy to generate, large on disk. |\n`GLTFGaussianSplatLoaderExtension` | `.gltf` / `.glb` | Implements the\n`KHR_gaussian_splatting` |\n\nIf you get to choose the format, use SPZ version 4 for viewers: it gives you the smallest transfer size and the fastest parse.\n\n### Loading PLY splats[#](#loading-ply-splats)\n\nSplats also often arrive as `.ply`\n\nfiles, since that's the native output of the original 3D Gaussian Splatting research code and of many training and cleanup tools. `GaussianSplatPLYLoader`\n\nhandles them, following the same pattern as `SPZLoader`\n\nand `SPLATLoader`\n\n:\n\n``` js\nimport { GaussianSplatPLYLoader } from 'three/addons/loaders/GaussianSplatPLYLoader.js';\nimport { GaussianSplat } from 'three/addons/objects/GaussianSplat.js';\n\nconst splatGeometry = await new GaussianSplatPLYLoader().loadAsync( 'point_cloud.ply' );\nscene.add( new GaussianSplat( splatGeometry ) );\n```\n\nThis is the right loader to reach for when a splat only exists as a raw `.ply`\n\nexport.\n\n### Loading glTF splats[#](#loading-gltf-splats)\n\nIf your splat is embedded in a glTF file, there's one extra setup step. Because `GaussianSplat`\n\nneeds `WebGPURenderer`\n\n, `GLTFLoader`\n\ndoesn't register the glTF splat plugin for you automatically, so you register it yourself:\n\n``` js\nimport { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\nimport { GLTFGaussianSplatLoaderExtension } from 'three/addons/loaders/GLTFGaussianSplatLoaderExtension.js';\n\nconst loader = new GLTFLoader();\nloader.register( ( parser ) => new GLTFGaussianSplatLoaderExtension( parser ) );\n\nconst gltf = await loader.loadAsync( 'scene.gltf' );\nscene.add( gltf.scene ); // splat primitives arrive as GaussianSplat instances\n```\n\nWith that registered, a mesh primitive using `KHR_gaussian_splatting`\n\nloads as a `GaussianSplat`\n\n(or a `Group`\n\nof 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.\n\n### Loading SPLAT and KSPLAT files (legacy formats)[#](#loading-splat-and-ksplat-files-legacy-formats)\n\n`SPLATLoader`\n\nand `KSPLATLoader`\n\nexist mainly for legacy compatibility, covering assets and pipelines built around `antimatter15/splat`\n\nand the `GaussianSplats3D`\n\nviewer.\n\nThe API matches `SPZLoader`\n\nclosely, so swapping between them is just a matter of picking the right loader class and pointing it at the matching extension:\n\n``` js\nimport { SPLATLoader } from 'three/addons/loaders/SPLATLoader.js';\nimport { GaussianSplat } from 'three/addons/objects/GaussianSplat.js';\n\nconst splatGeometry = await new SPLATLoader().loadAsync( 'model.splat' );\nscene.add( new GaussianSplat( splatGeometry ) );\njs\nimport { KSPLATLoader } from 'three/addons/loaders/KSPLATLoader.js';\nimport { GaussianSplat } from 'three/addons/objects/GaussianSplat.js';\n\nconst splatGeometry = await new KSPLATLoader().loadAsync( 'model.ksplat' );\nscene.add( new GaussianSplat( splatGeometry ) );\n```\n\nBoth loaders produce the same `BufferGeometry`\n\nshape as `SPZLoader`\n\n, so `GaussianSplat`\n\nand everything downstream of it (sorting, rendering, glTF export) behaves identically regardless of which loader you used to get there.\n\n## Capture, clean up, convert, render[#](#capture-clean-up-convert-render)\n\nGetting from a real-world subject to a splat in your Three.js scene takes four steps.\n\n### 1. Capture[#](#1-capture)\n\nStart 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:\n\nGaussian Splat capture in the mobile app, cloud processing.[Polycam](https://poly.cam/):Niantic's mobile scanning app, with on-device Gaussian Splat capture and export straight to[Scaniverse](https://scaniverse.com/):`.spz`\n\n.consumer splat-capture app, cloud-processed.[Luma AI](https://lumalabs.ai/):\n\nAny of the three will reconstruct a usable splat from your capture.\n\n### 2. Clean up[#](#2-clean-up)\n\nRaw reconstructions tend to come out with stray floater splats, background clutter, and rough edges, so it's worth trimming those before you ship.\n\nhas its own cropping and cleanup tools, handy if you captured with it and want to stay in one app.[Polycam](https://poly.cam/)(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.[SuperSplat](https://superspl.at/)\n\n### 3. Convert to SPZ[#](#3-convert-to-spz)\n\nOnce you have a clean splat (usually as `.ply`\n\nor `.splat`\n\n), convert it to `.spz`\n\nv4 before loading it in Three.js:\n\nupload[Niantic's online SPZ converter](https://scaniverse.com/spz):`.ply`\n\n/`.splat`\n\n, download`.spz`\n\n.- If you captured with\n**Scaniverse**, you can skip this step entirely, since it exports`.spz`\n\nv4 directly.\n\n### 4. Render[#](#4-render)\n\nWith a `.spz`\n\nfile in hand, drop it into your project and load it with `SPZLoader`\n\nand `GaussianSplat`\n\n, exactly as in the example earlier in this post.", "url": "https://wpnews.pro/news/how-to-use-three-js-s-new-native-gaussian-splats", "canonical_source": "https://ben3d.ca/blog/how-to-use-threejs-native-gaussian-splats", "published_at": "2026-08-17 00:00:00+00:00", "updated_at": "2026-08-28 21:48:44.372300+00:00", "lang": "en", "topics": ["artificial-intelligence", "computer-vision", "ai-tools"], "entities": ["Three.js", "Ben Houston", "SPZLoader", "GaussianSplat", "WebGPURenderer", "Polycam", "Luma AI", "Scaniverse"], "alternates": {"html": "https://wpnews.pro/news/how-to-use-three-js-s-new-native-gaussian-splats", "markdown": "https://wpnews.pro/news/how-to-use-three-js-s-new-native-gaussian-splats.md", "text": "https://wpnews.pro/news/how-to-use-three-js-s-new-native-gaussian-splats.txt", "jsonld": "https://wpnews.pro/news/how-to-use-three-js-s-new-native-gaussian-splats.jsonld"}}