React Three Fiber 3D 渲染
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,是场景中的可视物体。boxGeometry和meshStandardMaterial会被自动附加到 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 状态变化可能导致不必要的重新渲染。可以优先使用 ref 和 useFrame 直接操作对象属性,避免触发 React 渲染循环。仅在需要完全改变结构时才使用状态。
按需加载与 Suspense
R3F 和 drei 均支持 React 的 Suspense,大型模型、纹理等可以异步加载并显示 fallback:
<Suspense fallback={<LoadingSpinner />}>
<Model />
</Suspense>
同时 Canvas 的 mode 属性设为 concurrent(React 18+)还能启用并发特性。
后期处理与特效
drei 提供了 EffectComposer、Bloom、Outline 等组件,实现高级视觉效果。
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