Chainable onBeforeCompile + Uniforms access (per Mesh)

This little code will patch all material classes to have a chainable onBeforeCompile pattern, so when you assign a callback to onBeforeCompile it will be pushed into a plugins array property on the material instead overriding it. Assign null to reset or use removePlugin to remove a specific. Also addPlugin (same as assining) and hasPlugin is available.

It doesn’t break the API and works without core modification, so WebGLPrograms.js will get a combined string of all functions.

You can also set a priority on the patch function, for example for a patch patching another patch that should be called before, but especially on the code itself, if you use #include .. as entry point to insert the patch code order might matter. If you set DecalPatch.priority = 1; to 0 you’ll see the TintBluePatch will be applied in the shader after DecalPatch just like the order you assigned it and turn it fully blue, with higher priority a callback dominates it’s descendants. Of course this logic depends on how code is inserted, such as before a standard line or after.

So you can do the following without overriding the other:

material.onBeforeCompile = function( shader ) { ... };
material.onBeforeCompile = function( shader ) { ... };


material.onBeforeCompile = [
    function( shader ) { ... },
    function( shader ) { ... }

Below you find a example how you can make a more advanced plugin.

Code (latest):

Basic example:

Simple waving grass plugin:

Material Callbacks

When extending shaders with onBeforeCompile it only gives you acces to the uniforms once. This allows you to not only use uniforms per material but also per object, like in this example:

So you can extend materials in a actual plugin pattern and pass properties of meshes or materials to the shader.

Where you store per-mesh properties is up to you, you can either use the mesh directly like in the example (might be less optimal), store it in it’s userData object, or extend the mesh class (optimal).

Call THREE.MaterialCallback.use(); to apply it to the prototype of Mesh and SkinnedMesh, if you want it manually or need onBeforeRender yourself, just call within your callback: this, renderer, scene, ... )


Beautiful! I kept wanting to look into overriding toString on that function, but i guess it wasn’t as trivial as that, you still wanted to do some tracking.

+1 for another thing that doesnt take 6+ years and thousands of lines of code!


Made some updates

  • Cleanup, only at toString the instance needs to get in it’s scope
  • You can assign a array with callbacks to assign multiple at once
  • Gives error if anything but null, function or a array with such is assigned
  • Fixed order, so like in the example DecalPatch and TintBluePatch are assigned in the order so TintBluePatch makes it fully blue by the entry point logic, so a higher priority to DecalPatch lets it dominate it’s descendants
1 Like

Another downside of onBeforeCompile is other than ShaderMaterial you can only access it’s uniforms when compiling. I’ve extended it now with material callbacks at onBeforeRender.

See first post for more details, here is an example, notice all 3 spheres use the same material:

You can define a patch like the following, notice that the first object to be rendererd will be passed at compile, here you copy it’s properties already, otherwise one object would be the default value for the first frame. You could also store uniform values per material of course.

const myPatch = {
      compile: function( shader, object ) {
          THREE.patchShader(shader, {
            uniforms: {
              tintColor: new THREE.Color().copy( object.tint )
            header: 'uniform vec3 tintColor;',
            fragment: {
                '#include <fog_fragment>': 'gl_FragColor.rgb *= tintColor;'


      render: function( object, uniforms, context ) {
        context.set( uniforms.tintColor, object.tint );

myMaterial.onBeforeCompile = myPatch;

Here an example creating a RoundedBoxMesh class based on Cheap round-edged box (vertex shader)


Made another update

  • patches array renamed to plugins
  • Added addPlugin, removePlugin and hasPlugin to material prototypes
  • Ensures plugins/callbacks aren’t assigned multiple times
  • A frame callback can be defined, called once per frame, like in the following example to update a global time variable
  • If a requires array with plugins/callbacks is defined, it will add these and make sure the they added only once. This is usefull for shared ones like in this example, separating the noise code.


  • If you use post-processing or otherwise call renderer.render() more than once per frame set = false; and call THREE.MaterialCallback.frame(); whenever your actual loop starts.

Here is an example for a grass waving in wind plugin

It also doesn’t require you to modify the normals in advance. This is just a simple example, you could make it much more advanced, such as using some kind of global wind map, define a mass property per object for how much it gets influenced etc.

Used the fiddle from Backface Directional Lighting as template.

The plugin:

const GrassPlugin = {
    time: 0,
    frame: function() {

        this.time += 0.025;

    render: function( object, uniforms, context ) {

        context.set( uniforms.uTime, this.time );
        context.set( uniforms.uSize, object.waveLength );

    compile: function( shader ) {

        shader.uniforms.uSize = {
            value: 0
        shader.uniforms.uTime = {
            value: 0

        shader.fragmentShader = shader.fragmentShader.replace(
            '#include <normal_fragment_begin>',
            'vec3 normal = normalize( vNormal );'
        shader.vertexShader = shader.vertexShader.replace('#include <common>', `
                #include <common>
                #ifndef uTime
                uniform float uTime;
                uniform float uSize;
                float rand(float n){return fract(sin(n) * 43758.5453123);}
                float noise(float p){
                float fl = floor(p);
                float fc = fract(p);
                return mix(rand(fl), rand(fl + 1.0), fc);
                float noise(vec2 n) {
                const vec2 d = vec2(0.0, 1.0);
                vec2 b = floor(n), f = smoothstep(vec2(0.0), vec2(1.0), fract(n));
                return mix(mix(rand(b), rand(b + d.yx), f.x), mix(rand(b + d.xy), rand(b + d.yy), f.x), f.y);


        shader.vertexShader = shader.vertexShader.replace('#include <beginnormal_vertex>', `
            #include <beginnormal_vertex>
            objectNormal = vec3(0.0, 1.0, 0.0);
        shader.vertexShader = shader.vertexShader.replace('#include <project_vertex>', `
            vec4 GRASS_world = modelMatrix * vec4( transformed, 1.0 );
            transformed.xz += uv.y * ( noise(GRASS_world.xz + uTime ) * uSize );
            #include <project_vertex>