onBeforeCompile does not update MeshStandardMaterial when material updated

I created a custom shader from MeshStandardMaterial, set uniforms in it and use it so. Value of the uniform is a global variable that I update and the set the material
needsUpdate = true to update the material. OnBeforeCompile fires, uniform gets the new value, but I cannot observe any changes on the 3D mesh itself. I tried re-setting the material to my 3d meshed material field too, still does not work and does not throw any erros.

Do you mind sharing your code? Or even better a live example?

Thank you @Mugen87 the project is somewhat large and private at this stage can’t really share the code as is, and tried setting an example in codepen but having issues with Fbx Exporter plugin. Here is how it is:

var globalColor;

function SetMaterials() {
  var material= new THREE.MeshStandardMaterial();
  globalColor = new THREE.Vector4( 1, 0, 0, 1 );

  material.onBeforeCompile = function ( shader ) {
    shader.uniforms.my_uniform = { value: globalColor  };

    shader.vertexShader = VertexShader;
    shader.fragmentShader = FragmentShader;
  material.roughness = 0.8;

  //these are for blendshapes
  material.morphTargets = true; 
  material.skinning = true;

  //has other materials
  myMesh.material[0] = material;

Object will appear red.

function SetColor(color){
  globalColor = color;
  myMesh.material[0].needsUpdate = true;

Later I will call this method to update the color, I can observe that onBeforeCompile is fired and new value of the uniform is set. However I cannot observe any changes.

I again use some customized version of MeshStandardMaterial, where let’s say frag shader looks like this. I do not suspect there to be any error with tha shaders, as it works completely fine with the initial values I send however does not update. Though, I may need to add some include or define some keywords there, no idea.

uniform vec4 my_uniform ;
void main() {
  gl_FragColor = vec4( outgoingLight + my_uniform.xyz, 1.0);

Can you try it like so:

globalColor.copy( color );

If you just assign a new color object to globalColor, it does not automatically update the reference of shader.uniforms.my_uniform.value.

I also think setting needsUpdate to true is not necessary then.

Sorry my bad, to simplify the example I paraphrased it like so. The color actually is a hex string, which I later parse into THREE.Vector4.

function HexToVec4(hex){
  //ex: hex equals "#ab11df"
  var r = parseInt(hex.substring(1, 3), 16) / 255;
  var g = parseInt(hex.substring(3, 5), 16) / 255;
  var b = parseInt(hex.substring(5, 7), 16) / 255;
  var a = 1;

  if(hex.length > 7) {
    a = parseInt(hex.substring(7, 9), 16) / 255;
  return new THREE.Vector4( r, g, b, a );

The uniform line looks like this:
shader.uniforms.my_uniform = { value: HexToVec4(globalColor)};

So I suppose it should not be a reference vs value type issue. When I inspect the code (when it runs onBeforeCompile again after setting needsUpdate = true) the value of my_uniform is set correctly.

Thing is the official example also uses a custom uniform (time) and it not necessary to set needsUpdate to true.

A recompile of the shader is not necessary just because a uniform is updated.

1 Like

Thank you so much! Just like in that example I referenced the shader parameter in a global variable and set the uniform using it. I had no other way of reaching the uniforms other than running the onBeforeCompile, uniforms are not exposed in MeshStandardMaterial as far as I see.

@Sarge Take a look at this post: How to have different colors/textures on bottom and top side of a Plane?

1 Like