Function to extend Materials

This is a little helper function to create a THREE.ShaderMaterial extending a standard material. The second parameter is optional. Instead the constructor of a standard material, you can also pass a instance of a shader material and extend it the same way.

Using THREE.ShaderMaterial.extend

const myMaterial = THREE.ShaderMaterial.extend(THREE.MeshPhongMaterial, {

    // Will be prepended to vertex and fragment code
    header: 'varying vec3 vEye;',


    // Insert code lines by hinting at a existing
    vertex: {

        // Inserts the line after #include <fog_vertex>
        
        '#include <fog_vertex>': 'vEye = normalize(cameraPosition - w.xyz);',

        // Replaces a line (@ prefix) inside of the project_vertex include

        'project_vertex': {
            '@vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );': 'vec4 mvPosition = modelViewMatrix * vec4( transformed * 0.5, 1.0 );'
        }
    },
    fragment: {
        '#include <envmap_fragment>': 'diffuseColor.rgb += pow(dot(vNormal, vEye), 3.0);'
    },


    // Properties to apply to the new THREE.ShaderMaterial
    material: {
        skinning: true
    },


    // Uniforms (will be applied to existing or added)
    uniforms: {
        diffuse: new THREE.Color(0xffffff)
    }

});

Using THREE.patchShader

const myMaterial = new THREE.MeshPhongMaterial({

    onBeforeCompile: function(shader) {

        THREE.patchShader(shader, {

            header: 'uniform vec3 tint;',

            fragment: {
                '#include <fog_fragment>': 'gl_FragColor.rgb *= tint;'
            },

            vertex: {
                'project_vertex': {
                    '@vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );':
                        'vec4 mvPosition = modelViewMatrix * vec4( transformed * 0.25, 1.0 );'
                }
            },

            uniforms: {
                tint: new THREE.Color('orange')
            }

        });

    }
});

Code:

https://mevedia.com/share/ShaderMaterialExtend.js?v2

5 Likes

I’ve updated the code to automatically set the constants (such as USE_MAP) when the corresponding maps are set as uniform.

Also here is an example extending the THREE.MeshPhongMaterial with a fresnel effect:
https://codepen.io/Fyrestar/pen/RzVLYd

4 Likes

I updated the code, you can now replace lines too instead only inserting after them by prepending a @ symbol, you can also insert/replace lines inside of includes. Here how it both works basically.

THREE.ShaderMaterial.extend(THREE.MeshPhongMaterial, {

    vertex: {
    
        // Replaces a line inside of the project_vertex include
    
        'project_vertex': {
            '@vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );': 'vec4 mvPosition = modelViewMatrix * vec4( transformed * 0.5, 1.0 );'
        }
    }
});
3 Likes

Nice! I’ve been using a similar pattern but instead pass the ShaderLib.* object into it and return an object with the vertexShader, fragmentShader, defines, and uniforms fields modified so they can recursively extended – sort of like shader mixins:

const checkboardShader = checkerboardMixin(ShaderLib.standard);
const topographicLineShader = topolineMixin(ShaderLib.standard);
const checkboardTopoShader = checkerboardMixin(topolineMixin(ShaderLib.standard));

// ...

const material = new ShaderMaterial(checkboardTopoShader);

The application order of shader mixins can be a bit confusing but I’ve found it to be pretty flexible so far. You may be able to achieve something similar by allowing mixins to “build” the extension object you pass in.

Nice work!

1 Like

Thanks :+1: i updated it now so you can also pass a ShaderMaterial too instead the constructor of a standard material and extend them the same way.

https://codepen.io/Fyrestar/pen/mZxJBy

Edit: I’ve also added THREE.patchShader now, it basically deals with plain objects only and can be used for example at onBeforeCompile. It only deals with fragmentShader, vertexShader and uniforms, but with the same patching pattern, recursive includes etc.

In the pen above the outer ring is a material extending MeshPhongMaterial, the second is extending the extended material, and the third is patching with onBeforeCompile.

1 Like

Little update, with a question mark prefix lines can be prepended. If the alphaTest property is set and above 0 in uniforms the constant will be set.