2026.04.02
R3F 性能优化清单:把一个 24fps 的场景救回 120fps
一次真实的性能急救记录。六个步骤,把一个粒子 + 后期 + 十几个模型的 React Three Fiber 场景,从掉帧掉到肉眼可见,优化到高刷屏满帧运行。
上个月接手了一个营销页:R3F 写的,创意很棒,但在我的 M1 上只有 24fps,风扇转得像要起飞。最终它跑到了 120fps。这是完整的急救清单,按性价比排序。
1. 先测量,再动手
猜是没用的。装上 r3f-perf,让数据说话:
import { Perf } from 'r3f-perf'
<Canvas>
<Perf position="top-left" />
<Scene />
</Canvas>
看三个数字:draw calls、三角形数量、GPU 耗时。那个页面的初始状态:draw calls 217,三角形 340 万。目标:draw calls 50 以内,三角形 50 万以内。
2. setState 逐出 useFrame
搜一遍代码库,凡是 useFrame 里出现 setXxx 的,全部改成 ref 直改:
// ❌ 每帧一次 setState = 每帧一次协调,React 表示很累
useFrame(() => setRotation((r) => r + 0.01))
// ✅ 直接改 Three 对象,React 完全不知情
useFrame((_, delta) => {
meshRef.current.rotation.y += delta * 0.6
})
这一步就拿回了 20fps。React 的协调器很快,但每秒 60 次全树 diff 谁也扛不住。
3. 重复物体全部实例化
场景里 90 个小行星是 90 次 draw call。InstancedMesh 合并成 1 次:
const dummy = useMemo(() => new THREE.Object3D(), [])
useFrame(() => {
asteroids.forEach((a, i) => {
dummy.position.copy(a.position)
dummy.rotation.y = a.spin
dummy.updateMatrix()
meshRef.current.setMatrixAt(i, dummy.matrix)
})
meshRef.current.instanceMatrix.needsUpdate = true
})
<instancedMesh ref={meshRef} args={[undefined, undefined, 90]}>
<icosahedronGeometry args={[0.4, 0]} />
<meshStandardMaterial />
</instancedMesh>
217 个 draw call 降到 41 个。
4. dpr 封顶,抗锯齿关掉
4K 屏 + devicePixelRatio = 3 意味着每帧要画 2400 万像素。封顶到 2,视觉上几乎无损:
<Canvas
dpr={[1, 2]}
gl={{ antialias: false, powerPreference: 'high-performance' }}
>
粒子和辉光场景里 MSAA 基本看不出来,关掉换 15% 的 GPU 余量,值。
5. 灯光是奢侈品
每盏灯都是一次全场景着色计算,阴影贴图更是每帧一次额外渲染。那个页面里有 7 盏 PointLight,其中 5 盏「只是想加点氛围」:
- 氛围光交给
ambientLight+ 材质的emissive - 真实阴影换成 drei 的
ContactShadows(烘焙一次,不逐帧算) - 保留的动态光不超过 2 盏
6. 设备分级,移动端主动降载
最后一步是承认现实:手机 GPU 就是画不动桌面级的粒子量。入口处做一次分级:
const lowTier =
matchMedia('(pointer: coarse)').matches || innerWidth < 768
export const quality = {
particles: lowTier ? 4000 : 10000,
dpr: lowTier ? [1, 1.5] : [1, 2],
crystals: lowTier ? 4 : 7,
} as const
低端机少画一半粒子,用户只会觉得流畅,不会觉得寒酸。流畅本身就是高级感的一部分。
急救结果
| 指标 | 优化前 | 优化后 |
|---|---|---|
| FPS (M1) | 24 | 120 |
| Draw calls | 217 | 41 |
| 三角形 | 340 万 | 38 万 |
| GPU 帧耗时 | 38ms | 6.2ms |
性能优化最反直觉的一点:它几乎从不牺牲画面。上面六步做完,页面看起来和原来一模一样 —— 只是终于对得起那个创意了。