# How the SuperSplat Splat Shader Rendering Pipeline Works: GSplat Technical Architecture

> Explore the SuperSplat splat shader rendering pipeline. Discover how GSplat architecture projects Gaussians, evaluates fall-off and color, and writes to dual render targets for advanced 3D rendering.

- Repository: [PlayCanvas/supersplat](https://github.com/playcanvas/supersplat)
- Tags: architecture
- Published: 2026-05-10

---

**The SuperSplat splat shader rendering pipeline renders millions of 3D Gaussian splats through a custom GSplat shader architecture that projects Gaussian centers in vertex processing, evaluates fall-off and color-space operations in fragment processing, and writes to dual render targets for color and selection outline composition.**

The SuperSplat application, part of the playcanvas/supersplat repository, implements a sophisticated **splat shader rendering pipeline** optimized for real-time Gaussian Splat viewing and editing. This pipeline leverages PlayCanvas's extensible material system to inject custom GLSL shader chunks that handle the unique requirements of 3D Gaussian rendering, including per-splat transforms, spherical harmonic lighting, and interactive selection states.

## Vertex Shader Architecture in src/shaders/splat-shader.ts

The vertex stage constructs Gaussian geometry and prepares per-splat data for rasterization. According to the SuperSplat source code, this stage begins by including common GSplat utilities via `#include "gsplatCommonVS"`, which provides access to helper functions like `initSource` and `getCenter`.

### State Retrieval and Culling

The shader samples `uniform sampler2D splatState` to determine per-splat visibility and interaction states:

```glsl
uint vertexState = uint(texelFetch(splatState, splat.uv, 0).r * 255.0 + 0.5) & 7u;

```

This state value drives two critical early-out paths: the **pick-pass logic** (lines 40-60) that skips splats during selection operations, and the **deleted-splats cull** (lines 61-66) which discards removed splats before projection to save GPU cycles.

### Center Projection and Corner Offset

The shader calls `initCenter` (defined in **gsplatCenterVS**) to transform the Gaussian center into clip space, applying palette-based transformations via `applyPaletteTransform`. It then invokes `initCorner` to calculate the vertex offset within the 2D Gaussian ellipse:

```glsl
gl_Position = center.proj + vec4(corner.offset, 0.0);

```

The varying `texCoord_flags` packs both the Gaussian UV coordinates and selection/locked flags for interpolation to the fragment shader:

```glsl
texCoord_flags = vec4(corner.uv, ...);

```

## Fragment Shader Processing and Dual Render Targets

The fragment stage in [`src/shaders/splat-shader.ts`](https://github.com/playcanvas/supersplat/blob/main/src/shaders/splat-shader.ts) evaluates the Gaussian contribution and handles color composition. As implemented in playcanvas/supersplat, this stage distinguishes between rendering modes using preprocessor flags and writes to two simultaneous render targets.

### Gaussian Fall-off Evaluation

The shader computes the squared distance from the Gaussian center to determine fragment contribution:

```glsl
mediump float A = dot(texCoord_flags.xy, texCoord_flags.xy);
if (A > 1.0) discard;

```

Fragments outside the unit circle (the Gaussian's extent in UV space) are discarded, creating the characteristic elliptical splat shape with smooth edges.

### Color-Space Operations and Selection Handling

For the forward pass, the shader applies a comprehensive color pipeline: base color retrieval, optional spherical harmonic (SH) lighting evaluation, tint/temperature adjustments, brightness and saturation modifications, alpha clamping, and tonemapping. The fragment shader also handles **ring mode** (lines 92-100), which enforces minimum alpha for visible ring outlines around each splat when `ringSize > 0` and the splat is not locked.

### Dual Render Target Output

The shader writes to `pcFragColor0` (color buffer) and `pcFragColor1` (outline buffer):

```glsl
pcFragColor0 = vec4(color.xyz * alpha, alpha);
pcFragColor1 = vec4(0,0,0, selected ? norm : 0.0);

```

This dual-output architecture enables post-process outline extraction for selected splats without requiring an additional geometry pass, as detailed in the fragment shader implementation (lines 102-114).

## Shader Injection and Material Configuration

The custom GSplat shaders are injected into PlayCanvas materials at runtime through the shader chunk system. In [`src/splat.ts`](https://github.com/playcanvas/supersplat/blob/main/src/splat.ts), the `rebuildMaterial` method configures the material:

```typescript
material.shaderChunks.glsl.set('gsplatVS', vertexShader);
material.shaderChunks.glsl.set('gsplatPS', fragmentShader);
material.shaderChunks.glsl.set('gsplatCenterVS', gsplatCenter);

```

This injection occurs when splats are added to the scene via `Splat.add()`, which also configures the number of spherical harmonic bands and registers state texture updates.

## Render Orchestration and Off-Screen Rendering

The **splat shader rendering pipeline** coordinates execution through [`src/render.ts`](https://github.com/playcanvas/supersplat/blob/main/src/render.ts), which manages off-screen rendering for image/video export and viewport display.

### Off-Screen Buffer Management

The `render.image` and `render.video` functions initiate off-screen passes using `scene.camera.startOffscreenMode(width, height)`, directing splat rendering into dedicated color buffers free from UI overlays.

### Per-Frame Sorting and Uniform Updates

Before each frame, the system calls `instance.sort(scene.camera.mainCamera)` to ensure correct depth ordering for transparent splats. The `Splat.onPreRender` callback (lines 30-65) updates shader uniforms including `selectedClr`, `lockedClr`, `clrOffset`, `clrScale`, `saturation`, and `transformPalette` prior to the draw call.

### Buffer Read-back and Export

After GPU completion, the pipeline reads the color buffer (`workTarget.colorBuffer.read`) and routes data through `PngCompressor` for image export or `VideoEncoder` for video streaming, coordinating the full capture workflow from GPU memory to file output.

## Practical Implementation Examples

### Creating and Configuring a Splat

```typescript
import { Asset, Quat } from 'playcanvas';
import { Splat } from './splat';

// Load a .gsplat asset and initialize the rendering pipeline
const splat = new Splat(gsplatAsset, Quat.IDENTITY);
await splat.add();  // Attaches to scene, injects shaders, configures textures

```

This implementation from [`src/splat.ts`](https://github.com/playcanvas/supersplat/blob/main/src/splat.ts) (lines 76-104) triggers the complete shader setup and material injection sequence.

### Triggering Image Export

```typescript
await app.scene.events.invoke('render.image', {
    width: 1920,
    height: 1080,
    transparentBg: false,
    showDebug: false
});

```

This invokes the off-screen rendering pipeline defined in [`src/render.ts`](https://github.com/playcanvas/supersplat/blob/main/src/render.ts) (lines 19-84), handling buffer allocation, scene rendering, and PNG compression.

### Configuring Picking for Selection

```typescript
material.setDefine('PICK_PASS', '');
material.setParameter('pickOp', 2);
material.setParameter('pickMode', 0);

```

When `PICK_PASS` is defined, the vertex shader emits encoded splat IDs (lines 95-105 in [`src/shaders/splat-shader.ts`](https://github.com/playcanvas/supersplat/blob/main/src/shaders/splat-shader.ts)) rather than standard color data, enabling GPU-accelerated splat selection.

## Summary

- The **splat shader rendering pipeline** in SuperSplat uses custom GLSL shader chunks injected into PlayCanvas materials via [`src/splat.ts`](https://github.com/playcanvas/supersplat/blob/main/src/splat.ts) to extend the engine's native rendering capabilities.
- **Vertex processing** in [`src/shaders/splat-shader.ts`](https://github.com/playcanvas/supersplat/blob/main/src/shaders/splat-shader.ts) handles Gaussian projection, state-based culling through bit-masking, and palette transforms, outputting clip-space positions and interpolated state flags.
- **Fragment processing** evaluates Gaussian fall-off using UV-space distance, applies color-space operations including SH lighting and tonemapping, and writes to dual render targets for color and outline extraction.
- **Render orchestration** in [`src/render.ts`](https://github.com/playcanvas/supersplat/blob/main/src/render.ts) manages off-screen buffers for export, per-frame splat sorting for transparency correctness, and CPU-GPU synchronization for image/video capture.
- The pipeline supports specialized modes including pick-pass for selection, ring mode for visual debugging, and dual-output composition for post-process selection outlines.

## Frequently Asked Questions

### How does the splat shader rendering pipeline handle millions of splats efficiently?

The pipeline leverages GPU instancing through PlayCanvas's GSplat component, with per-frame depth sorting performed on the CPU via `instance.sort()` to ensure correct transparency ordering. Early culling in the vertex shader discards deleted splats before rasterization, and the `splatState` texture-based system minimizes uniform updates, minimizing fragment processing overhead for invisible elements.

### What is the purpose of the dual render target output in the fragment shader?

According to [`src/shaders/splat-shader.ts`](https://github.com/playcanvas/supersplat/blob/main/src/shaders/splat-shader.ts), the shader writes to `pcFragColor0` (the rendered scene) and `pcFragColor1` (selection outlines). This allows the outline post-process effect to extract selection boundaries using [`src/shaders/outline-shader.ts`](https://github.com/playcanvas/supersplat/blob/main/src/shaders/outline-shader.ts) without rendering the splats twice or using stencil buffers, optimizing the selection visualization pass.

### How is picking implemented in the splat shader rendering pipeline?

When the `PICK_PASS` preprocessor flag is defined and uniform `pickOp` is set to `2` with `pickMode` set to `0`, the vertex shader encodes the splat ID into the output color (lines 95-105 in [`src/shaders/splat-shader.ts`](https://github.com/playcanvas/supersplat/blob/main/src/shaders/splat-shader.ts)). This enables GPU-accelerated selection of individual splats by reading the pixel color at the mouse position and decoding the ID.

### Where does the Gaussian fall-off calculation occur in the shader code?

The fragment shader in [`src/shaders/splat-shader.ts`](https://github.com/playcanvas/supersplat/blob/main/src/shaders/splat-shader.ts) computes the squared UV distance using `dot(texCoord_flags.xy, texCoord_flags.xy)` and uses the `discard` keyword to clip fragments outside the unit circle. The remaining fragments apply an exponential fall-off function (`normExp`) to achieve the smooth Gaussian intensity distribution characteristic of 3D Gaussian Splatting.