R3F: MeshBasicMaterial too bright after replacing texture

I’m dynamically replacing textures on a MeshBasicMaterial and when replaced, the second texture is much brighter than the source. Could anyone advise whether it is possible to dynamically replace the texture of a material and ensure that the second texture is displayed at the correct brightness?

In the image below, the same video texture is being displayed twice. On the left plane, the video replaced another texture (and is too bright). On the right, the video was displayed first, and is the correct brightness.

Minimal reproduction here: replace-texture-brightness - CodeSandbox

Note this is related to R3F, using screen fill and replace by videoTexture but I am ideally looking to continue using the same material but with a new texture.

By all appearances, what is missing here is the correct color space (.encoding) on the video texture. But I’ve tried a few variations of texture.encoding = sRGBEncoding and it doesn’t seem to be having the expected effect… possibly the material needs to recompile (material.needsUpdate = true) when the texture is changed. But I’m not sure how to express that in R3F syntax…

1 Like

Thank you for the reply! In my less-minimal test example which I’m using locally, encoding is correctly set and the issue remains. Does anyone have a sense for how to force a re-compilation in r3f?

Worth noting that my minimal vanilla threejs example works correctly as seen here:

Code here: swap-texture-vanilla-threejs - CodeSandbox

@donmccurdy setting material.needsUpdate = true; after replacing the texture did the trick. In react, I used the useEffect hook to recompile the material after every texture replacement:

function ToggleableVideoMaterial({ texState }) {
  const texture = ToggleableTexture(texState)
  const materialRef = useRef()
  useEffect(() => {
    console.log('swap')
    if (materialRef.current) {
      materialRef.current.needsUpdate = true
    }
  }, [texture])
  return <meshBasicMaterial ref={materialRef} map={texture} toneMapped={false} />
}

Fix here: replace-texture-brightness-fix - CodeSandbox

If anyone has a more elegant solution, please let me know.

Thanks! :slight_smile:

<meshBasicMaterial
  ref={ref}
  map={tex}
  toneMapped={false}
  onUpdate={self => self.needsUpdate = true} />

useEffect is OK, but prefer useLayoutEffect to avoid FOUC, uE will fire after render, uLE will fire before. you also don’t need if (materialRef.current) { materialRef cannot be null.

the shortest effect notation would be

useLayoutEffect(() => void (ref.current.needsUpdate = true), [texture])
2 Likes

Thanks @drcmda !

Is there a reason materials in @react-three/fiber aren’t automatically set to recompile after an update?

And noted re: UseLayoutEffect.

It’s not always necessary (e.g. changing .color or .roughness) and will be expensive to do if you don’t need it. Replacing a texture does not in general require a recompile either. I’m not sure if this is specific to swapping Texture vs. VideoTexture, or some change in the encoding behavior, I may not understand how that works with video sources.

it doesn’t change what threejs is or how it works. this was deliberate because we don’t want to be an engine, just a different way to express three. technically fiber doesn’t know what three is, it doesn’t even have a dependency on it, you have to provide it. that’s what allows it to be somewhat independent, there is no maintenance effort. if three now needs an update for something, you’ll have to provide it just like in vanilla.

an exception to this is when you abstract your own component. for instance most if not all the components in drei take care of such things.

1 Like

Hey i have a similar Problem with meshStandardMaterial. When I’m changing the texture, the new texture appears way brighter than it actually is. I tried it like the example with the meshBasicMaterial but it didnt work. From the useCustomization hook i get a String with the texture name. Im changing the texture with two if statements whats the problem?

export function Tuerblatt(props) {
  const { material, doorWidth, doorHeight } = useCustomization();
  const { nodes, materials } = useGLTF("./models/tuerBlattV3.gltf");

  const materialRef = useRef()

  const doorWidthMetric = (doorWidth / 0.86)
  const doorHeightMetric = (doorHeight / 2) 

  const tuerBlattTexture = useTexture({
    map: './textures/tuerblattTexture/doorAlt.jpg',
  })

  const tuerBlattTextureAlt = useTexture({
    map: './textures/tuerblattTexture/doorDefault.jpg',
  })

  const tuerBlattTextureWood = useTexture({
    map: './textures/tuerblattTexture/doorHell.jpg',
  })

  tuerBlattTexture.map.wrapS = tuerBlattTexture.map.wrapT = THREE.RepeatWrapping
  tuerBlattTextureAlt.map.wrapS = tuerBlattTextureAlt.map.wrapT = THREE.RepeatWrapping
  tuerBlattTextureWood.map.wrapS = tuerBlattTextureWood.map.wrapT = THREE.RepeatWrapping

  tuerBlattTexture.map.repeat.set(doorWidthMetric, doorHeightMetric)
  tuerBlattTextureAlt.map.repeat.set(doorWidthMetric, doorHeightMetric)
  tuerBlattTextureWood.map.repeat.set(doorWidthMetric, doorHeightMetric)

  useEffect(() => void (materialRef.current.needsUpdate = true), [material])

  return (
    <group {...props} dispose={null} key={material}>
      <mesh
        geometry={nodes.Türblatt.geometry}
        position={[0.13, 0, 0]}
        scale={[1, doorHeightMetric, doorWidthMetric]}
        receiveShadow
        castShadow>
          {/* <meshStandardMaterial {...tuerBlattTexture} /> */}
          <meshStandardMaterial ref={materialRef} envMapIntensity={envMapIntensity} toneMapped={false}
            {...(material === "CPLEicheCerne" ? tuerBlattTexture : (material === "StandardGlatt" ? tuerBlattTextureAlt : tuerBlattTextureWood))}
          />
        </mesh>
    </group>
  );
}

useGLTF.preload("./models/tuerBlatt.gltf");

I had a similar problem while updating an old project from the v146 to v160. The texture was way more bright/exposed than before.

Since the v152 texture.encoding = THREE.sRGBEncoding is deprecated, the fix that worked for me is texture.colorSpace = THREE.SRGBColorSpace as mentioned in the deprecation notice :+1:

1 Like