广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何在React中用Three.js加载glTF/glb模型并实现流畅交互控制?

在React项目里展示3D模型,目前最主流的做法是使用react-three-fiber配合Three.js。react-three-fiber是一个声明式的React渲染器,它把Three.js的场景对象映射成JSX组件,开发者不需要手动管理渲染循环、场景、相机等底层细节。但即便使用了react-three-fiber,模型加载、交互控制和资源清理仍然需要认真处理,否则页面很容易出现加载卡顿、内存增长甚至模型无法显示的问题。本文会从实际项目出发,带你一步步完成glTF/glb模型从加载到交互的完整流程。

如何在React中用Three.js加载glTF/glb模型并实现流畅交互控制?

一、项目初始化:原生Three.js还是react-three-fiber

在React中集成Three.js有两种常见方式。第一种是直接使用Three.js库,在useEffect里创建场景、相机、渲染器,手动编写动画循环并在组件卸载时清理资源。这种方式代码量较大,但控制力最强,适合复杂场景或需要深度定制的项目。第二种是使用react-three-fiber,它把Three.js的对象封装成React组件,允许用JSX来声明场景结构,同时自动处理渲染循环和基础的资源清理。对于大部分业务项目来说,react-three-fiber配合@react-three/drei提供的辅助组件能显著减少样板代码,开发效率更高。

如果你的项目只需要展示一个glTF/glb模型并加上基础的旋转缩放交互,强烈建议采用react-three-fiber + drei的组合。drei封装了大量常用功能,例如OrbitControls、useGLTF、Environment等,几乎不需要手动操作Three.js底层对象。初始化项目时可以使用以下命令创建Vite React工程并安装依赖:

npm create vite@latest react-3d-model -- --template react
cd react-3d-model
npm install three @react-three/fiber @react-three/drei

安装完成后,three是底层3D库,@react-three/fiber是React渲染器,@react-three/drei是工具集。三个包版本需要保持兼容,一般使用最新稳定版即可。接下来就可以在组件中创建<Canvas>画布,开始加载模型。

二、使用useLoader加载glTF/glb模型

react-three-fiber提供了useLoader这个hook来加载外部资源。对于glTF/glb格式,可以直接使用Three.js的GLTFLoader。下面是一个最小化的加载示例,模型文件放在项目public/models/目录下:

import { Canvas, useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { Suspense } from 'react'
function Model() {
  const gltf = useLoader(GLTFLoader, '/models/scene.glb')
  return <primitive object={gltf.scene} scale={0.8} />
}
function App() {
  return (
    <Canvas camera={{ position: [0, 2, 5], fov: 45 }}>
      <ambientLight intensity={0.6} />
      <directionalLight position={[5, 10, 5]} intensity={1} />
      <Suspense fallback={null}>
        <Model />
      </Suspense>
    </Canvas>
  )
}
export default App

useLoader返回一个包含scene属性的glTF对象,通过<primitive>组件可以把这个原生Object3D对象插入到react-three-fiber的场景图中。由于模型加载是异步操作,必须用Suspense包裹,否则加载期间会抛出Promise导致组件树渲染失败。fallback可以返回一个加载指示器,这里暂时用null表示不显示任何内容。

很多模型在导出时坐标原点并不在中心,或者缩放比例与场景不一致。加载后可以通过计算包围盒来居中模型。在useEffect中获取gltf.scene,使用Box3计算包围盒,然后调整模型位置和缩放,使其位于原点并适配相机视野。这个步骤对于不同来源的模型文件非常关键,否则可能出现模型偏移出屏幕或者尺寸过大过小的问题。

三、交互控制:OrbitControls实现旋转、缩放与平移

要实现用户通过鼠标或触摸旋转、缩放、平移模型,最方便的方式是使用drei提供的OrbitControls组件。把它加到<Canvas>内部即可自动绑定相机控制。下面是一个带有阻尼效果和距离限制的示例:

import { OrbitControls } from '@react-three/drei'
function App() {
  return (
    <Canvas>
      <ambientLight />
      <Model />
      <OrbitControls 
        enableDamping 
        dampingFactor={0.05}
        minDistance={2}
        maxDistance={10}
        maxPolarAngle={Math.PI / 2}
      />
    </Canvas>
  )
}

enableDamping开启惯性效果,让旋转停止时有一个平滑的减速过程。dampingFactor控制阻尼强度,值越小惯性越明显。minDistance和maxDistance用来限制相机与目标点的距离,防止用户把模型缩放过小或过大。maxPolarAngle限制垂直旋转角度,这里设置为Math.PI / 2表示只允许从水平面以上观察模型,避免视角穿到模型底部。

除了基础的轨道控制,实际业务里经常需要点击模型上的某个部件触发交互,比如高亮显示或弹出信息。这时可以在mesh或primitive上绑定onClick事件,使用event.stopPropagation()阻止事件穿透到背后的物体。再结合useState记录当前选中的对象,动态修改材质的emissive颜色或者显示一个提示面板,就能实现常见的点击交互效果。这类需求在商品展示和数字孪生项目中非常普遍。

四、性能优化与资源释放

React 18的StrictMode在开发模式下会故意双重挂载组件,用来帮助发现副作用问题。对于Three.js场景来说,这意味着useLoader可能会触发两次网络请求,导致模型被重复加载。解决方法是使用drei的useGLTF,它内部基于路径做了缓存,多次调用同一个URL时只会真正请求一次,后续直接返回缓存结果。代码写法如下:

import { useGLTF } from '@react-three/drei'
function Model({ url }) {
  const { scene } = useGLTF(url)
  return <primitive object={scene} />
}
useGLTF.preload('/models/scene.glb')

useGLTF不仅解决了重复加载问题,还支持preload方法提前把模型加载到缓存中,进一步提升切换速度。需要注意的是,缓存会一直保留在内存里,如果模型数量很多或者单个模型很大,需要根据业务场景决定是否手动清除缓存。

在组件卸载时,Three.js的几何体和材质不会自动从GPU内存中释放。如果页面反复挂载和卸载模型组件,显存占用会不断上升,最终导致页面卡顿甚至崩溃。正确的做法是在useEffect的清理函数里遍历场景,对每个mesh的geometry和material调用dispose方法:

import { useEffect } from 'react'
import { useGLTF } from '@react-three/drei'
function Model({ url }) {
  const { scene } = useGLTF(url)
  useEffect(() => {
    return () => {
      scene.traverse((obj) => {
        if (obj.isMesh) {
          obj.geometry.dispose()
          if (Array.isArray(obj.material)) {
            obj.material.forEach(m => m.dispose())
          } else {
            obj.material.dispose()
          }
        }
      })
    }
  }, [scene])
  return <primitive object={scene} />
}

这段代码会在组件卸载前遍历模型的所有子节点,对每一个网格释放几何体和材质资源。如果模型使用了纹理贴图,还需要注意纹理对象的dispose,避免纹理占用显存。通常材质dispose时会一并处理,但某些自定义材质可能需要单独释放。通过合理的资源管理,可以保证长时间运行的3D应用保持稳定。

掌握这些核心要点后,你就能在React中可靠地展示glTF/glb模型,并实现流畅的交互控制。无论是做商品3D预览、建筑可视化还是虚拟展厅,这套方案都能提供较好的性能和可维护性。

广告位 · 文章底部(后台-模块管理替换此区域)