# Gaussian splats, explained for people who build things

> What a Gaussian splat is and is not, how it is drawn, the file formats you will meet, what splats are good and bad at, and what that means for a WebXR experience built on one.

Michal Takáč, 2 October 2026 · Guides
Source: https://graspable.dev/blog/gaussian-splats-explained

A Gaussian splat scene looks like a photograph you can walk around in. It is tempting to treat it like any other 3D model. It is not one, and the differences decide what you can build on it.

This guide explains what a splat is, how it gets onto the screen, and what it can and cannot do, with the consequences for a WebXR experience.

## What a splat is

The technique comes from the paper [3D Gaussian Splatting for Real-Time Radiance Field Rendering](https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/) by Kerbl, Kopanas, Leimkühler and Drettakis (SIGGRAPH 2023). A scene is a large set of 3D Gaussians: soft, stretched blobs. The paper gives each one four properties:

- a **position**,
- a **shape**: an anisotropic covariance, stored as a scale for each of three axes and a rotation,
- an **opacity**,
- a **colour** that depends on the direction you look from, stored as spherical harmonic coefficients.

There are many of them. The paper reports 1 to 5 million Gaussians for every scene it tested.

Nobody models these blobs by hand. In the paper they are optimised from photographs: the cameras are calibrated with structure from motion, the sparse points that step produces become the first Gaussians, and an optimisation then moves, reshapes, splits and removes Gaussians until the rendered images match the photographs. Generative models can now produce the same representation from a text prompt or a single image; that is how the worlds in Graspable are made.

## What a splat is not

**Not a mesh.** A mesh is a surface made of triangles, with materials that respond to light. A splat scene has no surface at all. It is a cloud of semi-transparent blobs that only looks solid when enough of them overlap. The paper's own closing remarks say it would be interesting to see whether the Gaussians can be used for mesh reconstruction. They are not one already.

**Not photogrammetry.** Classic photogrammetry also starts from photographs and structure from motion, then uses multi-view stereo to reconstruct geometry, which is usually delivered as a textured mesh. A splat skips the surface and keeps appearance instead. That is why thin and fuzzy things, such as plants, look better as splats than as scanned meshes.

