DocumentationAdvancedMaterial Plugins

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, constants
  • CUSTOM_VERTEX_MAIN_BEGIN / CUSTOM_VERTEX_MAIN_END, logic inside the vertex shader main
  • CUSTOM_FRAGMENT_DEFINITIONS, declare variables for fragment shader
  • CUSTOM_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/.