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.
This page covers shader customization for non-unified rendering (when entity.gsplat.unified = false). Each component has its own material that can be customized independently.
For unified rendering, see Work Buffer Rendering which provides similar customization capabilities through a global render modifier.
Introduction
Override the gsplatModifyVS shader 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.
View Live Example - See shader chunk customization in action with animated splats.
API Reference
The gsplatModifyVS shader chunk allows you to override three functions that customize how splats are rendered:
modifySplatCenter
Transform the position of splat centers in model space.
GLSL:
void modifySplatCenter(inout vec3 center)
WGSL:
fn modifySplatCenter(center: ptr<function, vec3f>)
Parameters:
center- The splat center position in model space
Example:
// Offset all splats up by 1 unit
void modifySplatCenter(inout vec3 center) {
center.y += 1.0;
}
modifySplatRotationScale
Modify the splat size and shape by adjusting the rotation quaternion and scale vector.
GLSL:
void modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale)
WGSL:
fn modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>)
Parameters:
originalCenter- The original splat center position before modificationmodifiedCenter- The splat center position aftermodifySplatCenter()was appliedrotation- Quaternion (x, y, z, w) representing the splat's rotationscale- Scale vector representing the splat's size in each axis
Example:
// Scale all splats by 2x
void modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {
scale *= 2.0;
}
modifySplatColor
Transform splat colors and opacity.
GLSL:
void modifySplatColor(vec3 center, inout vec4 color)
WGSL:
fn modifySplatColor(center: vec3f, color: ptr<function, vec4f>)
Parameters:
center- The splat center position (aftermodifySplatCenter()was applied)color- The splat color (RGBA)
Example:
// Darken all splats by 50%
void modifySplatColor(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('gsplatModifyVS', customShader);
// Update the material to recompile with the new shader
gsplatMaterial.update();
GLSL Example: Position and Color Animation
const customShader = `
uniform float uTime;
void modifySplatCenter(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 modifySplatRotationScale(vec3 originalCenter, vec3 modifiedCenter, inout vec4 rotation, inout vec3 scale) {
// No modification to size
}
void modifySplatColor(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('gsplatModifyVS', 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 modifySplatCenter(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 modifySplatRotationScale(originalCenter: vec3f, modifiedCenter: vec3f, rotation: ptr<function, vec4f>, scale: ptr<function, vec3f>) {
// No modification to size
}
fn modifySplatColor(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('gsplatModifyVS', 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('gsplatModifyVS');
gsplatMaterial.update();
Helper Functions
The following helper functions are available in modifySplatRotationScale() for manipulating splat size and shape:
gsplatGetSizeFromScale
Extract the current size of a splat from its scale vector.
GLSL:
float gsplatGetSizeFromScale(vec3 scale)
WGSL:
fn gsplatGetSizeFromScale(scale: vec3f) -> f32
Example:
// Clamp splat size to a specific range
float size = gsplatGetSizeFromScale(scale);
float newSize = clamp(size, 0.01, 0.5);
scale *= newSize / size;
gsplatMakeSpherical
Make splats spherical with a specific radius.
GLSL:
void gsplatMakeSpherical(inout vec3 scale, float radius)
WGSL:
fn gsplatMakeSpherical(scale: ptr<function, vec3f>, radius: f32)
Example:
// Make all splats perfectly spherical with uniform size
float size = gsplatGetSizeFromScale(scale);
gsplatMakeSpherical(scale, size * 0.5);
// Or hide a splat by setting scale to zero
scale = vec3(0.0);
Direct Scale Manipulation
Since the new API provides direct access to the scale vector, you can easily modify splat sizes:
// Double the size of all splats
scale *= 2.0;
// Scale non-uniformly
scale.x *= 2.0; // Stretch horizontally
// Hide a splat
scale = vec3(0.0);
Examples
Here are some examples demonstrating custom shader techniques:
Animation Effects
Simple Sinusoidal Animation - 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.