A problem that I’m seeing is that if a material subclass does the following, it won’t work as expected:
class MyMaterial extends MeshPhongMaterial {
#camera = new PerspectiveCamera
get camera() {return this.#camera}
set camera(c) {
this.#camera = c
this.needsUpdate = true // "re-compile"
}
onBeforeCompile(shader) {
// only runs once, initially
if (this.#camera instanceof OrthographicCamera)
shader.defines.ORTHOGRAPHIC = ""
else delete shader.defines.ORTHOGRAPHIC
// ... patch shader for example ...
}
}
and the problem is that, if we set myMat.camera = new OrthographicCamera, the ORTHOGRAPHIC define does not get updated.
Workaround:
The above example can be written like the following instead:
class MyMaterial extends MeshPhongMaterial {
#camera = new PerspectiveCamera
get camera() {return this.#camera}
set camera(c) {
this.#camera = c
this.needsUpdate = true // "re-compile"
this.onBeforeCompile = c instanceof PerspectiveCamera
? this.#onBeforeCompilePerspective
? this.#onBeforeCompileOrtho
}
#onBeforeCompilePerspective = (shader) => {
delete shader.defines.ORTHOGRAPHIC
patch(shader)
}
#onBeforeCompileOrtho = (shader) => {
shader.defines.ORTHOGRAPHIC = ""
patch(shader)
}
onBeforeCompile = this.#onBeforeCompilePerspective
}
function patch(shader) {...}
And now, because WelGLProgram.getProgramCacheKey will return a different key of the a different onBeforeCompile function is detected, it will make a new program upon toggle (and successive toggles use one program or the other).
If you set needsUpdate to true and expect a recompilation of the shader, you have to ensure that the program cache key actually changes. If not, three.js won’t recompile the shader since no structural changes happened.
When using onBeforeCompile() like in your above example, you have to make use of customProgramCacheKey() which was introduced some while ago for a similar use case. This method returns onBeforeCompile() as a string by default, but you can implement it with any code you like. The documentation contains a code example to identify two different permutations that onBeforeCompile() can produce.
Indeed I realized that after looking at the source code, namely that onBeforeCompile functions are used in the cache key hence I could swap them.
Maybe needsUpdate needs some addition regarding the “recompile” that it mentions. The docs made it seem like needsUpdate would surely trigger a recompile.
The doc for customProgramCacheKey says
Unlike properties, the callback is not supported by .clone(), .copy() and .toJSON().
What does it mean? material.clone() will not include the customProgramCacheKey for the cloned material?