You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在React Three Fiber中彻底销毁GLB模型及其关联动画与所有资源

Fixing Memory Leaks with GLTF Models in React Three Fiber After Route Changes

Alright, let's tackle this memory leak issue you're facing. The core problem here is that when your Office component unmounts (like when you switch routes), the Three.js resources tied to it—scene objects, skinned meshes, animation mixer, and all their dependencies—aren't being properly cleaned up, leaving them lingering in memory. Here's how to fix it step by step:

1. Properly Track the Animation Mixer with a Ref

First, stop creating a new AnimationMixer on every render. Use a useRef to hold the mixer instance so you can access it later for cleanup:

const mixerRef = useRef(null);

// Initialize mixer once when the component mounts
useEffect(() => {
  mixerRef.current = new THREE.AnimationMixer(scene);
  animations.forEach((clip) => {
    const action = mixerRef.current.clipAction(clip);
    action.play();
  });
  // Cleanup function runs on unmount
  return () => {
    // Stop all animations and destroy the mixer
    mixerRef.current?.stopAllAction();
    mixerRef.current?.uncacheRoot(scene);
    mixerRef.current = null;
  };
}, [scene, animations]);

2. Prevent Stale useFrame Updates

Your useFrame callback keeps running even after the component unmounts if you don't flag it to stop. Add a mounted state ref to handle this:

const isMountedRef = useRef(true);

useFrame((state, delta) => {
  if (isMountedRef.current && mixerRef.current) {
    mixerRef.current.update(delta);
  }
});

// Add this to your component's cleanup logic
useEffect(() => {
  return () => {
    isMountedRef.current = false;
  };
}, []);

3. Recursively Clean Up All Three.js Resources

Three.js objects like geometries, materials, and textures hold GPU memory that won't be released unless you explicitly call dispose(). Create a helper function to traverse your scene and clean every resource:

const disposeSceneResources = (obj) => {
  // Clean geometry
  if (obj.geometry) obj.geometry.dispose();
  // Clean materials (handle arrays for multi-material objects)
  if (obj.material) {
    Array.isArray(obj.material) 
      ? obj.material.forEach(material => material.dispose()) 
      : obj.material.dispose();
  }
  // Clean textures if present
  if (obj.texture) obj.texture.dispose();
  // Recurse through child objects
  obj.children.forEach(child => disposeSceneResources(child));
};

Call this function in your component's cleanup useEffect to wipe all resources when the component unmounts:

useEffect(() => {
  return () => {
    disposeSceneResources(scene);
    // Remove the entire scene from the renderer's active scene graph
    const { scene: threeScene } = useThree.getState();
    threeScene.remove(scene);
  };
}, [scene]);

4. Modified Full Component Code

Putting it all together, here's your updated Office component with complete cleanup logic:

import { useGLTF, useFrame, useThree } from '@react-three/fiber';
import * as THREE from 'three';
import { useEffect, useRef } from 'react';

const Office = (props) => {
  const { scene, nodes, materials, animations } = useGLTF('/studio.glb');
  const mixerRef = useRef(null);
  const isMountedRef = useRef(true);
  const group = useRef();

  // Camera setup (unchanged)
  useThree(({ camera }) => {
    camera.position.set(xPos, yPos, 7);
    camera.rotation.set(0.1, -0.75, 0.08);
    camera.fov = 35;
    camera.updateProjectionMatrix();
  });

  // Animation mixer initialization + full cleanup
  useEffect(() => {
    mixerRef.current = new THREE.AnimationMixer(scene);
    animations.forEach((clip) => {
      const action = mixerRef.current.clipAction(clip);
      action.play();
    });

    return () => {
      // Stop animations and clean mixer
      mixerRef.current?.stopAllAction();
      mixerRef.current?.uncacheRoot(scene);
      mixerRef.current = null;
      // Mark component as unmounted
      isMountedRef.current = false;
      // Dispose all scene resources
      disposeSceneResources(scene);
      // Detach scene from renderer
      const { scene: threeScene } = useThree.getState();
      threeScene.remove(scene);
    };
  }, [scene, animations]);

  // Update mixer only if component is active
  useFrame((state, delta) => {
    if (isMountedRef.current && mixerRef.current) {
      mixerRef.current.update(delta);
    }
  });

  // Resource disposal helper
  const disposeSceneResources = (obj) => {
    if (obj.geometry) obj.geometry.dispose();
    if (obj.material) {
      Array.isArray(obj.material) 
        ? obj.material.forEach(m => m.dispose()) 
        : obj.material.dispose();
    }
    if (obj.texture) obj.texture.dispose();
    obj.children.forEach(disposeSceneResources);
  };

  return (
    <group ref={group} dispose={null}>
      <group rotation={[ Math.PI / 2, 0, 0 ]} scale={[ 0.01, 0.01, 0.01 ]}>
        <group name="Character">
          <primitive object={nodes.pasted__Hips} />
          <skinnedMesh geometry={nodes.pasted__MocapGuy_Caruncula.geometry} />
          {/* Rest of your mesh components */}
        </group>
      </group>
    </group>
  );
}

useGLTF.preload('/studio.glb'); // Preload for smoother navigation

Key Takeaways

  • Explicit disposal is non-negotiable for Three.js GPU resources—they won't get garbage collected automatically.
  • Stopping the mixer prevents orphaned animation updates and clears cached animation data.
  • Detaching the scene from the renderer's active graph ensures no leftover objects are rendered or held in memory.

This setup will fully clean up all 3D resources when the component unmounts, eliminating memory leaks when navigating back and forth with React Router.

内容的提问来源于stack exchange,提问作者Rafael

火山引擎 最新活动