Does my Code create a new Material all the time? (R3F)

Hi, quick question about R3F. Does this code always call new THREE.MeshStandardMaterial when declaring a meshStandardMaterial with the same texture?

<mesh
        castShadow
        receiveShadow
        geometry={nodes.zargeKantigRemodeledDichtungLinks.geometry}
        position={[-0.0255 - positionXDichtung, -0.0065, 0.4205 + positionZ]}
        scale={[1, skalierungsFaktorVertikal, 1]}>
          <meshStandardMaterial envMapIntensity={.2} map={oberflächeZarge.map}/>
      </mesh>
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.zargeKantigRemodeledVorneLinks.geometry}
        position={[-0.0255, -0.0065, 0.4205 + positionZ]}
        scale={[1, skalierungsFaktorVertikal, 1]}>
          <meshStandardMaterial envMapIntensity={.2} map={oberflächeZarge.map}/>
      </mesh>
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.zargeKantigRemodeledHintenMitte.geometry}
        position={[-0.1175 - positionXTiefe, 0.9895 + positionYOffsetHorizont, 0]}
        scale={[1, 1, skalierungsFaktorHorizont]}>
          <meshStandardMaterial envMapIntensity={.2} map={oberflächeZarge.map}/>
      </mesh>
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.zargeKantigRemodeledFutterbrettKanteMitte.geometry}
        position={[-0.1175 - positionXTiefeKante, 0.9895 + positionYOffsetHorizont, 0]}
        scale={[1, 1, skalierungsFaktorHorizont]}>
          <meshStandardMaterial envMapIntensity={.2} map={oberflächeZarge.map}/>
      </mesh>

Yes.

Each of your meshes have there own instance of a meshStandardMaterial. The map is shared though.

I check the uuid of objects to doublecheck if something is shared, or is its own instance, or was re-instanced for some reason.

E.g.,

import { Canvas, useLoader } from '@react-three/fiber'
import { Environment } from '@react-three/drei'
import { useThree } from '@react-three/fiber'
import { useEffect } from 'react'
import * as THREE from 'three'

function Boxes() {
  const { scene } = useThree()
  const texture = useLoader(THREE.TextureLoader, './img/grid.png')

  useEffect(() => {
    console.log(scene.children[0].material.uuid)
    console.log(scene.children[1].material.uuid)
    console.log(scene.children[2].material.uuid)
  }, [scene.children])

  return (
    <>
      <mesh position={[-1.2, 0, 0]}>
        <boxGeometry />
        <meshStandardMaterial map={texture} />
      </mesh>
      <mesh position={[0, 0, 0]}>
        <boxGeometry />
        <meshStandardMaterial map={texture} />
      </mesh>
      <mesh position={[1.2, 0, 0]}>
        <boxGeometry />
        <meshStandardMaterial map={texture} />
      </mesh>
    </>
  )
}

export default function App() {
  return (
    <Canvas camera={{ position: [0, 0, 3] }}>
      <Environment preset="forest" />
      <Boxes />
    </Canvas>
  )
}

The three box materials have there own uuid, despite the JSX being identical.


Below is the source code in the material constructor that generates a new uuid

1 Like

three dedupes shaders, the shader code is the cache key. you can confirm this using r3f-perf, it will show 1 shader.

import { Perf } from 'r3f-perf'

