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)24120
Draw calls21741
三角形340 万38 万
GPU 帧耗时38ms6.2ms

性能优化最反直觉的一点:它几乎从不牺牲画面。上面六步做完,页面看起来和原来一模一样 —— 只是终于对得起那个创意了。