Chainable onBeforeCompile + Uniforms per Mesh

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.

Notes:

  • If you use post-processing or otherwise call renderer.render() more than once per frame set THREE.MaterialCallback.auto = 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.

https://codepen.io/Fyrestar/pen/PMyZpR
GrassPlugin

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;
                #endif
                
                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>
        `);

    }
};
6 Likes