Custom Shaders
The PlayCanvas Engine supports custom shaders for Gaussian Splats, allowing you to create advanced visual effects and customize the rendering behavior beyond the standard implementation.
Introduction
There are two ways to customize Gaussian Splat rendering with shaders:
-
Shader Chunk Customization (Recommended) - Override the
gsplatCustomizeVSshader chunk to customize splat position, size, and color. This allows you to override only the relevant parts of the shader while leaving the core shader functionality intact. -
Full Shader Replacement - Replace the entire vertex and fragment shaders for complete control. This provides maximum flexibility but requires understanding the full shader implementation.
Most use cases can be accomplished with shader chunk customization, which is covered in detail below.
View Live Example - See shader chunk customization in action with animated splats.
API Reference
The gsplatCustomizeVS shader chunk allows you to override three functions that customize how splats are rendered:
modifyCenter
Transform the position of splat centers in model space.
GLSL:
void modifyCenter(inout vec3 center)
WGSL:
fn modifyCenter(center: ptr<function, vec3f>)
Parameters:
center- The splat center position in model space
Example:
// Offset all splats up by 1 unit
void modifyCenter(inout vec3 center) {
center.y += 1.0;
}
modifyCovariance
Modify the splat size and shape by adjusting covariance values.
GLSL:
void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB)
WGSL:
fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr<function, vec3f>, covB: ptr<function, vec3f>)
Parameters:
originalCenter- The original splat center position before modificationmodifiedCenter- The splat center position aftermodifyCenter()was appliedcovA,covB- Covariance values that define splat size and orientation
Example:
// Scale all splats by 2x
void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) {
gsplatApplyUniformScale(covA, covB, 2.0);
}
modifyColor
Transform splat colors and opacity.
GLSL:
void modifyColor(vec3 center, inout vec4 color)
WGSL:
fn modifyColor(center: vec3f, color: ptr<function, vec4f>)
Parameters:
center- The splat center position (aftermodifyCenter()was applied)color- The splat color (RGBA)
Example:
// Darken all splats by 50%
void modifyColor(vec3 center, inout vec4 color) {
color.rgb *= 0.5;
}
Usage Examples
Basic Setup
To apply a custom shader chunk to a Gaussian Splat material:
// Get the shader language for the current device
const shaderLanguage = device.isWebGPU ? 'wgsl' : 'glsl';
// Define your custom shader code
const customShader = `
// Your shader functions here
`;
// Set the custom shader chunk override on the gsplat material
gsplatMaterial.getShaderChunks(shaderLanguage).set('gsplatCustomizeVS', customShader);
// Update the material to recompile with the new shader
gsplatMaterial.update();
GLSL Example: Position and Color Animation
const customShader = `
uniform float uTime;
void modifyCenter(inout vec3 center) {
// Create a wave effect based on height
float heightIntensity = center.y * 0.2;
center.x += sin(uTime * 5.0 + center.y) * 0.3 * heightIntensity;
}
void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) {
// No modification to size
}
void modifyColor(vec3 center, inout vec4 color) {
// Add a golden tint to the wave peaks
float sineValue = abs(sin(uTime * 5.0 + center.y));
vec3 gold = vec3(1.0, 0.85, 0.0);
float blend = smoothstep(0.9, 1.0, sineValue);
color.rgb = mix(color.rgb, gold, blend);
}
`;
// Set the custom shader chunk override on the gsplat material
const shaderLanguage = app.graphicsDevice.isWebGPU ? 'wgsl' : 'glsl';
gsplatMaterial.getShaderChunks(shaderLanguage).set('gsplatCustomizeVS', customShader);
gsplatMaterial.update();
// Update the uniform each frame
const uTime = app.graphicsDevice.scope.resolve('uTime');
let time = 0;
app.on('update', (dt) => {
time += dt;
uTime.setValue(time);
});
WGSL Example: Position and Color Animation
const customShader = `
uniform uTime: f32;
fn modifyCenter(center: ptr<function, vec3f>) {
// Create a wave effect based on height
let heightIntensity = (*center).y * 0.2;
(*center).x += sin(uniform.uTime * 5.0 + (*center).y) * 0.3 * heightIntensity;
}
fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr<function, vec3f>, covB: ptr<function, vec3f>) {
// No modification to size
}
fn modifyColor(center: vec3f, color: ptr<function, vec4f>) {
// Add a golden tint to the wave peaks
let sineValue = abs(sin(uniform.uTime * 5.0 + center.y));
let gold = vec3f(1.0, 0.85, 0.0);
let blend = smoothstep(0.9, 1.0, sineValue);
(*color) = vec4f(mix((*color).rgb, gold, blend), (*color).a);
}
`;
// Set the custom shader chunk override on the gsplat material
const shaderLanguage = app.graphicsDevice.isWebGPU ? 'wgsl' : 'glsl';
gsplatMaterial.getShaderChunks(shaderLanguage).set('gsplatCustomizeVS', customShader);
gsplatMaterial.update();
Removing Custom Shaders
To remove a custom shader and revert to default rendering:
// Remove the custom shader chunk override from the gsplat material
const shaderLanguage = app.graphicsDevice.isWebGPU ? 'wgsl' : 'glsl';
gsplatMaterial.getShaderChunks(shaderLanguage).delete('gsplatCustomizeVS');
gsplatMaterial.update();
Helper Functions
The following helper functions are available in modifyCovariance() for manipulating splat size and shape:
gsplatApplyUniformScale
Scale splats uniformly by a factor.
GLSL:
void gsplatApplyUniformScale(inout vec3 covA, inout vec3 covB, float scale)
WGSL:
fn gsplatApplyUniformScale(covA: ptr<function, vec3f>, covB: ptr<function, vec3f>, scale: f32)
Example:
// Double the size of all splats
gsplatApplyUniformScale(covA, covB, 2.0);
gsplatExtractSize
Extract the current size of a splat.
GLSL:
float gsplatExtractSize(vec3 covA, vec3 covB)
WGSL:
fn gsplatExtractSize(covA: vec3f, covB: vec3f) -> f32
Example:
// Clamp splat size to a specific range
float size = gsplatExtractSize(covA, covB);
float newSize = clamp(size, 0.01, 0.5);
gsplatApplyUniformScale(covA, covB, newSize / size);
gsplatMakeRound
Make splats round/spherical with a specific radius.
GLSL:
void gsplatMakeRound(inout vec3 covA, inout vec3 covB, float radius)
WGSL:
fn gsplatMakeRound(covA: ptr<function, vec3f>, covB: ptr<function, vec3f>, radius: f32)
Example:
// Make all splats perfectly round with uniform size
float size = gsplatExtractSize(covA, covB);
gsplatMakeRound(covA, covB, size * 0.5);
// Or hide a splat by setting radius to 0
gsplatMakeRound(covA, covB, 0.0);
Examples
Here are some examples demonstrating different custom shader techniques:
Animation Effects
Simple Sinusoidal Animation - Uses Shader Chunk Customization - Applies a simple shader to animate Gaussian color and position using a sine wave. This example demonstrates how to create dynamic, procedural motion effects by modifying splat properties in real-time.
Transition Effects
3D Gaussian Splat Statues - Uses Full Shader Replacement - Uses custom shaders to transition splats on and off screen via a hot, plasma-type effect. This showcases how custom shaders can create dramatic visual transitions and material effects.
Lighting and Relighting
3DGS with Physics and Relighting - Uses Full Shader Replacement - Uses custom shaders to relight a splat to implement a night mode with multiple moving point light sources. This example demonstrates advanced lighting techniques and how to dynamically modify splat appearance based on scene lighting conditions.