Improve Three.js 用于审计并修复 Three.js 或 React Three Fiber (R3F) 代码库中最影响体验的问题:每帧运行的工作、从未被释放的 GPU 资源、每次渲染都重建的场景图对象,以及用户肉眼可见的视觉缺陷。React Doctor 提供机器验证的代码扫描,本技能补充通用 React 扫描器所缺乏的帧循环判断与视觉检查。
核心原则:严重程度随渲染循环而定。useFrame 或 requestAnimationFrame 回调中的代码每秒运行 60 次,因此其中一处轻微低效也重于设置面板中的重大低效。所有发现应按其运行位置排序,而不是按规则默认严重程度。
工作流
第 1 步:侦察
扫描前先确认技术栈:纯 Three.js 还是 R3F,使用了哪些辅助库(drei、postprocessing、rapier),渲染循环位于何处(useFrame 钩子、requestAnimationFrame、<Canvas frameloop> 设置)。
构建热路径图:每个 useFrame 函数体、每个 RAF 回调、每个指针移动处理器。这些文件在第 3 步接受最严格审查。
第 2 步:扫描
以只读方式运行 React Doctor 收集结构化证据:
npx react-doctor@latest --verbose
如需在修改后检查回归,运行 --scope changed 并确认分数未下降。
第 3 步:按帧循环杠杆分级
使用热路径图对扫描器发现重新排序,然后寻找扫描器无法看到的 Three.js 专属问题。报告前必须在文件:行处确认每个发现。
高严重程度(每帧运行或泄漏 GPU 内存):
useFrame 内分配:每帧调用 new Vector3()、new Color(),或向 Three.js API 传入新数组。修复:将临时对象提升到模块作用域或用 useMemo,然后就地修改。
useFrame 内 setState:每帧重新渲染 React 树。修复:直接修改 ref;state 仅保留给选择或可见性等离散变化。
缺少释放:命令式创建且从未释放的 geometry、material、texture 或 render target。修复:在清理函数中调用 dispose(),或将对象移入 R3F 声明式树由其管理生命周期。
渲染时重建对象:未用 useMemo 就创建 geometry 或 material 实例,或内联 args 数组每次渲染身份变化,迫使 R3F 重建底层对象。
中严重程度(每次渲染或每次交互的浪费):
不稳定的场景图 props:作为 props 内联 new THREE.Vector3() 或新的 material 对象(position={[x, y, z]} 这样的普通数组没问题,R3F 会处理)。
缺少实例化:数百个相同 mesh 逐个渲染,而非通过 <Instances> 或 InstancedMesh。
浪费的帧:仅在交互时变化的场景使用了 frameloop="always"。修复:frameloop="demand" 加 invalidate()。
未缓存的资源加载:在 useLoader、useTexture 或 useGLTF 之外加载纹理和模型,失去缓存与 Suspense 集成。
低严重程度(卫生):React Doctor 对非画布 UI 代码的发现、缺少 <Preload>、过大的纹理。
第 4 步:视觉审计
检查场景实际渲染的内容。每个视觉发现都需要证据:截图、帧捕获或可复现的观察,绝不可凭阅读源码猜测。有开发服务器和浏览器时,加载应用、捕获首个稳定帧,移动相机并交互后再次捕获。无浏览器时,检查下列代码级成因,并将每个发现标注为“从源码推断”。
应用迷你评分表。仅当证据显示失败条件时该行才失败:
区域 / 检查 / 失败条件
渲染健全性:加载后场景达到稳定帧 / 黑画布、WebGL 上下文错误或内容始终不出现
几何:沿接缝、边缘、边界移动相机 / 缝隙、缺失面、可见背面,或两个表面在同一深度闪烁(z-fighting)
透明与深度:跨越深度顺序边界,使用重叠或透射表面 / 排序错误、光晕、本应透射却表现为不透明,或掠射角闪烁
纹理:近、远、掠射角查看贴图表面 / 纹理缺失、拉伸、接缝、摩尔纹、闪烁,或颜色空间错误导致褪色
材质与光照:在受光表面改变光照和视角方向 / 表面忽略光照方向,或反射金属无环境可反射
阴影:让投射者、接收者和光源遍历其范围 / 阴影痤疮、阴影脱离或漂浮、静止时闪烁,或阴影比投射者存留更久
相机:在移动和过渡中跟随主体 / 主体出画、相机穿入几何体,或前景遮挡游玩区域
比例与接触:将物体比例和静置接触与周围对比 / 物体漂浮、陷入或穿插支撑面,或比例不合理
图像稳定性:在支持的分辨率下缓慢平移相机 / 轮廓、细几何或高光爬行、闪烁或重影
缩放与 DPR:改变视口大小、缩放和设备像素比 / 扭曲、模糊、拉伸输出,或内容超出视口
每个评分表行有少量常见代码级成因。某行失败时先检查这些:
褪色或过暗:renderer.outputColorSpace 未设为 SRGBColorSpace,颜色纹理缺少 texture.colorSpace = SRGBColorSpace,或数据纹理(法线、粗糙度)被错误标记为 sRGB
Z-fighting:共面几何需要 polygonOffset 或位置微调,或近平面相对场景比例设得过小
阴影痤疮或漂浮阴影:shadow.bias 和 shadow.normalBias 未调优,或阴影相机视锥远大于场景
画布模糊或拉伸:渲染器尺寸未与画布 CSS 尺寸同步,从未调用 setPixelRatio,或 resize 处理器忘记 camera.updateProjectionMatrix()
黑色金属:metalness: 1 且未设置 scene.environment
透明排序故障:大型透明 mesh 需要 depthWrite: false、手动 renderOrder,或拆分为更小 mesh
闪烁与爬行:缺少纹理各向异性、抗锯齿被禁用,或细几何需要更厚的形态
第 5 步:修复
按严重程度顺序修复:先修高严重程度性能发现和失败的视觉行。当发现对应某条 React Doctor 规则时,获取规范配方而非自行发挥:
https://www.react.doctor/prompts/rules/<plugin>/<rule>.md
对于 Three.js 专属发现,应用分级列表或上述成因列表中点名的修复。
第 6 步:验证
运行 npx react-doctor@latest --verbose --scope changed 并确认分数未回退。使用与原始证据相同的视角和交互重新检查每个失败的视觉评分表行,确认其通过。然后验证行为:场景渲染、动画播放、交互响应。若有浏览器开发者工具,在空闲场景环绕时观察内存配置文件;空闲时堆上升意味着释放泄漏仍存在。
扫描器总会遗漏的检查
每次审计都需手动复核这些项:
每个命令式创建的 GPU 资源的 dispose() 覆盖情况
useFrame 和 RAF 回调内的分配与 setState
画布或 window 上未清理的事件监听器与 ResizeObserver
每次指针移动对全场景而非过滤目标列表进行射线检测
当场景只有一部分需要时全局启用阴影或后处理
渲染器和每个颜色纹理上的颜色空间配置