Material Plugins
Material Plugins are a powerful system that allows you to inject custom shader code into built-in materials (like StandardMaterial or PBRMaterial) without forking or modifying those materials directly. This provides immense flexibility to enhance or alter rendering behavior while preserving the core implementation.
Core concepts
Material Plugins extend existing Babylon.js materials by subclassing MaterialPluginBase
. This base class provides lifecycle methods that you override to hook into the material pipeline. The main responsibilities of a plugin can be grouped into four areas:
1. Declare new uniforms, attributes, or samplers
Plugins can add their own shader inputs:
- Uniforms, single values (float, vec2, vec3, matrices) or textures that remain constant across a draw call.
- Attributes, per-vertex data passed from the geometry to the shader.
- Samplers, textures the shader can read.
2. Inject GLSL code at predefined shader injection points
Instead of rewriting Babylon’s entire shader, plugins return code snippets for specific injection hooks. These hooks are predefined markers like:
CUSTOM_VERTEX_DEFINITIONS
, where to declare varyings, uniforms, constantsCUSTOM_VERTEX_MAIN_BEGIN
/CUSTOM_VERTEX_MAIN_END
, logic inside the vertex shader mainCUSTOM_FRAGMENT_DEFINITIONS
, declare variables for fragment shaderCUSTOM_FRAGMENT_MAIN_BEGIN
/CUSTOM_FRAGMENT_MAIN_END
, logic inside the fragment shader main
3. Bind values at render time
Declaring uniforms isn’t enough, you also need to update them every frame. This ensures the shader always sees the latest values when rendering.
4. Toggle features via defines
Defines act like compile-time switches (#ifdef ... #endif
in GLSL). They allow you to conditionally enable or disable plugin logic without recompiling everything unnecessarily.
FadeRevealMaterialPlugin
Now that you have an overview of the Material Plugins system, let’s look at a practical example. The plugin below applies a distance-based reveal effect: objects close to a defined origin remain fully visible, while those farther away smoothly fade out, leaving behind a glowing edge at the boundary.
Graph
Here is an overview of the effect, made with the Node Material Editor. The graph representation makes it easier to understand how the different nodes work together to drive the reveal effect, showing at a glance the flow of calculations and how each parameter contributes to the final look.

Methods
Now it’s time to analyze the implementation, method by method.
isEnabled
Provides a convenient property to toggle the plugin on/off. Updates the material’s defines so the shader knows whether to include the custom logic.
get isEnabled() {
return this._isEnabled;
}
set isEnabled(enabled) {
if (this._isEnabled === enabled) return;
this._isEnabled = enabled;
this._enable(enabled);
}
prepareDefines
Updates shader defines before compilation. Defines act like #ifdef
macros in GLSL, allowing conditional inclusion of code.
prepareDefines(defines: Record<string, any>) {
defines["FadeReveal"] = this._isEnabled;
}
getClassName
Returns the runtime name of the plugin.
getClassName() {
return "FadeRevealMaterialPlugin";
}
getUniforms
Declares new uniforms (position, radius, gradient, glow color) needed by the plugin. These uniforms are injected into the fragment shader if the FadeReveal
define is enabled.
getUniforms() {
return {
ubo: [
{ name: "u_origin", size: 3, type: "vec3" },
{ name: "u_radius", size: 1, type: "float" },
{ name: "u_gradient", size: 1, type: "float" },
{ name: "u_glowColor", size: 3, type: "vec3" },
],
fragment: `
#ifdef FadeReveal
uniform vec3 u_origin;
uniform float u_radius;
uniform float u_gradient;
uniform vec3 u_glowColor;
#endif
`
};
}
bindForSubMesh
Runs every frame before rendering a submesh. Updates the uniform buffer with the current plugin values. Ensures the shader has fresh data for origin, radius, gradient, and glow color.
bindForSubMesh(uniformBuffer: UniformBuffer) {
if (!this._isEnabled) return;
uniformBuffer.updateVector3("u_origin", FadeRevealMaterialPlugin.origin);
uniformBuffer.updateFloat("u_radius", FadeRevealMaterialPlugin.radius);
uniformBuffer.updateFloat("u_gradient", FadeRevealMaterialPlugin.gradient);
uniformBuffer.updateColor3("u_glowColor", FadeRevealMaterialPlugin.glowColor);
}
getCustomCode
Injects GLSL code at Babylon’s shader injection points.
Vertex shader: calculates the world-space position of each vertex and passes it to the fragment shader (v_xyz1
).
Fragment shader: computes distance from the origin, fades alpha smoothly based on radius and gradient (smoothstep
), adds a glow effect at the fade boundary by tinting the RGB channels.
getCustomCode(shaderType: string) {
if (shaderType === "vertex") {
return {
CUSTOM_VERTEX_DEFINITIONS: `
varying vec3 v_xyz1;
`,
CUSTOM_VERTEX_MAIN_END: `
vec4 output1 = world * vec4(position, 1.0);
v_xyz1 = output1.xyz;
`,
CUSTOM_FRAGMENT_DEFINITIONS: "",
CUSTOM_FRAGMENT_MAIN_END: ""
};
}
if (shaderType === "fragment") {
return {
CUSTOM_VERTEX_DEFINITIONS: "",
CUSTOM_VERTEX_MAIN_END: "",
CUSTOM_FRAGMENT_DEFINITIONS: `
varying vec3 v_xyz1;
`,
CUSTOM_FRAGMENT_MAIN_END: `
float dist = length(u_origin - v_xyz1);
float maxDist = u_radius + u_gradient;
float radialAlpha = smoothstep(maxDist, u_radius, dist);
gl_FragColor.a *= radialAlpha;
float glowStrength = 1.0 - radialAlpha;
gl_FragColor.rgb += glowStrength * u_glowColor * gl_FragColor.a;
`
};
}
return null;
}
In the following example, use the slider to increase or decrease the radius and watch the objects in the scene appear or disappear. You can also use the color picker to change the glow color, and adjust the gradient to control how soft or thick the fade effect appears.
Learn More
For advanced use cases, please refer to Babylon.js documentation: https://doc.babylonjs.com/features/featuresDeepDive/materials/using/materialPlugins/.