关于 threejs-r3f
面向 Three.js 和 React Three Fiber 的创意编码技能,提供场景、资源加载、动画与性能优化指导。适合构建 Web 3D 内容的开发者,涵盖 React 与非 React 场景的技术选择。
主要功能
- 01Three.js 与 R3F 场景搭建
- 02Drei 组件与资源加载
- 03动画循环与 Hook 用法
- 04模型及纹理优化
- 05GPU 资源清理建议
适用场景
- 01为 React 项目搭建 3D 场景
- 02加载并展示 GLTF 模型
- 03优化静态或大型 3D 场景
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ athevon/ genjutsu --skill threejs-r3f安装后在 Claude Code 中输入/threejs-r3f确认已加载。
详细介绍
版本敏感。 以下所有 API 名称、SDK 门槛和浏览器支持声明均已于 2026-09-08 对照一手资料核实。核对了哪些内容以及核对时间,见
_jutsu/VERSIONS.md。如果该日期已久,请在依据版本号采取行动前重新核实。
Three.js 与 React Three Fiber
Web 端 3D。Three.js 是引擎,R3F 是 React 渲染器。 此处提供简明规则。深入内容见
references/。版本:
threer185(0.185.x)·@react-three/fiber9.x ·@react-three/drei10.x ·@react-three/postprocessing3.x。 R3F 9 仅支持 React 19——其声明的 peer 范围为react/react-dom>=19 <19.3(drei 10 要求^19)。React 18 项目必须继续使用 R3F 8 + drei 9。搭建项目骨架前,请检查已安装的 React 主版本。 渲染器:WebGLRenderer仍是 R3F 的默认渲染器。WebGPURenderer+ TSL 已发布(未标记为实验性),并会在不支持时自动回退到 WebGL 2;但 Three.js 文档称其为“WebGLRenderer的新替代方案”,而非默认选项。通过异步gl回调启用:import * as THREE from 'three/webgpu' <Canvas gl={async (props) => { const r = new THREE.WebGPURenderer(props as any); await r.init(); return r }} />
何时使用什么
| 需求 | 工具 | 原因 |
|---|---|---|
| 完整 3D 场景(模型、灯光、物理效果) | R3F + drei | 声明式、适配 React、生态完善 |
| 原生 3D(不使用 React) | 直接使用 Three.js | 更轻量,没有 React 开销 |
| UI 上的简单 3D 变换 | CSS transform3d | 由 GPU 合成,无需 WebGL 上下文 |
| 2D 粒子 / 生成式图形 | Canvas 2D | API 更简单,GPU 开销更低 |
| 仅着色器视觉效果(无场景图) | 原生 WebGL / ShaderMaterial | 控制力最大,抽象最少 |
场景搭建模式
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>
规则:
- 始终将场景内容包在
<Suspense>中——加载器(GLTF、纹理、HDRI)需要它 - 设置
dpr={[1, 2]}来限制像素比(保留 Retina 效果,同时避免 GPU 过载) - 尽量精简 Canvas 父组件——重新渲染会传播到场景中
R3F Hooks
| Hook | 用途 | 注意事项 |
|---|---|---|
useFrame((state, delta) => {}) | 每帧逻辑(动画、物理效果) | 绝不要在其中调用 setState |
useThree() | 访问 gl、scene、camera、size、viewport、pointer | 只解构所需内容 |
useLoader(TextureLoader, url) | 加载任意 Three.js 资源 | 用 Suspense 包裹父组件 |
useGraph(scene) | 从已加载场景中提取节点 / 材质 | 在 useGLTF 后使用很方便 |
useFrame 提示
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
})
Drei 基础组件
| 组件 | 用途 |
|---|---|
Environment | HDRI 光照(预设:studio、sunset、city、forest、dawn) |
Float | 静止时的浮动动画(speed、rotationIntensity、floatIntensity) |
Text3D | 挤出式 3D 文本(需要 Facetype.js 的 JSON 字体) |
useGLTF | 加载 .glb/.gltf 模型(返回 { nodes, materials, scene }) |
useGLTF.preload(url) | 在组件挂载前预加载模型 |
MeshTransmissionMaterial | 玻璃 / 水晶 / 液体折射效果 |
PresentationControls | 通过拖动旋转产品展示模型 |
Center | 自动将任意网格组居中 |
Detailed | LOD——根据相机距离切换几何体 |
useTexture | 加载支持 Suspense 的纹理 |
Instances | 以声明式方式实例化重复网格 |
后期处理
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 默认采用选择性效果——将材质颜色 / emissive 提高到 1.0 以上即可使其发光
luminanceThreshold={1}= 除非显式设置 emissive,否则不会有任何内容发光- EffectComposer 内部的顺序很重要
- 效果会合并到单个渲染通道中(这是为了提升性能而设计的)
性能模式
| 模式 | 适用场景 |
|---|---|
<Instances> / InstancedMesh | 100 个以上相同的网格(粒子、树木、群体) |
<Detailed distances={[0, 50, 100]}> | LOD:根据距离切换高/低精度模型 |
在 <primitive> 上设置 dispose={null} | 重用共享几何体时,防止自动释放 |
useGLTF + Draco | 压缩 .glb 模型(体积减少 70-90%) |
useTexture + KTX2 | 压缩 GPU 纹理(占用 1/4 显存) |
在 Canvas 上设置 frameloop="demand" | 仅在内容发生变化时渲染(静态场景) |
使用 useThree 中的 invalidate() | 在按需模式下触发渲染 |
<AdaptiveDpr pixelated /> + <AdaptiveEvents />(drei) | 摄像机移动时降低分辨率并停用射线检测,静止后恢复 |
<Bvh>(drei) | 需要快速射线检测/悬停拾取的复杂场景 |
**目标指标:**绘制调用 < 100,三角形 < 1M,中端 GPU 上达到 60fps。
使用 stats-gl 或 r3f-perf 进行监控。
不要这样做
1. 切勿在 useFrame 中调用 setState
这会导致 React 每秒完整重渲染 60 次。直接修改引用。
// 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
})
2. 切勿在渲染循环中分配内存
每帧调用一次 new Vector3() = GC 峰值 = 卡顿。
// 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)
})
3. 切勿忘记 dispose(内存泄漏)
Three.js 纹理、几何体和材质都存储在 GPU 上。卸载 React 组件不会释放它们。
// 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()
}
}, [])
4. 切勿让 Canvas 父组件重新渲染
父组件中的状态变化会导致整个 Canvas 卸载并重新挂载 = 闪烁、状态丢失、资源重新加载。
// 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 />
</>
)
}
5. 切勿在没有 Suspense 的情况下加载资源
加载器(useGLTF、useTexture、useLoader)会抛出 Promise。没有 Suspense 就会导致崩溃。
// BAD
<Canvas>
<Model /> {/* useGLTF inside -- will throw */}
</Canvas>
// GOOD
<Canvas>
<Suspense fallback={<Loader />}>
<Model />
</Suspense>
</Canvas>
快速参考:加载子资源
| 需求 | 加载 |
|---|---|
| 场景样板代码、灯光设置、控件 | references/scene-setup.md |
| 自定义着色器、GLSL 模式、uniform | references/shaders.md |
| 动画原理、缓动、时序 | ../motion-principles/SKILL.md |
| GSAP + Three.js 集成 | ../gsap/SKILL.md |