Hi, My Question is bit more inclined towards Javascript than ThreeJS but both are related.
I’m making a demo where I dynamically change the texture on glb material based on scroll position. But the problem is whenever I’m changing the texture the screen goes blank for 1-2 seconds as the browser tries to fetch it on the go. Now I seen people using http caching and service worker cache API but I just don’t want to deal with cache invalidation issues and this demo shows that even with both the cache the file is not instantly accessed.
So my question is I’m using React three fiber so how can prefetch all the data upfront in useEffect or useLayoutEffect without caching them?
So far my observation is that once the file is being loaded, the screen doesn’t go blank and the texture can be swapped instantly without any issue and warning in the console. Due to this problem my console is telling me that I’m recalling ktx2loader multiple times when the browser tries to get the data.
there is a component for that in drei <Preload all /> this will force everything to be visible to a camera once and flush the textures to gpu, which is especially useful for scroll. by default three will first upload and process an object when it “sees” it eg if it’s in the rendering cameras frustum.
otherwise all loader hooks have preload functions, for instance useTexture.preload(“/foo.png”) in global space. keep in mind that this will not upload to the gpu, only fetch, the upload is the stressful part and should never happen runtime.
also make sure you wrap stuff into suspense. if you don’t everything that is within the same bound of something that loads gets unmounted. there’s also useTransition which retains the current thing until the new is ready, it won’t end up in a fallback.
useTexture.preload("/mountains.png")
useTexture.preload("/cities.png")
function Image({ url }) {
const texture = useTexture(url)
...
function Scene() {
const [url, setUrl] = useState("/mountains.png")
..
// this is safe
<SomeModel />
<Suspense fallback={<AnythingThatShowsUpWhileLoading />}>
// this will run into the fallback above when loading,
// but leaves everything else untouched
<Image url={url} />
</Suspense>
and with transitions
const [url, setUrl] = useState("/mountains.png")
const [isPending, startTransition] = useTransition()
...
useEffect(() => {
startTransition(() => setUrl("/cities.png")
// you can show pending state optionally
{isPending && <ComponentThatShowsPendingState />}
// this is safe
<SomeModel />
<Suspense fallback={<ThisWillOnlyShowOnFirstLoad />}>
// on first load this will run into the fallback above when loading,
// but leaves everything else untouched. when the url is changed
// the old image will persist until the new one has loaded
<Image url={url} />
</Suspense>
I’m making a demo where I dynamically change the texture on glb material based on scroll position
this could be a bad idea. the problem is texture upload, which is an expensive op. you have to look through stack overflow if it’s possible to do that pre-emptively.
So my confusion is currently I’m fetching all the path for topLayerTexture with useState which is causing this issue, should I ditch the useState and describe it in upfront as below
Update, after thinking it for a while, I finally got the solution
It turns out that I was doing it in a wrong way. I still need useState and the following code works perfectly without any glitch thanks to @drcmda for suggesting me for using useKTX2.preload()
right, useKTX2.preload(afg_hlg) is for global space, right in the module, not the component. this makes sure it starts to load (and parse!) right away. the GPU upload happens when three “sees” it, or using <Preload all /> but that would only work if there are meshes in the scene that do have the textures on them.
Hi drcmda, can you explain to me how i implement the transition between two textures? I’m not really getting the code you are providing. I am familiar with Suspense to set a fallback option. But the effect i want is that the texture stays the same until the other is loaded. I am using zustand to get global variables that control which texture gets loaded. So do i have to save the state before somehow and then change it when the new map is ready? Here is my component i want to use it in:
you can use useTransition or useDeferredValue to flag a component as pending. if it pends it will not go into the suspense fallback but hang on to the current result. you would use transitions if you control state yourself. if something like zustand, redux, leva etc holds state for you, then just defer the value.
hangs on to the current result on successive loads
you could show pending state by comparing url against deferred
PS, “UmgebungStore” is against convention. hooks should be lowercase and start with “use”. a linter would not let that pass. better rename to useUmgebungStore.
well it works for me. But it came with heavy performance issues when deploying it to others with slower maschines. I use this deferred value for about 16 different textures. Is this a problem?
suspense and defer have no relation to that. you have a texture, three renders it. you should generally consider using one and the same material for all these meshes if the texture matches but that’s a purely threejs related concern.
yes i use the same Material for all of them and just deferring the map. But good to know that useDeferred value is not the problem, i read a bit about it now. I’ll have to check my code then to fix this. I come back if i find the issue