threejs-r3f
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseThree.js & React Three Fiber
Three.js & React Three Fiber
3D on the web. Three.js is the engine, R3F is the React renderer. Concise rules here. Deep-dive in.references/
网页端3D开发。Three.js是核心引擎,R3F是React渲染器。 此处为简明规则。详细内容请查看目录。references/
When to Use What
工具选择指南
| Need | Tool | Why |
|---|---|---|
| Full 3D scene (models, lights, physics) | R3F + drei | Declarative, React-friendly, ecosystem |
| Vanilla 3D (no React) | Three.js direct | Lighter, no React overhead |
| Simple 3D transforms on UI | CSS | GPU-composited, no WebGL context |
| 2D particles / generative | Canvas 2D | Simpler API, less GPU overhead |
| Shader-only visuals (no scene graph) | Raw WebGL / ShaderMaterial | Maximum control, minimal abstraction |
| 需求 | 工具 | 原因 |
|---|---|---|
| 完整3D场景(模型、灯光、物理效果) | R3F + drei | 声明式语法、适配React、生态完善 |
| 原生3D开发(不使用React) | 直接使用Three.js | 更轻量化,无React性能开销 |
| 对UI进行简单3D变换 | CSS | GPU合成渲染,无需WebGL上下文 |
| 2D粒子/生成式效果 | Canvas 2D | API更简单,GPU开销更低 |
| 仅着色器视觉效果(无场景图) | 原生WebGL / ShaderMaterial | 控制度最高,抽象层最少 |
Scene Setup Patterns
场景搭建模式
tsx
import { Canvas } from '@react-three/fiber'
import { Environment, OrbitControls } from '@react-three/drei'
import { Suspense } from 'react'
<Canvas camera={{ position: [0, 2, 5], fov: 45 }} dpr={[1, 2]} gl={{ antialias: true }}>
<Suspense fallback={null}>
<Environment preset="studio" />
<OrbitControls makeDefault />
<Scene />
</Suspense>
</Canvas>Rules:
- Always wrap scene content in -- loaders (GLTF, textures, HDRI) need it
<Suspense> - Set to clamp pixel ratio (Retina without melting GPUs)
dpr={[1, 2]} - Keep the Canvas parent component minimal -- re-renders propagate into the scene
tsx
import { Canvas } from '@react-three/fiber'
import { Environment, OrbitControls } from '@react-three/drei'
import { Suspense } from 'react'
<Canvas camera={{ position: [0, 2, 5], fov: 45 }} dpr={[1, 2]} gl={{ antialias: true }}>
<Suspense fallback={null}>
<Environment preset="studio" />
<OrbitControls makeDefault />
<Scene />
</Suspense>
</Canvas>规则:
- 始终用包裹场景内容——加载器(GLTF、纹理、HDRI)需要它
<Suspense> - 设置来限制像素比(在Retina屏幕上避免GPU过载)
dpr={[1, 2]} - 保持Canvas父组件尽可能简洁——父组件重渲染会传递到场景中
R3F Hooks
R3F钩子函数
| Hook | Purpose | Gotcha |
|---|---|---|
| Per-frame logic (animation, physics) | Never setState inside |
| Access gl, scene, camera, size, viewport, pointer | Destructure only what you need |
| Load any Three.js resource | Wrap parent in Suspense |
| Extract nodes/materials from loaded scene | Useful after useGLTF |
| 钩子 | 用途 | 注意事项 |
|---|---|---|
| 逐帧逻辑处理(动画、物理效果) | 切勿在内部调用setState |
| 访问gl、场景、相机、尺寸、视口、指针 | 仅解构所需内容 |
| 加载任意Three.js资源 | 父组件需包裹在Suspense中 |
| 从加载的场景中提取节点/材质 | 在useGLTF之后使用更实用 |
useFrame Tips
useFrame使用技巧
tsx
useFrame((state, delta) => {
// Use delta for framerate-independent animation
meshRef.current.rotation.y += delta * 0.5
// Access clock for time-based effects
material.uniforms.uTime.value = state.clock.elapsedTime
})tsx
useFrame((state, delta) => {
// 使用delta实现帧率无关的动画
meshRef.current.rotation.y += delta * 0.5
// 访问clock实现基于时间的特效
material.uniforms.uTime.value = state.clock.elapsedTime
})Drei Essentials
Drei核心组件
| Component | Use Case |
|---|---|
| HDRI lighting (presets: studio, sunset, city, forest, dawn) |
| Idle floating animation (speed, rotationIntensity, floatIntensity) |
| Extruded 3D text (needs JSON font from Facetype.js) |
| Load .glb/.gltf models (returns { nodes, materials, scene }) |
| Preload model before component mounts |
| Glass/crystal/liquid refraction effects |
| Drag-to-rotate for product showcases |
| Auto-center any group of meshes |
| LOD -- swap geometry by camera distance |
| Load textures with Suspense support |
| Declarative instancing for repeated meshes |
| 组件 | 使用场景 |
|---|---|
| HDRI光照(预设:工作室、日落、城市、森林、黎明) |
| 闲置漂浮动画(可配置速度、旋转强度、漂浮强度) |
| 挤压式3D文字(需要Facetype.js生成的JSON字体) |
| 加载.glb/.gltf模型(返回{ nodes, materials, scene }) |
| 在组件挂载前预加载模型 |
| 玻璃/水晶/液体折射效果 |
| 产品展示的拖拽旋转功能 |
| 自动居中任意网格组 |
| LOD(细节层次)——根据相机距离切换几何体 |
| 支持Suspense的纹理加载 |
| 重复网格的声明式实例化 |
Postprocessing
后期处理
tsx
import { EffectComposer, Bloom, ChromaticAberration } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'
<EffectComposer>
<Bloom
luminanceThreshold={1}
luminanceSmoothing={0.4}
intensity={0.6}
/>
<ChromaticAberration
blendFunction={BlendFunction.NORMAL}
offset={[0.002, 0.002]}
/>
</EffectComposer>Rules:
- Bloom is selective by default -- lift material color/emissive above 1.0 to make it glow
- = nothing glows unless explicitly emissive
luminanceThreshold={1} - Order matters inside EffectComposer
- Effects are merged into a single pass (performant by design)
tsx
import { EffectComposer, Bloom, ChromaticAberration } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'
<EffectComposer>
<Bloom
luminanceThreshold={1}
luminanceSmoothing={0.4}
intensity={0.6}
/>
<ChromaticAberration
blendFunction={BlendFunction.NORMAL}
offset={[0.002, 0.002]}
/>
</EffectComposer>规则:
- Bloom默认是选择性发光——将材质颜色/自发光值提升至1.0以上即可实现发光效果
- = 只有显式设置自发光的对象才会发光
luminanceThreshold={1} - EffectComposer内部的组件顺序很重要
- 特效会合并为单次渲染通道(设计上保证高性能)
Performance Patterns
性能优化方案
| Pattern | When |
|---|---|
| 100+ identical meshes (particles, trees, crowds) |
| LOD: swap hi/lo models by distance |
| Prevent auto-dispose when reusing shared geometry |
| Compress .glb models (70-90% size reduction) |
| Compressed GPU textures (1/4 VRAM) |
| Only render when something changes (static scenes) |
| Trigger a render in demand mode |
Offscreen canvas ( | Run rendering off main thread |
Target metrics: < 100 draw calls, < 1M triangles, 60fps on mid-range GPU.
Use or to monitor.
stats-glr3f-perf| 方案 | 适用场景 |
|---|---|
| 100个以上相同网格(粒子、树木、人群) |
| LOD:根据距离切换高低精度模型 |
在 | 复用共享几何体时防止自动释放 |
| 压缩.glb模型(体积减少70-90%) |
| 压缩GPU纹理(显存占用降至1/4) |
在Canvas上设置 | 仅在内容变化时渲染(静态场景) |
从useThree调用 | 在按需模式下触发渲染 |
离屏画布( | 在主线程外运行渲染逻辑 |
目标指标: < 100次绘制调用,< 100万个三角形,中端GPU上达到60fps。使用或进行监控。
stats-glr3f-perfDo Not
禁忌事项
1. Never setState in useFrame
1. 切勿在useFrame中调用setState
Causes full React re-render 60x/second. Mutate refs directly.
tsx
// BAD
useFrame(() => {
setRotation(prev => prev + 0.01) // React re-render every frame
})
// GOOD
useFrame((_, delta) => {
meshRef.current.rotation.y += delta * 0.5 // Direct mutation, zero re-renders
})这会导致React每秒重渲染60次。直接修改ref即可。
tsx
// 错误示例
useFrame(() => {
setRotation(prev => prev + 0.01) // 每帧触发React重渲染
})
// 正确示例
useFrame((_, delta) => {
meshRef.current.rotation.y += delta * 0.5 // 直接修改,无重渲染开销
})2. Never allocate in the render loop
2. 切勿在渲染循环中分配内存
new Vector3()tsx
// BAD
useFrame((state) => {
const target = new THREE.Vector3(0, Math.sin(state.clock.elapsedTime), 0)
meshRef.current.position.copy(target)
})
// GOOD
const _target = useMemo(() => new THREE.Vector3(), [])
useFrame((state) => {
_target.set(0, Math.sin(state.clock.elapsedTime), 0)
meshRef.current.position.copy(_target)
})每帧创建会导致GC峰值,造成卡顿。
new Vector3()tsx
// 错误示例
useFrame((state) => {
const target = new THREE.Vector3(0, Math.sin(state.clock.elapsedTime), 0)
meshRef.current.position.copy(target)
})
// 正确示例
const _target = useMemo(() => new THREE.Vector3(), [])
useFrame((state) => {
_target.set(0, Math.sin(state.clock.elapsedTime), 0)
meshRef.current.position.copy(_target)
})3. Never forget dispose (memory leak)
3. 切勿忘记释放资源(内存泄漏)
Three.js textures, geometries, and materials live on the GPU. Unmounting a React component does NOT free them.
tsx
// BAD -- texture stays in VRAM after unmount
const texture = useLoader(TextureLoader, '/big-texture.jpg')
// GOOD -- R3F auto-disposes when using JSX primitives
// For manual resources, dispose in cleanup:
useEffect(() => {
return () => {
texture.dispose()
geometry.dispose()
material.dispose()
}
}, [])Three.js的纹理、几何体和材质存储在GPU中。卸载React组件并不会自动释放这些资源。
tsx
// 错误示例 -- 组件卸载后纹理仍占用显存
const texture = useLoader(TextureLoader, '/big-texture.jpg')
// 正确示例 -- 使用JSX原语时R3F会自动释放资源
// 对于手动管理的资源,在清理函数中释放:
useEffect(() => {
return () => {
texture.dispose()
geometry.dispose()
material.dispose()
}
}, [])4. Never re-render the Canvas parent
4. 切勿让Canvas父组件重渲染
State changes in the parent force the entire Canvas to remount = flash, lost state, reloaded assets.
tsx
// BAD
function App() {
const [uiState, setUiState] = useState(false) // re-renders remount Canvas
return (
<>
<button onClick={() => setUiState(!uiState)}>Toggle</button>
<Canvas><Scene config={uiState} /></Canvas>
</>
)
}
// GOOD -- isolate Canvas in its own component
function App() {
return (
<>
<UI />
<SceneCanvas />
</>
)
}父组件的状态变化会强制整个Canvas重新挂载,导致画面闪烁、状态丢失、资源重新加载。
tsx
// 错误示例
function App() {
const [uiState, setUiState] = useState(false) // 重渲染会触发Canvas重新挂载
return (
<>
<button onClick={() => setUiState(!uiState)}>切换</button>
<Canvas><Scene config={uiState} /></Canvas>
</>
)
}
// 正确示例 -- 将Canvas隔离到独立组件中
function App() {
return (
<>
<UI />
<SceneCanvas />
</>
)
}5. Never load assets without Suspense
5. 切勿在无Suspense的情况下加载资源
Loaders (useGLTF, useTexture, useLoader) throw promises. Without Suspense, you get crashes.
tsx
// BAD
<Canvas>
<Model /> {/* useGLTF inside -- will throw */}
</Canvas>
// GOOD
<Canvas>
<Suspense fallback={<Loader />}>
<Model />
</Suspense>
</Canvas>加载器(useGLTF、useTexture、useLoader)会抛出Promise。没有Suspense会导致崩溃。
tsx
// 错误示例
<Canvas>
<Model /> {/* 内部使用useGLTF -- 会抛出错误 */}
</Canvas>
// 正确示例
<Canvas>
<Suspense fallback={<Loader />}>
<Model />
</Suspense>
</Canvas>Quick Reference: Loading Sub-resources
快速参考:加载子资源
| Need | Load |
|---|---|
| Scene boilerplate, lighting rigs, controls | |
| Custom shaders, GLSL patterns, uniforms | |
| Animation principles, easing, timing | |
| GSAP + Three.js integration | |
| 需求 | 加载路径 |
|---|---|
| 场景模板、灯光系统、控制器 | |
| 自定义着色器、GLSL模式、uniforms | |
| 动画原理、缓动、时序 | |
| GSAP + Three.js集成 | |