export default function App() {
  return (
    <Canvas>
      <Perf />

but there still will be three instances, im guessing this must have some kind of overhead still, so you could do this:

// In global space
const surface = new THREE.MeshStandardMaterial({ envMapIntensity: .2, map: oberflächeZarge.map })
...
<mesh material={surface} />
<mesh material={surface} />
<mesh material={surface} />

or

const [material, set] = useState()
return (
  <>
    <meshStandardMaterial ref={set} envMapIntensity={.2} map={oberflächeZarge.map}/>
    {material && (
      <>
        <mesh material={material} />
        <mesh material={material} />
        <mesh material={material} />
1 Like

people are scared to create new instances even when it does not matter :pensive: cant make up my mind if that’s good thing or bad thing.

does it matter? would love to have a final verdict somewhere. debug tools show 1 shader, but there are x classes with their own uuid’s, will they really all refer to the same and it’s something nobody needs to worry about?

Possibly yeah, draw calls on each material clone?

once upon a time there was heated discussion on github over a case where uuids were causing a major memory leak because of the way javascript stores strings internally (mrdoob responded that uuids are critical for people who wish to merge their jsons (I dont think I ever saw a single person who was doing that) and since every change that removed the leak also made uuids slower, the original code will stay) iirc this was the very same discussion that got @munrocket banned.

And regards to synchronising uuids among socket.io environments, does the same apply?

Sounds like drama, but odd that this doesn’t seem to be documented. there’s a little about the material cache key but not enough to know if it becomes a problem if you have multiple instances of the same shader material.

Rules of Optimization:
    Rule 1: Don't do it.
    Rule 2 (for experts only): Don't do it yet. 

– Michael A. Jackson

Thanks for the replies:) so if i’m declaring a THREE.MeshStandardMaterial once and apply it to every mesh they do not update if im changing the texture…
This is my Code right now thats working:

  const oberflächeZarge = useTexture({
    map: `./textures/zargeTexture/${OFL_ZA}.jpg`
  })

  oberflächeZarge.map.wrapS = oberflächeZarge.map.wrapT = THREE.RepeatWrapping

  oberflächeZarge.map.repeat.set(2, 2)

  useEffect(() => {
    oberflächeZarge.map.needsUpdate = true; // Force texture update
  }, [OFL_ZA]);
[...]
return(
<mesh
        castShadow
        receiveShadow
        geometry={nodes.zargeKantigRemodeledDichtungLinks.geometry}
        position={[-0.0255 - positionXDichtung, -0.0065, 0.4205 + positionZ]}
        scale={[1, skalierungsFaktorVertikal, 1]}>
          <meshStandardMaterial envMapIntensity={.2} map={oberflächeZarge.map}/>
      </mesh>
      <mesh
        castShadow
        receiveShadow
        geometry={nodes.zargeKantigRemodeledVorneLinks.geometry}
        position={[-0.0255, -0.0065, 0.4205 + positionZ]}
        scale={[1, skalierungsFaktorVertikal, 1]}>
          <meshStandardMaterial envMapIntensity={.2} map={oberflächeZarge.map}/>
      </mesh>
)

I have alot of meshes where i need the texture on so it would be the best way to declare it once. But as i mentioned the texture doesnt update with correct lighting. (OFL_ZA is a prop in my component)

this document describes all the constructs and properties that require a recompile, texture is one of them three.js docs

i think it requires needsUpdate on the material according to that document.

btw, something else, useEffect fires after react has rendered and after threejs has rendered out on the screen. this can result in flash of un-styled content (FOUC). useLayoutEffect fires after react but before threejs, it hasn’t rendered yet.

1 Like

I implemented it like this now but same problem, the texture looks washed out as it does not update.

const zargeMaterial = new THREE.MeshStandardMaterial()

export function ZargeKantig2({OFL_ZA, FALZ_ZA_TIEF, HOE_ZFM, BR_ZFM, TIEF_ZA, KAUS, FALZ_MASS}) {
  const { nodes, materials } = useGLTF("./models/TĂĽrblattStdGlatt/ZargeKantigMitDichtung.glb");

  const texture = new THREE.TextureLoader().load(`./textures/zargeTexture/${OFL_ZA}.jpg`);
  zargeMaterial.map = texture
  zargeMaterial.envMapIntensity = .2

  useLayoutEffect(() => {
    zargeMaterial.map.needsUpdate = true
  }, [OFL_ZA]);

return(
<mesh
        castShadow
        receiveShadow
        material={zargeMaterial}
        geometry={nodes.zargeKantigRemodeledDichtungLinks.geometry}
        position={[-0.0255 - positionXDichtung, -0.0065, 0.4205 + positionZ]}
        scale={[1, skalierungsFaktorVertikal, 1]}>
          {/* <meshStandardMaterial envMapIntensity={.2} map={oberflächeZarge.map}/> */}
      </mesh>
      <mesh
        castShadow
        receiveShadow
        material={zargeMaterial}
        geometry={nodes.zargeKantigRemodeledVorneLinks.geometry}
        position={[-0.0255, -0.0065, 0.4205 + positionZ]}
        scale={[1, skalierungsFaktorVertikal, 1]}>
          {/* <meshStandardMaterial envMapIntensity={.2} map={oberflächeZarge.map}/> */}
      </mesh>
)
}

i changed the r3f meshStandardMaterial to the THREE.MeshStandardMaterial in the material property. now it looks like this: (the lower left part is as i had it, not washed out. The frame around is implemented like the code above)
Screenshot 2023-07-06 100818

nothing odd, people who run into problems always write to github tickets, not to documentation files :sweat_smile: the issue about uuids is here if you want to read it. but the point is, even though a large number of js objects instances might not translate into a large number of webgl objects 3js creates, you can still run into other unrelated issue, so they cant give you the definite answer that you want.

probably not, since the complete string is coming over the socket. still an issue on the sending side tho.