Extending three.js materials with GLSL

What are three.js materials?

MeshStandardMaterial extended with specular/gloss + instancing +map transformations. See demo.

What do materials do?

What do shaders do?

What are we trying to solve?

new Loader.load( ‘someModel.someFormat’, model => scene.add(model) )
model.traverse( obj => {
obj.receiveShadows = true
obj.castShadows = true
})

The research

#define PHYSICAL                //GLSL  varying vec3 vViewPosition;   //GLSL#ifndef FLAT_SHADED             //GLSL  varying vec3 vNormal;         //GLSL#endif                          //GLSL#include <common>               //NOT GLSL
#include <uv_pars_vertex> //NOT GLSL
#include <uv2_pars_vertex> //NOT GLSL
#ifdef USE_ALPHAMAP 
diffuseColor.a *= texture2D( alphaMap, vUv ).g;
#endif

The problem

The copy paste route

var myStandardMaterial = new THREE.MeshStandardMaterial()
myStandardMaterial.roughness = 1
var myExtendedStandardMaterial = new MyExtendedStandardMaterial()
myStandardMaterial.uniforms.roughness.value = 1 //interface changed

The monkey patch route

//before loading your app
THREE.ShaderChunk.some_chunk = my_chunk
#ifdef MY_DEFINE  myLogic()#else 

threeDefaultLogic()
#endif
var myDefaultMaterial = new THREE.MeshStandardMaterial()var myModifiedMaterial = new THREE.MeshStandardMaterial()
myModifiedMaterial.defines.MY_DEFINE = '' //triggers our branch

The onBeforeCompile route

#ifdef USE_ALPHAMAP 
diffuseColor.a *= texture2D( alphaMap, vUv * 2. ).g;
#endif
var myMaterial = new THREE.MeshStandardMaterial()myMaterial.onBeforeCompile = shader => {  shader.fragmentShader = //this is the fragment program string in the template format 
shader.fragmentShader.replace( //we have to transform the string
'#include <alphamap_fragment>', //we will swap out this chunk
require('my_alphamap_fragment.glsl') //with our own
)
}
#ifdef USE_ALPHAMAP 
diffuseColor.a *= texture2D( alphaMap, vUv * myValue ).g;
#endif
var myMaterial = new THREE.MeshStandardMaterial()myMaterial.userData.myValue = { value: 2 } //this will be our input, the system will just reference itmyMaterial.onBeforeCompile = shader => {  shader.uniforms.myValue = myMaterial.userData.myValue //pass this input by reference

//prepend the input to the shader
shader.fragmentShader = 'uniform vec2 myValue;\n' + shader.fragmentShader
//the rest is the same
shader.fragmentShader =
shader.fragmentShader.replace(
'#include <alphamap_fragment>',
require('my_alphamap_fragment.glsl')
)
}

Some code

Some gotchas

myMesh.customDepthMaterial =   new THREE.MeshDepthMaterial() 

Some thoughts

const material = new THREE.MeshBasicMaterial()material.chunks.begin_normal = myChunk
const myGenericOnBeforeCompile = shader=>{
const {customUniforms, customChunks} = this.userData
Object.keys(customUniforms).forEach(uName=>{
shader.uniforms[uName] = customUniforms[uName]
})
Object.keys(customChunks).forEach(chunkName=>{
//store `vertex` or `fragment`
let shaderStage = customChunks[chunkName].vertexStage
shaderStage = `${shaderStage}Shader`
shader[shaderStage] = shader[shaderStage].replace(
`#include <${chunkName}>`,
customChunks[chunkName]
}
})
}
myMaterial.onBeforeCompile = myGenericOnBeforeCompile.bind(myMaterial)

i like computer graphics