**Not a NeRF.** A neural radiance field stores the scene in a neural network. To draw a pixel, [NeRF](https://arxiv.org/abs/2003.08934) queries the network at many points along the camera ray and combines the answers with volume rendering. Gaussian splatting keeps the same idea of a radiance field, light leaving each point in each direction, but stores it as explicit blobs that a GPU can draw directly. The paper presents this as the first real-time rendering solution for radiance fields.

## How splats are drawn

"Splatting" means each Gaussian is projected onto the screen, where it becomes a soft 2D ellipse. The ellipses are then combined with alpha blending. Blending only gives the right picture when it is done in depth order, so the splats have to be **sorted** for the current viewpoint.

The paper does this with a tile-based rasteriser: it splits the screen into tiles of 16 by 16 pixels, sorts all Gaussians once on the GPU with a radix sort, and blends them front to back in each tile until the pixel is opaque.

In a browser the same job is done with the tools WebGL has. [Spark](https://sparkjs.dev), the renderer we use, draws each splat as two triangles that cover the Gaussian out to a chosen number of standard deviations, as transparent geometry blended back to front. Its [system design page](https://sparkjs.dev/docs/system-design/) describes how the order is found: distances are read back from the GPU, sorted in a background worker, and used on a following frame. All splats in the scene are then drawn with a single instanced draw call.

Two costs follow from this, and they are not the costs of a mesh scene:

- **Sorting.** It has to be redone as the viewpoint moves, for every splat.
- **Blending.** Every splat is transparent, so the GPU draws many layers over the same pixel. The cost grows with the number of pixels, not only with the number of splats.

Loading a splat into a three.js scene with Spark takes a few lines:

```js
import * as THREE from 'three'
import { SparkRenderer, SplatMesh } from '@sparkjsdev/spark'

const scene = new THREE.Scene()
const renderer = new THREE.WebGLRenderer({ antialias: false })

// One SparkRenderer draws every splat in the scene.
scene.add(new SparkRenderer({ renderer }))

const room = new SplatMesh({ url: '/worlds/loft/world-500k.spz' })
room.quaternion.set(1, 0, 0, 0) // a half turn around x: from y-down to y-up
scene.add(room)
```

## File formats

- **.ply** is what the paper's reference implementation writes. It is uncompressed and understood by the most tools. World Labs' [export specifications](https://docs.worldlabs.ai/marble/export/specs) describe it the same way: the format with broader software compatibility.
- **.spz** is an open format for compressed splats from Niantic. Its [repository](https://github.com/nianticlabs/spz) says files are typically around 10 times smaller than the corresponding .ply, with minimal visual difference. This is what you want to send over a network. The loft in the picture above is 7.5 MB for 500,000 splats.
- **.splat** comes from the early WebGL viewer [antimatter15/splat](https://github.com/antimatter15/splat). It leaves out the spherical harmonics to keep files small, so colour no longer changes with the viewing direction.

Spark loads all three, and a few more. Its [loading page](https://sparkjs.dev/docs/loading-splats/) lists them.

One trap: files differ in which way is up. World Labs' files use a y-down convention, and three.js is y-up, so the code above turns the splat half a turn around x.

## What splats are good at

- **Looking real.** Soft light, reflections that shift as you move, foliage, clutter. Things that take a long time to model and light come for free.
- **Whole places.** A room or a courtyard arrives complete, not object by object.

## What splats are bad at

- **Nothing is solid.** There are no surfaces to collide with, stand on or cast a ray against. You need a separate, invisible mesh of the same place for physics.
- **The light is baked in.** A splat stores the colour that leaves each point, not a material. Adding a light to the scene does nothing to it. You cannot move the sun.
- **No shadows either way.** A splat does not cast a shadow on your meshes and does not receive one from them.
- **Colour depends on where you stand.** That is a feature, it is what makes highlights look right, but it means a splat has no single "texture" you could bake or edit.
- **Quality depends on what was seen.** The paper's limitations section says artefacts appear in regions that were not well observed. For a generated world the same holds for regions far from the point it was generated from: they blur.

![A loft interior drawn from Gaussian splats; a blue jar, a white bottle and a terracotta bowl made of ordinary meshes stand on the table](https://graspable.dev/blog/gaussian-splats-explained/loft-splat-with-meshes.webp)

*A splat and meshes in one scene, on a flat screen. The room is 500,000 splats. The three ceramic pieces and the labels are ordinary meshes.*

## What this means for a WebXR experience

**Scale has to be right.** In a headset a room at 80% size feels wrong at once. A splat file has no fixed unit, so you need to know how many metres one unit is, and where the floor is. World Labs returns both numbers with every world; see [their note on rendering SPZ files](https://docs.worldlabs.ai/api/rendering-spz).

**You need a collider.** Teleporting, walking, placing an object on a table and throwing a ball all need surfaces. Use a coarse mesh of the same place, in the same coordinates, and never show it.

**Budget the splats, and the pixels.** A headset draws [one view for each eye](https://developer.mozilla.org/en-US/docs/Web/API/XRView) on a mobile chip. Spark's [performance guide](https://sparkjs.dev/docs/performance/) recommends 1 million splats or fewer for a Quest 3, and says to leave multisampling off because it does not improve splats and costs a lot. Treat any such number as a starting point and measure on the device.

**Light your own objects from the splat.** Meshes you add will look pasted in unless they pick up the colours of the place. Rendering the splat into an environment map and using it to light the meshes works well.

**Fake the contact shadows.** A soft dark blob under an added object does what a real shadow cannot.

**Keep people near the good part.** Start them where the scene is sharpest and limit how far they can go.

![The same loft seen through an emulated headset: a left-eye and a right-eye image side by side, with two controllers in view](https://graspable.dev/blog/gaussian-splats-explained/loft-emulated-headset.webp)

*The loft in the emulated headset, both eyes. Every splat is drawn once per eye.*

## Doing this in Graspable

Graspable has three templates built on splats: Splat World, World Explorer and World Game. Each starts inside a generated place at real scale, with its collider wired to physics, and the agent can generate a new place when you ask for one. See [Generated worlds](https://graspable.dev/docs/worlds.md) and [Templates](https://graspable.dev/docs/templates.md).

The rest of this series goes into the details:

- [Graspable now generates worlds with World Labs](https://graspable.dev/blog/generate-worlds-with-world-labs.md)
- [Spark: the renderer behind our splat templates](https://graspable.dev/blog/spark-splat-renderer.md)
- [Performance of Gaussian splats in WebXR](https://graspable.dev/blog/gaussian-splat-performance-webxr.md)
- [How our world templates work](https://graspable.dev/blog/how-world-templates-work.md)

## Sources

- [3D Gaussian Splatting for Real-Time Radiance Field Rendering](https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/), Kerbl, Kopanas, Leimkühler and Drettakis, SIGGRAPH 2023; the paper is also on [arXiv](https://arxiv.org/abs/2308.04079)
- [NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis](https://arxiv.org/abs/2003.08934), Mildenhall and others
- [spz](https://github.com/nianticlabs/spz), Niantic, and [splat](https://github.com/antimatter15/splat), antimatter15
- [System design](https://sparkjs.dev/docs/system-design/), [Loading splats](https://sparkjs.dev/docs/loading-splats/) and [Performance tuning](https://sparkjs.dev/docs/performance/), Spark
- [Export file specs](https://docs.worldlabs.ai/marble/export/specs) and [Rendering Marble SPZ files in third-party engines](https://docs.worldlabs.ai/api/rendering-spz), World Labs
- [XRView](https://developer.mozilla.org/en-US/docs/Web/API/XRView), MDN, and the [WebXR Device API](https://www.w3.org/TR/webxr/), W3C
