Texture don't load on first render, using either useTexture or TextureLoader (with repros)

Hi,

I’m working on a small project where I load textures dynamically from a server on the first load and user actions. Everything works fine, except for the first render.

I created a repro here: immutable-wave-cprcpd - CodeSandbox

Simply click on the canvas to see the issue.

For some reason, it seems the texture update is queued but never rendered. Resizing the browser window or scrolling makes it render correctly. I can also see for a very short time the initial texture being applied right before the new texture when switching textures.

Not sure what I’m doing wrong here?

Thanks!

useLayoutEffect is a version of useEffect that fires before the browser repaints the screen. the result of TextureLoader is only returned after the texture has been loaded meaning you’ve got a race condition eg. the screen is being rendered before the texture has loaded, have you considered using useTexture from within drei?

https://drei.pmnd.rs/?path=/story/loaders-texture--use-texture-scene-st

2 Likes

Thanks for your answer!

I tried using useEffect as well without much success.

My first version was using useTexture but I had some weird issues with the bumpMap or normalMap not being applied correctly. I can do a repro as well.

I unfortunately have the exact same issue with useTexture as shown here: great-margulis-enrzyy - CodeSandbox

You can also see the issue I encountered with normal maps (or any kind of maps). Compare this to the first codesandbox. The front part is much darker and you can see strong edges on the shoulders. This does not happen when using the TextureLoader. (I know the code to set the new texture is wrong in this case, but since the initial texture has the normal map I just need to change a prop to trigger the rerender to show the issue)

Would appreciate any pointers on this, tried multiple examples in the documentation and GitHub issues (like here: Textures from `useTexture` don’t receive options applied in `useEffect` · Issue #902 · pmndrs/drei · GitHub) but I can’t get rid of the problem.

useTexture is nothing else than new THREE.TextureLoader().load(url, data => … it cannot have an effect on normals etc. you should always prefer useTexture though because it prevents jank, it will pre-emptively upload the texture to the gpu. ootb threejs will only start to upload once the renderer “sees” a texture, if it’s in frustum.

a normal texture can behave messed up, in threejs, if it has the from color space (it should be linear), and three expects textures to be upside down (flipY=false)

other than that the sandbox i just tried makes no sense to me, it has a useLayoutEffect with async texture loaders in it (that means the results will be ready … whenever). that completely drowns what useLoader does, it destroys suspense, so the results is practically a race condition and cannot be awaited any longer. if you have changed it to useTexture let’s have another look.

const textures = useTexture({
  map: "foo,png",
  bumpMap.: "bar.png",
  ...
})
return (
  <mesh>
    <meshStandardMaterial {...material} />

another thing is material={materialRef.current} you can’t put one ref on multiple materials. a ref is a reference to a single object.

1 Like

Thanks for your answer. I did make a second sandbox with useTexture that you can try here: great-margulis-enrzyy - CodeSandbox

I’m using the callback from useTexture but I also tried with the just settings the map props right after or in a useEffect without success

I’m not sure I understand the part about the ref. My ref references one material and I use the referenced material on multiple meshes.

why does the group have a material? only meshes can have materials? i don’t understand what is supposed to happen.

there are also meshes without any material.

I’ve read that this was the way to share a single material between multiple meshes. Those answer might have been wrong.

The mesh without material are the buttons. I just removed it to make the repro simpler. I just removed the mesh, it’s not needed for my example.

  const [texture1, texture2, .......] = useTexture(urls)
  const material = useMemo(() => {
    texture1.repeat.set(x, y)
    ...
    return new THREE.StandardMaterial({ ........ })
  }, [texture])
  return (
    <group>
     <mesh geometry={foo} material={material} />
     <mesh geometry={bar} material={material} />
     <mesh geometry={baz} material={material} />
1 Like

That was it!

Many thanks for taking the time. Truly appreciate it!

1 Like