React Three Fiber 3D 渲染

FreeGuideOnline 最新 2026-07-10

bash npm create vite@latest my-r3f-app -- --template react cd my-r3f-app npm install three @react-three/fiber @react-three/drei


- `three`:底层 3D 引擎
- `@react-three/fiber`:React 渲染器
- `@react-three/drei`:实用工具集(包含各种辅助组件、材质、控制器等)

### 渲染第一个 3D 场景

将 `src/App.jsx` 替换为以下代码:

```jsx
import { Canvas } from '@react-three/fiber'

function Box() {
  return (
    <mesh>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="hotpink" />
    </mesh>
  )
}

export default function App() {
  return (
    <Canvas>
      <ambientLight intensity={0.5} />
      <pointLight position={[10, 10, 10]} />
      <Box />
    </Canvas>
  )
}

要点解释

  • <Canvas> 是 R3F 的根组件,它创造了一个全屏的 3D 视口,并自动处理 WebGL 上下文、相机、渲染循环等。
  • <mesh> 对应 Three.js 中的 THREE.Mesh,是场景中的可视物体。
  • boxGeometrymeshStandardMaterial 会被自动附加到 mesh 上,无需手动 .add()

运行 npm run dev,你会看到一个粉色立方体位于屏幕中央。

R3F 核心概念

组件即对象

R3F 中的每个组件都对应一个 Three.js 类。例如 <mesh> 创建 THREE.Mesh<directionalLight> 创建 THREE.DirectionalLight。组件属性会被转换为构造函数的参数或对象的属性。

<mesh position={[0, 1, 0]} rotation={[Math.PI / 4, 0, 0]}>
  ...
</mesh>

等价于:

const mesh = new THREE.Mesh()
mesh.position.set(0, 1, 0)
mesh.rotation.set(Math.PI / 4, 0, 0)

生命周期与自动清理

R3F 会跟踪所有 3D 对象、几何体、材质、纹理等的创建与销毁。当组件卸载时,相关的 GPU 资源会被自动释放,这从根本上避免了内存泄漏问题。

事件系统

你可以像处理 DOM 事件一样为 3D 对象绑定交互:

<mesh
  onClick={() => console.log('clicked')}
  onPointerOver={() => console.log('hover')}
  onPointerOut={() => console.log('out')}
>
  ...
</mesh>

这些事件背后由 raycaster 射线检测实现,即使物体移动或旋转也能正确命中。

构建一个动态场景

添加多个几何体和材质

import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'

function Scene() {
  return (
    <>
      <mesh position={[-2, 0, 0]}>
        <sphereGeometry args={[0.7, 32, 32]} />
        <meshStandardMaterial color="orange" />
      </mesh>

      <mesh position={[0, 0, 0]}>
        <torusKnotGeometry args={[0.5, 0.2, 100, 16]} />
        <meshStandardMaterial color="mediumpurple" roughness={0.3} metalness={0.8} />
      </mesh>

      <mesh position={[2, 0, 0]}>
        <coneGeometry args={[0.7, 1, 32]} />
        <meshStandardMaterial color="skyblue" />
      </mesh>
    </>
  )
}

export default function App() {
  return (
    <Canvas camera={{ position: [0, 0, 5] }}>
      <ambientLight intensity={0.4} />
      <directionalLight position={[5, 5, 5]} intensity={0.8} />
      <Scene />
      <OrbitControls />
    </Canvas>
  )
}

我们引入了 OrbitControls(来自 drei),它允许用户拖拽旋转、缩放场景。camera 属性设置了初始相机位置。

使用 Ref 控制变换

React 中通过 useRef 可以获取 Three.js 对象的引用,实现动画或交互控制。

import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'

function RotatingBox() {
  const meshRef = useRef()

  useFrame((state, delta) => {
    meshRef.current.rotation.x += delta * 0.5
    meshRef.current.rotation.y += delta * 0.3
  })

  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="tomato" />
    </mesh>
  )
}
  • useFrame 会在每帧渲染前调用,delta 是距离上一帧的时间差,用于确保动画与帧率无关。
  • state 包含时钟、相机等信息。

光照与环境映射

真实感渲染离不开好的光照。除了环境光和方向光,你还可以使用:

<ambientLight intensity={0.2} />
<directionalLight castShadow position={[5, 10, 5]} intensity={0.8} />
<pointLight position={[-5, 5, 5]} intensity={0.5} />
<Environment preset="sunset" />  // 来自 drei,提供基于 HDR 的环境贴图

要让物体投射和接受阴影,需配置阴影映射:

<Canvas shadows>
  ...
  <mesh castShadow receiveShadow>
    ...
  </mesh>
  <directionalLight
    castShadow
    shadow-mapSize-width={1024}
    shadow-mapSize-height={1024}
  />
  <mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]} position={[0, -1, 0]}>
    <planeGeometry args={[10, 10]} />
    <shadowMaterial transparent opacity={0.4} />
  </mesh>
</Canvas>

加载 3D 模型

使用 useGLTF 加载 .glb 文件

drei 提供了 useGLTF 钩子,可以异步加载 GLTF 模型并缓存:

import { useGLTF } from '@react-three/drei'

function Model() {
  const { scene } = useGLTF('/path/to/model.glb')
  return <primitive object={scene} scale={0.8} />
}

<primitive> 用于将原生 Three.js 对象(这里是整个场景)挂载到 R3F 中。

你也可以直接使用 Gltf 组件替代:

import { Gltf } from '@react-three/drei'
function Model() { return <Gltf src="/model.glb" scale={0.8} /> }

处理模型动画

如果模型包含动画:

import { useAnimations } from '@react-three/drei'

function AnimatedModel() {
  const { scene, animations } = useGLTF('/animated.glb')
  const { actions, mixer } = useAnimations(animations, scene)

  useEffect(() => {
    actions['Idle']?.play()
  }, [actions])

  return <primitive object={scene} />
}

交互与事件进阶

可拖拽物体

利用 drei 的 DragControls 让用户直接拖拽物体:

import { DragControls } from '@react-three/drei'
import { useThree } from '@react-three/fiber'

function DraggableMeshes() {
  const { camera, gl } = useThree()
  return (
    <DragControls>
      <mesh position={[-1, 0, 0]}>
        <boxGeometry />
        <meshStandardMaterial color="red" />
      </mesh>
      <mesh position={[1, 0, 0]}>
        <sphereGeometry />
        <meshStandardMaterial color="blue" />
      </mesh>
    </DragControls>
  )
}

高亮显示与光标样式

通过指针事件改变材质或外观:

function HoverableMesh() {
  const [hover, setHover] = useState(false)
  return (
    <mesh
      onPointerOver={() => setHover(true)}
      onPointerOut={() => setHover(false)}
    >
      <boxGeometry />
      <meshStandardMaterial color={hover ? 'yellow' : 'gray'} />
    </mesh>
  )
}

全局设置光标样式可以这样:

<Canvas
  onPointerMissed={() => (document.body.style.cursor = 'auto')}
>
  {/* 物体内部的事件会自行处理 */}
</Canvas>

性能优化最佳实践

复用几何体和材质

相同的几何体和材质应该共享同一个实例,避免 GPU 内存浪费。利用 React 的 useMemo 或 drei 的 useFBO/useTexture 缓存:

const geometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), [])
const material = useMemo(() => new THREE.MeshStandardMaterial({ color: 'teal' }), [])

return (
  <>
    <mesh geometry={geometry} material={material} position={[-2, 0, 0]} />
    <mesh geometry={geometry} material={material} position={[2, 0, 0]} />
  </>
)

实例化渲染(Instancing)

对于大量重复物体,使用 InstancedMesh 可将成千上万个对象在一次绘制调用中完成:

import { useRef, useMemo } from 'react'
import * as THREE from 'three'

function Instances({ count = 1000 }) {
  const meshRef = useRef()
  const dummy = useMemo(() => new THREE.Object3D(), [])

  useMemo(() => {
    for (let i = 0; i < count; i++) {
      dummy.position.set(
        (Math.random() - 0.5) * 10,
        (Math.random() - 0.5) * 10,
        (Math.random() - 0.5) * 10
      )
      dummy.updateMatrix()
      meshRef.current.setMatrixAt(i, dummy.matrix)
    }
    meshRef.current.instanceMatrix.needsUpdate = true
  }, [count, dummy])

  return (
    <instancedMesh ref={meshRef} args={[null, null, count]}>
      <boxGeometry />
      <meshStandardMaterial />
    </instancedMesh>
  )
}

合理使用 React 状态

3D 场景的更新不同于 DOM,频繁的 React 状态变化可能导致不必要的重新渲染。可以优先使用 refuseFrame 直接操作对象属性,避免触发 React 渲染循环。仅在需要完全改变结构时才使用状态。

按需加载与 Suspense

R3F 和 drei 均支持 React 的 Suspense,大型模型、纹理等可以异步加载并显示 fallback:

<Suspense fallback={<LoadingSpinner />}>
  <Model />
</Suspense>

同时 Canvasmode 属性设为 concurrent(React 18+)还能启用并发特性。

后期处理与特效

drei 提供了 EffectComposerBloomOutline 等组件,实现高级视觉效果。

import { EffectComposer, Bloom, Outline } from '@react-three/postprocessing'

function Effects() {
  return (
    <EffectComposer>
      <Bloom luminanceThreshold={0.3} luminanceSmoothing={0.9} intensity={1.5} />
    </EffectComposer>
  )
}

// 需要高亮物体的 outline
function HighlightedMesh() {
  return (
    <mesh>
      <boxGeometry />
      <meshStandardMaterial />
      <Outline blur edgeStrength={10} />
    </mesh>
  )
}

<Effects /> 放入 Canvas 内即可。

完整的示例项目结构

推荐的项目结构:

src/
  components/     # 可复用的 3D 组件
  assets/         # 模型、纹理等资源
  scenes/         # 组织场景逻辑的组件
  App.jsx
  main.jsx