ShaderMaterial Defines for GLSL Macros in Three.js
This article explains how to use the defines object in
Three.js ShaderMaterial to control GLSL preprocessor
macros. You will learn how to inject defines into your shaders, write
conditional GLSL code based on these defines, and update them
dynamically at runtime to optimize shader performance and code
reusability.
Understanding the Defines Object
In Three.js, ShaderMaterial allows you to pass a
defines object containing key-value pairs. During shader
compilation, Three.js automatically prepends these pairs as
#define KEY VALUE statements at the very top of both your
vertex and fragment shaders. This is highly useful for toggling
features, setting constants, or conditionally compiling blocks of code,
which helps avoid runtime branch performance penalties in WebGL.
Here is a basic example of declaring defines when instantiating a
ShaderMaterial:
const material = new THREE.ShaderMaterial({
defines: {
USE_COLOR: true,
MAX_LIGHTS: 4,
GRID_SIZE: '10.0'
},
vertexShader: vertexShaderSource,
fragmentShader: fragmentShaderSource
});Three.js converts this object into the following GLSL preprocessor commands at the top of your shader code:
#define USE_COLOR true
#define MAX_LIGHTS 4
#define GRID_SIZE 10.0Using Defines in GLSL Shaders
Once declared, you can use standard GLSL preprocessor directives like
#ifdef, #ifndef, #if, and
#endif to conditionally compile your shader code.
// Fragment Shader Example
uniform vec3 uBaseColor;
varying vec3 vCustomColor;
void main() {
vec3 finalColor = uBaseColor;
#ifdef USE_COLOR
finalColor *= vCustomColor;
#endif
gl_FragColor = vec4(finalColor, 1.0);
}If USE_COLOR is defined and truthy, the compiler
includes the multiplication step. If it is undefined or set to false,
the compiler completely ignores that block of code, saving GPU
cycles.
Updating Defines at Runtime
You can modify the defines object after the material has
been created. However, because preprocessor macros are resolved at
compile time, you must instruct Three.js to recompile the shader program
by setting the material’s needsUpdate property to
true.
// Toggle the feature off
material.defines.USE_COLOR = false;
// Trigger a shader recompilation
material.needsUpdate = true;To completely remove a define, use the delete
keyword:
delete material.defines.USE_COLOR;
material.needsUpdate = true;Best Practices and Performance
- Avoid Frequent Recompilation: Setting
material.needsUpdate = trueforces WebGL to recompile the shader. This is a heavy CPU/GPU operation. Do not toggle defines inside the animation loop (requestAnimationFrame). Use uniforms instead of defines if the value changes every frame. - Value Casting: Three.js converts numbers and
booleans directly to strings when prepending them to the shader. Ensure
your GLSL code handles types correctly (e.g., if a define represents a
float, write it as a string like
'10.0'in JavaScript to avoid type-mismatch errors in GLSL).