Object-specific uniforms / uniformsNeedUpdate for MeshStandardMaterial

I’m working on supporting lightmaps exported from Unity in Three.js. They work a little differently from three.js’s implementation where a few global lightmaps are shared across materials. In this implementation multiple instances of the same mesh have their lightmap data baked into a single lightmap, the mesh has one uv2 set and each instance of the mesh has a lightmap offset/scale. Also each instance could theoretically be baked into a different lightmap.

In my implementation I’d like to solve this by having all the instances of the same mesh share a material. That way I can update uniforms in one place and ensure that they all share the same program. However, they do need to set a couple uniforms on a per-mesh basis. Here’s what my current solution looks like:

mesh = new Mesh(geometryObj, materialObj);

if (lightMap) {
  const { offset, scale, intensity, texture: lightMapTexture } = lightMap;

  const material = materialObj as MeshBasicMaterial | MeshStandardMaterial;

  if (!material.userData.lightMapTransform) {
    const lightMapTransformMatrix = new Matrix3().setUvTransform(0, 0, 1, 1, 0, 0, 0);
    const lightMapTransform = new Uniform(lightMapTransformMatrix);

    material.onBeforeCompile = (shader) => {
      shader.uniforms.lightMapTransform = lightMapTransform;
    };

    material.needsUpdate = true;

    material.userData.lightMapTransform = lightMapTransform;
  }

  mesh.onBeforeRender = () => {
    material.lightMapIntensity = intensity * Math.PI;
    material.lightMap = lightMapTexture.texture;
    ((material.userData.lightMapTransform as Uniform).value as Matrix3).setUvTransform(
      offset[0],
      offset[1],
      scale[0],
      scale[1],
      0,
      0,
      0
    );
  };
}

The mesh.onBeforeRender hook ends up working out well, it gets called per-mesh and I can update the material right before it’s used. However, due to how uniform caching works, only the first mesh to get rendered in that frame sets its uniforms.

In previous versions of Three.js there were dynamic uniforms that could change multiple times in a frame. Is there any other way to force a material’s uniforms to be refreshed even if two objects with the same material render back to back?

It looks like this exists for ShaderMaterial Suggestion: Support refreshing specific uniforms independent of switching program / material · Issue #9870 · mrdoob/three.js · GitHub is there any work around for getting this to work with the MeshStandardMaterial?

Were you able to find a solution to this problem? I am looking for a similar solution!

Yeah, here is my workaround Add uniformsNeedUpdate to base Material class by robertlong · Pull Request #1 · matrix-org/three.js · GitHub

And here is it in use: thirdroom/mesh.render.ts at e7f99bd81de0d0bdd48cf1e95b0fcaaf5df6cf3c · matrix-org/thirdroom · GitHub

So correct me if I am wrong I have looked through the Third Room Repo and it appears you’re not using images for your baked lighting. I am guessing you’re capturing your dynamic lighting at runtime and creating baked lighting from that?

Maybe lightmap texture into glb file

Yup, baked lighting and stored as a texture in the glb with a custom extension.

Forgive me since I am new to programming in Threejs. But from what I can understand is you have your own custom branch of Threejs that you have updated to make this work. So if I just npm install Threejs it will not have these changes made to it. I am trying to do something similar in that I want to use my baked lightmaps from Blender inside of Threejs on Instanced meshes, but be able to use custom offset and scale for the UV islands to place the correct lightmap on the instances. Would this still be possible with the route you have implemented?