Texture disposal in R3F

Hi all,

i struggle with texture disposing for a while now… I tried it on my own with the documentation of three.js and r3f, unfortunatly i still have no working solution. For context, i’m building a configurator app where textures change alot. I implemented textures with different resolutions and also the case to handle hex input instead of textures. Here is a sandbox that makes it more clear:

Currently i tried to achieve the disposal with saving the previous selection of the user and use the useTexture.clear() Method to free the texture. For my understanding i also have to dispose the texture now but how? Also if you would implement this function differently i’m open for improvement suggestions.

Greets Tom

first of all, hooks can’t be conditional. a component must return the exact order of hooks every time, there can’t be any if then else including or excluding hooks conditionally.

as for useTexture, that hook is made to cache textures, so that they are re-usable throughout the app. this is likely not what you want. you can of course clear them from the cache, but this doesn’t dispose them, you dispose it via texture.dispose().

i would suggest you just use vanilla textures whose lifecycle you track yourself, you don’t need a cache if you don’t re-use, and working with a cache that you invalidate all the time is pointless.

const loader = new THREE.TextureLoader()

function Foo({ url }) {
  const texture = useMemo(() => loader.load(url), [url])
  useEffect(() => {
    return () => texture.dispose()
  }, [texture])

keep in mind that if that component switches from a.jpg to b.jpg back to a.jpg it will process a.jpg again. this is what useTexture avoids.

Thanks for the informative answer, i changed my code to match yours and it works with textures. Problem is when i only want a color without a texture the last selected texture stays on the object. Do i have to dispose it from the mesh aswell?

Okay i changed it to this code, now textures and hex values are supported:

Is this the way you would implement this feature? The CubeTexture Component in my Project is getting used in alot of other components so i want it to work as good as it gets:)
Edit: I tested switching textures and it doesnt seem that the texture has to be loaded again :melting_face: is it really disposing?

it’s fine

export function CubeTextureMaterial({ OFL_TB }) {
  const AFLG = useTürenStore((state) => state.AFLG)
  const isColor = OFL_TB.startsWith('#')
  const texture = isColor ? 'white' : OFL_TB
  const color = isColor ? OFL_TB : 'white'
  const map = useMemo(() => loader.load(`./textures/${AFLG}/${texture}.jpg`), [AFLG, texture])
  useEffect(() => () => map.dispose(), [map])
  return <meshStandardMaterial envMapIntensity={0.1} map={map} color={color} roughness={0.3} toneMapped={false} />

TürenStore((state) => state.AFLG) is a bit weird. all hooks start with “use”, this would be flagged by a linter. a white texture is a good idea btw because you can’t go from a texture to no texture in threejs, you would have to re-compile the material. as for hook dependencies, they’re supposed to indicate when a hook should run, if you throw something like ./textures/${AFLG}/${texture}.jpg in the dependencies array, and then use it again in the function it would bloat the bundle, the full string as a dep is unnecessary.

:thinking: Setting material.map = null removes a texture, see “remove texture” button when model and texture are loaded…

Thanks for the help!! I struggled a while with this topic

not according to documentation three.js docs

The following properties can't be easily changed at runtime
(once the material is rendered at least once):

numbers and types of uniforms
presence or not of
  vertex colors
  shadow map
  alpha test

i’ve tried it as well and it seems to behave like the docs say. i don’t know about the codepen, perhaps it’s re-creating the materials somehow.

tried it out quickly festive-smoke-sjrtdy - CodeSandbox just changing it to null has no affect. im logging the material out and map is null, texture stays present nonetheless.

Interesting, it’s always worked without fail for me personally :thinking:

Nope it’s just setting the material.map to null on click

1 Like

very curious. i tried with a vanilla example doing the same, click it map goes to null, texture remains. Threejs basic example (forked) - CodeSandbox maybe the codepen is tripping something else up. the reason why you can’t remove a texture without re-compile is that the texture is uploaded to the gpu only once, this happens in gl.initTexture.

1 Like

Wild, no idea what that’s about, the pen is doing the same, loading model and texture, assigning the texture to map of material, initializing the scene, rendering and then adding an event listener for the button… :thinking: This has always worked with runtime js for me as I say, maybe bundler is latching / caching the material map somehow?

1 Like

It doesn’t work in the codepen you linked @drcmda , but it’s working for me in glitch.

Tested both .map =null and .alphaMap=null

Texture goes away.

I also tried .alphaTest = .5 and that worked too! The docs lie!

linking with latest three off of threejs.org

(Click listener is at the bottom of script.js)

Super weird!

@Mugen87 Any insights :smiley: ?

.map and .alphaMap don’t have a setter… so whatever mechanism is there is polling based…

1 Like