How the SuperSplat Splat Shader Rendering Pipeline Works: GSplat Technical Architecture
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:
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:
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:
texCoord_flags = vec4(corner.uv, ...);
Fragment Shader Processing and Dual Render Targets
The fragment stage in 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:
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):
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, the rebuildMaterial method configures the material:
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, 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
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 (lines 76-104) triggers the complete shader setup and material injection sequence.
Triggering Image Export
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 (lines 19-84), handling buffer allocation, scene rendering, and PNG compression.
Configuring Picking for Selection
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) 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.tsto extend the engine's native rendering capabilities. - Vertex processing in
src/shaders/splat-shader.tshandles 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.tsmanages 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, 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 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). 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →