BabylonJS - ShaderMaterial
BabylonJS - Using ShaderMaterial
Section titled “BabylonJS - Using ShaderMaterial”ShaderMaterial allows you to define a mesh’s appearance using custom shader programs written in GLSL (OpenGL Shading Language). This gives you maximum control over the rendering process, enabling unique visual effects beyond standard materials.
It acts as a bridge, passing data (like mesh vertices, normals, UVs, and custom uniforms) from your JavaScript code to the Vertex and Fragment shaders running on the GPU.
Note: Writing GLSL shaders requires understanding GPU rendering concepts. For many effects, the NodeMaterial editor provides a powerful visual alternative without direct GLSL coding.
Creating a ShaderMaterial
Section titled “Creating a ShaderMaterial”You create an instance of BABYLON.ShaderMaterial:
const myShaderMaterial = new BABYLON.ShaderMaterial( "myShader", // Unique name for the material scene, // The Babylon scene { vertex: "customVertexShaderName", // Identifier for vertex shader code fragment: "customFragmentShaderName" // Identifier for fragment shader code // Or use vertexSource/fragmentSource for direct strings // Or vertexElement/fragmentElement for script tag IDs // Or provide a base path string like "./shaders/myShader" // (loads myShader.vertex.glsl and myShader.fragment.glsl) }, { attributes: ["position", "normal", "uv"], // Standard attributes Babylon provides uniforms: ["worldViewProjection", "time", "customColor"], // Uniforms expected by shaders samplers: ["textureSampler"] // Texture samplers expected by shaders });Parameters Explained
Section titled “Parameters Explained”- Name: A unique string identifying this material.
- Scene: The BabylonJS scene instance.
- Shader Path/Object: Specifies where to find the GLSL code. Several options:
-
{ vertex: "name", fragment: "name" }: Uses shaders stored inBABYLON.Effect.ShadersStore["nameVertexShader"]andBABYLON.Effect.ShadersStore["nameFragmentShader"].
-
{ vertexSource: "glsl code string", fragmentSource: "glsl code string" }: Embeds GLSL code directly as strings.
-
{ vertexElement: "id", fragmentElement: "id" }: Uses GLSL code embedded within<script>tags in your HTML, identified by theirid.
-
"./path/shaderName": (Recommended for organization) Loads shaders from external files namedshaderName.vertex.glslandshaderName.fragment.glsllocated relative to your HTML.
- Options Object: Declares the data your shaders expect:
-
attributes: An array of strings listing the per-vertex attributes required (e.g.,position,normal,uv,color). BabylonJS automatically provides standard ones if the mesh has them.
-
uniforms: An array of strings listing the uniform variables required. Uniforms are constant across all vertices/fragments for a single draw call (e.g., transformation matrices likeworldViewProjection, time, colors, custom values). BabylonJS provides standard matrices automatically.
-
samplers: An array of strings listing the texture samplers required (e.g.,textureSampler).
GLSL Basics for ShaderMaterial
Section titled “GLSL Basics for ShaderMaterial”You’ll need two shader programs:
- Vertex Shader: Runs once per vertex. Its primary job is to calculate the final screen position (
gl_Position) of the vertex. It can also pass data (like UV coordinates or calculated lighting values) to the fragment shader viavaryingvariables. - Fragment (Pixel) Shader: Runs once per pixel covered by the mesh. Its primary job is to calculate the final color (
gl_FragColor) of that pixel, often using data passed from the vertex shader and texture lookups.
Common GLSL variable types:
float: Single-precision floating-point number.vec2,vec3,vec4: Vectors of 2, 3, or 4 floats (used for positions, colors, UVs).mat3,mat4: 3x3 or 4x4 matrices (used for transformations).sampler2D: Represents a 2D texture.attribute: Input data per vertex (from JavaScript viaattributesoption).uniform: Input data constant per draw call (from JavaScript viauniformsoption).varying: Used to pass data from the vertex shader to the fragment shader.gl_Position(Vertex Shader Output): The final clip-space position of the vertex.gl_FragColor(Fragment Shader Output): The final color of the pixel.
GLSL shaders typically start with precision highp float; to specify the default precision for floating-point calculations.
Example: Simple Texture Shader
Section titled “Example: Simple Texture Shader”Let’s create a simple material that just applies a texture to a mesh using ShaderMaterial. We’ll store the GLSL code in the ShadersStore for this example.
import * as BABYLON from '@babylonjs/core';
const canvas = document.getElementById('renderCanvas');const engine = new BABYLON.Engine(canvas, true);
const createScene = async function() { const scene = new BABYLON.Scene(engine); const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 5, BABYLON.Vector3.Zero(), scene); camera.attachControl(canvas, true); const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
// --- GLSL Shader Code ---
// Vertex Shader: Pass position and UVs through BABYLON.Effect.ShadersStore["customVertexShader"] = ` precision highp float;
// Attributes (provided by Babylon based on mesh data) attribute vec3 position; attribute vec2 uv;
// Uniforms (provided by Babylon) uniform mat4 worldViewProjection;
// Varying (data to pass to fragment shader) varying vec2 vUV;
void main(void) { // Calculate final screen position gl_Position = worldViewProjection * vec4(position, 1.0); // Pass UV coordinates to the fragment shader vUV = uv; } `;
// Fragment Shader: Sample texture using UVs BABYLON.Effect.ShadersStore["customFragmentShader"] = ` precision highp float;
// Varying (received from vertex shader) varying vec2 vUV;
// Uniforms (texture sampler we'll set from JS) uniform sampler2D textureSampler;
void main(void) { // Look up color in the texture at the interpolated UV coordinate gl_FragColor = texture2D(textureSampler, vUV); } `;
// --- Create ShaderMaterial Instance ---
const shaderMaterial = new BABYLON.ShaderMaterial("simpleTextureShader", scene, { vertex: "custom", // Corresponds to ShadersStore entry name fragment: "custom" }, { attributes: ["position", "uv"], // We need position and UVs from the mesh uniforms: ["worldViewProjection"], // Babylon provides this matrix samplers: ["textureSampler"] // Declare the sampler uniform we'll use } );
// --- Set Uniforms ---
// Load the texture const mainTexture = new BABYLON.Texture("path/to/your/texture.jpg", scene);
// Set the texture sampler uniform shaderMaterial.setTexture("textureSampler", mainTexture);
// Optional: Disable backface culling if needed shaderMaterial.backFaceCulling = false;
// --- Apply to Mesh ---
const plane = BABYLON.MeshBuilder.CreatePlane("plane", {size: 2}, scene); plane.material = shaderMaterial;
return scene;};
createScene().then(scene => { engine.runRenderLoop(function() { if (scene) { scene.render(); } });});
window.addEventListener('resize', function() { engine.resize(); });Explanation
Section titled “Explanation”- GLSL Code: We define basic vertex and fragment shaders. The vertex shader transforms the
positionusing theworldViewProjectionmatrix (provided by BabylonJS) and passes theuvcoordinates to the fragment shader via thevUVvarying. - Fragment Shader: It receives the interpolated
vUVand uses the built-intexture2Dfunction to look up the color from thetextureSamplerat those coordinates, setting the result togl_FragColor. - ShaderMaterial Creation: We instantiate
ShaderMaterial, referencing the shaders stored inShadersStorevia the"custom"identifier. We declare the necessaryattributes(position,uv),uniforms(worldViewProjection), andsamplers(textureSampler). - Setting Uniforms: We create a
BABYLON.Textureand useshaderMaterial.setTexture("textureSampler", mainTexture)to bind it to theuniform sampler2D textureSamplervariable in the fragment shader. - Application: We assign the created
shaderMaterialto thematerialproperty of a plane mesh.
Passing Custom Data (Uniforms)
Section titled “Passing Custom Data (Uniforms)”You can pass your own data (like time for animations, colors, threshold values) to shaders using uniforms.
// --- Inside createScene ---
// Modify Fragment Shader to use a custom colorBABYLON.Effect.ShadersStore["customColorFragmentShader"] = ` precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; uniform vec4 customColor; // Added uniform uniform float time; // Added uniform
void main(void) { vec4 textureColor = texture2D(textureSampler, vUV); // Modulate texture color by customColor and add a time-based effect gl_FragColor = textureColor * customColor * (0.75 + sin(time) * 0.25); }`;
// Create ShaderMaterial, adding new uniformsconst customShaderMaterial = new BABYLON.ShaderMaterial("customColorShader", scene, { vertex: "custom", fragment: "customColor" }, // Use the new fragment shader { attributes: ["position", "uv"], uniforms: ["worldViewProjection", "customColor", "time"], // Added uniforms samplers: ["textureSampler"] });
// Set the textureconst myTexture = new BABYLON.Texture("path/to/your/texture.jpg", scene);customShaderMaterial.setTexture("textureSampler", myTexture);
// Set initial custom uniform valuesconst colorToSend = new BABYLON.Color4(0.5, 1.0, 0.5, 1.0); // Greenish tintcustomShaderMaterial.setColor4("customColor", colorToSend);customShaderMaterial.setFloat("time", 0);
// Apply to a meshconst sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2}, scene);sphere.material = customShaderMaterial;
// Animate the 'time' uniform in the render looplet time = 0;scene.onBeforeRenderObservable.add(() => { time += engine.getDeltaTime() / 1000.0; // Time in seconds customShaderMaterial.setFloat("time", time);});Here, we added uniform vec4 customColor and uniform float time to the fragment shader. In JavaScript, we declare these in the uniforms array during ShaderMaterial creation. We then use methods like setColor4("uniformName", value) and setFloat("uniformName", value) to pass the data. Other methods include setVector3, setMatrix, etc. Updating the time uniform in the render loop creates an animation effect within the shader.
ShaderMaterial is a powerful tool for advanced rendering. Start with simple examples and gradually increase complexity. Always check the browser’s developer console for GLSL compilation errors if your shader doesn’t work as expected.