API

Custom Materials

Custom materials

Custom materials let you keep Babylon.js material features while injecting small pieces of GLSL into specific parts of the generated shader.

  • CustomMaterial extends StandardMaterial.
  • PBRCustomMaterial extends PBRMaterial.

Use them when you want to add a focused shader effect without rewriting a full ShaderMaterial. For larger reusable material extensions, a material plugin is usually easier to maintain, but custom materials are useful for quick shader customization and prototyping.

Playground examples

This example uses CustomMaterial to add uniforms, displace vertices, change the diffuse color, and adjust the final fragment color:

PG: CustomMaterial shader injection

This example uses PBRCustomMaterial to keep PBR lighting, metallic and roughness behavior while changing the albedo and final fragment color:

PG: PBRCustomMaterial albedo injection

Using custom materials

Custom materials are part of the materials library, so they are not included in the main babylon.js file.

For a script reference, include the materials library after Babylon.js:

<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="https://cdn.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>

For ES modules, import the classes from @babylonjs/materials:

import { CustomMaterial } from "@babylonjs/materials/custom/customMaterial";
import { PBRCustomMaterial } from "@babylonjs/materials/custom/pbrCustomMaterial";

CustomMaterial

CustomMaterial starts from StandardMaterial. The material keeps standard material properties such as diffuse color, specular color, textures, alpha, lights, fog, and shadows, then applies your injected shader code at the hook points you choose.

import { Color3 } from "@babylonjs/core/Maths/math.color";
import { CustomMaterial } from "@babylonjs/materials/custom/customMaterial";
var material = new CustomMaterial("custom", scene);
material.diffuseColor = Color3.White();
material.AddUniform("stripeScale", "float", 8.0);
material.AddUniform("warmColor", "vec3", new Color3(1.0, 0.5, 0.08));
material.AddUniform("coolColor", "vec3", new Color3(0.03, 0.2, 0.75));
material.Fragment_Definitions(`
float bandedGradient(float value, float scale) {
return smoothstep(0.25, 0.75, 0.5 + 0.5 * sin(value * scale));
}
`);
material.Fragment_Custom_Diffuse(`
float bands = bandedGradient(vPositionW.y, stripeScale);
result = mix(coolColor, warmColor, bands);
`);

Fragment_Custom_Diffuse runs after the standard material has computed its diffuse color. In that hook, result is a helper alias for the material's internal diffuseColor variable.

PBRCustomMaterial

PBRCustomMaterial starts from PBRMaterial. It keeps PBR features such as metallic, roughness, image-based lighting, reflection, and environment response, then lets you inject shader code into the PBR shader.

import { Color3 } from "@babylonjs/core/Maths/math.color";
import { PBRCustomMaterial } from "@babylonjs/materials/custom/pbrCustomMaterial";
var material = new PBRCustomMaterial("custom-pbr", scene);
material.albedoColor = new Color3(0.9, 0.78, 0.55);
material.metallic = 0.85;
material.roughness = 0.28;
material.AddUniform("stripeScale", "float", 6.0);
material.AddUniform("accentColor", "vec3", new Color3(0.03, 0.55, 1.0));
material.Fragment_Custom_Albedo(`
float stripe = smoothstep(0.35, 0.75, 0.5 + 0.5 * sin(vPositionW.y * stripeScale));
result = mix(surfaceAlbedo, accentColor, stripe * 0.65);
`);
material.Fragment_Before_FragColor(`
finalColor.rgb = mix(finalColor.rgb, vec3(0.05, 0.12, 0.22), 0.12);
`);

Fragment_Custom_Albedo runs after the PBR material has computed its surface albedo. In that hook, result is a helper alias for surfaceAlbedo.

Adding uniforms

Use AddUniform(name, kind, value) when your shader code needs a custom uniform:

import { Texture } from "@babylonjs/core/Materials/Textures/texture";
import { Color3 } from "@babylonjs/core/Maths/math.color";
material.AddUniform("tint", "vec3", new Color3(0.2, 0.8, 1.0));
material.AddUniform("scale", "float", 4.0);
material.AddUniform("maskTexture", "sampler2D", new Texture("textures/mask.png", scene));

The kind string is the GLSL type that will be declared in the shader, such as float, vec2, vec3, vec4, mat4, or sampler2D.

Shader hook points

These hooks are available on both CustomMaterial and PBRCustomMaterial:

MethodWhere the code is injected
Fragment_Begin(shaderPart)
At the beginning of the fragment shader main function.
Fragment_Definitions(shaderPart)
Before the fragment shader main function. Use this for helper functions and varying declarations.
Fragment_MainBegin(shaderPart)
Near the start of the fragment shader main function.
Fragment_Custom_Alpha(shaderPart)
Where the shader updates alpha. In this hook, result maps to alpha.
Fragment_Before_FragColor(shaderPart)
Just before the final fragment color is written.
Vertex_Begin(shaderPart)
At the beginning of the vertex shader main function.
Vertex_Definitions(shaderPart)
Before the vertex shader main function. Use this for helper functions and varying declarations.
Vertex_MainBegin(shaderPart)
Near the start of the vertex shader main function.
Vertex_Before_PositionUpdated(shaderPart)
After positionUpdated is available and before the final position update. In this hook, result maps to positionUpdated.
Vertex_Before_NormalUpdated(shaderPart)
After normalUpdated is available and before the final normal update. In this hook, result maps to normalUpdated.

CustomMaterial also supports:

MethodWhere the code is injected
Fragment_Custom_Diffuse(shaderPart)
After the standard material diffuse color is computed. In this hook, result maps to diffuseColor.

PBRCustomMaterial also supports:

MethodWhere the code is injected
Fragment_Custom_Albedo(shaderPart)
After the PBR surface albedo is computed. In this hook, result maps to surfaceAlbedo.
Fragment_Custom_MetallicRoughness(shaderPart)
During metallic and roughness computation.
Fragment_Custom_MicroSurface(shaderPart)
During microsurface computation.
Fragment_Before_FinalColorComposition(shaderPart)
Before final color composition.

Notes

The injected shader code is GLSL, including when the scene runs in WebGPU mode.

If you define a varying to pass data from vertex shader code to fragment shader code, declare it in both Vertex_Definitions and Fragment_Definitions.

Use AddUniform for uniforms instead of declaring uniform values manually in Fragment_Definitions or Vertex_Definitions, because custom material uniforms must also be registered with the material.