improve-threejs
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseImprove Three.js
优化Three.js
Audits a Three.js or React Three Fiber (R3F) codebase and fixes what hurts most: work that runs every frame, GPU resources that never get disposed, scene-graph objects rebuilt on every render, and visual defects the user can see. React Doctor supplies the machine-verified code scan; this skill supplies the frame-loop judgment and the visual inspection a general React scanner lacks.
The core principle: severity follows the render loop. Code inside or a callback runs 60 times per second, so a minor inefficiency there outweighs a major one in a settings panel. Rank every finding by where it runs, not by the rule's default severity.
useFramerequestAnimationFrame对Three.js或React Three Fiber(R3F)代码库进行审计,并修复最影响性能与体验的问题:每帧运行的冗余操作、从未释放的GPU资源、每次渲染时重建的场景图对象,以及用户可见的视觉缺陷。React Doctor提供机器验证的代码扫描;本工具则补充了通用React扫描器所缺乏的帧循环判断与视觉检查能力。
核心原则:问题严重程度取决于是否涉及渲染循环。或回调中的代码每秒运行60次,因此此处的微小低效,其影响远超过设置面板中的重大低效。需根据代码运行位置而非规则默认等级来排序所有问题。
useFramerequestAnimationFrameWorkflow
工作流程
Step 1: Recon
步骤1:前期排查
Identify the stack before scanning: plain Three.js or R3F, which helper libraries are in use (drei, postprocessing, rapier), and where the render loop lives ( hooks, , the setting).
useFramerequestAnimationFrame<Canvas frameloop>Build a hot-path map: every body, every RAF callback, every pointer-move handler. These files get the strictest review in Step 3.
useFrame扫描前先确定技术栈:是原生Three.js还是R3F,使用了哪些辅助库(drei、postprocessing、rapier),以及渲染循环的位置(钩子、、设置)。
useFramerequestAnimationFrame<Canvas frameloop>构建热路径地图:所有函数体、所有RAF回调、所有指针移动处理器。这些文件将在步骤3中接受最严格的审查。
useFrameStep 2: Scan
步骤2:扫描
Run React Doctor read-only to collect structured evidence:
bash
npx react-doctor@latest --verboseFor a regression check after making changes, run with and confirm the score did not drop.
--scope changed运行React Doctor只读模式以收集结构化证据:
bash
npx react-doctor@latest --verbose若要在修改后检查是否出现回归,可添加参数运行,并确认分数未下降。
--scope changedStep 3: Triage by frame-loop leverage
步骤3:按帧循环影响程度划分优先级
Re-rank the scanner's findings using the hot-path map, then hunt for the Three.js-specific problems the scanner cannot see. Confirm every finding at its before reporting it.
file:lineHIGH severity, runs every frame or leaks GPU memory:
- Allocation inside :
useFrame,new Vector3(), or fresh arrays passed to Three.js APIs each frame. Fix: hoist a scratch object to module scope ornew Color(), then mutate it in placeuseMemo - inside
setState: re-renders the React tree on every frame. Fix: mutate refs directly; reserve state for discrete changes like selection or visibilityuseFrame - Missing disposal: geometries, materials, textures, or render targets created imperatively and never disposed. Fix: call in the cleanup function, or move the object into R3F's declarative tree so it owns the lifecycle
dispose() - Object reconstruction in render: geometry or material instances created without , or inline
useMemoarrays whose identity changes each render, forcing R3F to rebuild the underlying objectargs
MEDIUM severity, per-render or per-interaction waste:
- Unstable scene-graph props: inline or fresh material objects as props (plain arrays like
new THREE.Vector3()are fine; R3F handles them)position={[x, y, z]} - Missing instancing: hundreds of identical meshes rendered individually instead of through or
<Instances>InstancedMesh - Wasted frames: on a scene that only changes on interaction. Fix:
frameloop="always"plusframeloop="demand"invalidate() - Uncached asset loading: textures and models loaded outside ,
useLoader, oruseTexture, losing caching and Suspense integrationuseGLTF
LOW severity, hygiene: React Doctor findings on non-canvas UI code, missing , oversized textures.
<Preload>结合热路径地图重新排序扫描器的发现结果,然后寻找扫描器无法识别的Three.js特定问题。在报告前需确认每个问题对应的位置。
file:line高严重程度:每帧运行或泄漏GPU内存
- 内的内存分配:每帧都向Three.js API传递
useFrame、new Vector3()或新数组。修复方案:将临时对象提升到模块作用域或通过new Color()缓存,然后在原地修改useMemo - 内调用
useFrame:每帧都会重新渲染React树。修复方案:直接修改refs;仅将state用于离散变更,如选择或可见性切换setState - 未释放资源:通过命令式创建的几何体、材质、纹理或渲染目标从未被释放。修复方案:在清理函数中调用,或将对象移入R3F的声明式树中,由其管理生命周期
dispose() - 渲染时重建对象:未通过缓存的几何体或材质实例,或内联
useMemo数组每次渲染时标识变更,导致R3F重建底层对象args
中严重程度:每次渲染或交互时的冗余操作
- 不稳定的场景图属性:将内联或新材质对象作为props(纯数组如
new THREE.Vector3()无问题,R3F会处理)position={[x, y, z]} - 未使用实例化:数百个相同网格单独渲染,未通过或
<Instances>实现实例化InstancedMesh - 冗余帧渲染:场景仅在交互时变更,却设置。修复方案:设置
frameloop="always"并配合frameloop="demand"invalidate() - 未缓存的资源加载:在、
useLoader或useTexture外部加载纹理和模型,丢失缓存与Suspense集成能力useGLTF
低严重程度:代码整洁性:React Doctor针对非画布UI代码、缺失、过大纹理等问题的发现结果。
<Preload>Step 4: Visual audit
步骤4:视觉审计
Inspect what the scene actually renders. Every visual finding needs evidence: a screenshot, a frame capture, or a reproduced observation, never a guess from reading source. When a dev server and browser are available, load the app, capture the first stable frame, then capture again after moving the camera and interacting. When no browser is available, check the code-level causes listed below and label each finding as inferred from source.
Apply the mini rubric. A row fails only when the evidence shows the failure condition:
| Area | Check | Fail when |
|---|---|---|
| Render sanity | The scene reaches a stable frame after load | Black canvas, WebGL context errors, or content that never appears |
| Geometry | Move the camera along seams, edges, and boundaries | Gaps, missing faces, visible backfaces, or two surfaces flickering at the same depth (z-fighting) |
| Transparency and depth | Cross depth-order boundaries with overlapping or transmissive surfaces | Wrong sort order, halos, opaque surfaces that should transmit, or flicker at grazing angles |
| Textures | View mapped surfaces close, far, and at grazing angles | Missing textures, stretching, seams, moiré, shimmer, or washed-out colors from a wrong color space |
| Materials and lighting | Change light and view direction on lit surfaces | Surfaces that ignore light direction, or reflective metals with no environment to reflect |
| Shadows | Move casters, receivers, and the light through their range | Acne, detached or floating shadows, flicker at rest, or shadows that outlive their caster |
| Camera | Follow the primary subject through movement and transitions | Subject leaves frame, camera clips into geometry, or foreground blocks the play area |
| Scale and contact | Compare object scale and resting contact against surroundings | Objects float above, sink into, or intersect their support surface, or sit at implausible scale |
| Image stability | Pan the camera slowly at supported resolutions | Silhouettes, thin geometry, or highlights that crawl, sparkle, or ghost |
| Resize and DPR | Change viewport size, zoom, and device pixel ratio | Distortion, blur, stretched output, or content leaving the viewport |
Each rubric row has a small set of usual code-level causes. Check these first when a row fails:
- Washed-out or too-dark colors: not set to
renderer.outputColorSpace, color textures missingSRGBColorSpace, or a data texture (normal, roughness) wrongly marked sRGBtexture.colorSpace = SRGBColorSpace - Z-fighting: coplanar geometry needing or a position nudge, or a near plane set far too small for the scene scale
polygonOffset - Shadow acne or floating shadows: and
shadow.biasuntuned, or a shadow camera frustum far larger than the sceneshadow.normalBias - Blurry or stretched canvas: renderer size not synced to canvas CSS size, never called, or a resize handler that forgets
setPixelRatiocamera.updateProjectionMatrix() - Black metals: with no
metalness: 1setscene.environment - Transparency sorting glitches: large transparent meshes needing , manual
depthWrite: false, or a split into smaller meshesrenderOrder - Shimmer and crawl: missing texture anisotropy, antialiasing disabled, or thin geometry needing thicker forms
检查场景实际渲染效果。每个视觉问题都需要证据:截图、帧捕获或可复现的观察结果,绝不能仅凭阅读代码猜测。若有开发服务器和浏览器,加载应用后捕获第一个稳定帧,然后在移动相机和交互后再次捕获。若无浏览器,则检查下方列出的代码层面原因,并将每个问题标记为从代码推断得出。
应用迷你评估标准。仅当证据显示存在失败条件时,才判定对应项失败:
| 领域 | 检查内容 | 判定失败的情况 |
|---|---|---|
| 渲染合理性 | 场景加载后能稳定渲染出画面 | 画布黑屏、WebGL上下文错误,或内容始终无法显示 |
| 几何体 | 沿接缝、边缘和边界移动相机 | 出现缝隙、缺失面、可见背面,或同一深度的两个表面闪烁(z-fighting) |
| 透明度与深度 | 让重叠或透射表面穿过深度顺序边界 | 排序错误、光晕、本应透射却不透明的表面,或掠射角下出现闪烁 |
| 纹理 | 近距离、远距离及掠射角查看映射表面 | 纹理缺失、拉伸、接缝、摩尔纹、闪烁,或因颜色空间错误导致颜色褪色 |
| 材质与光照 | 在受光表面上改变光照和视角方向 | 表面无视光照方向,或反射金属无环境可反射 |
| 阴影 | 移动阴影投射物、接收物和光源,使其覆盖整个范围 | 出现shadow acne、分离或悬浮的阴影、静止时闪烁,或阴影在投射物消失后仍存在 |
| 相机 | 跟随主要对象完成移动与过渡 | 对象移出画面、相机切入几何体,或前景遮挡操作区域 |
| 缩放与接触 | 对比对象缩放比例与静止时和周围环境的接触情况 | 对象漂浮在支撑表面上方、陷入其中或与其相交,或缩放比例不合常理 |
| 图像稳定性 | 在支持的分辨率下缓慢平移相机 | 轮廓、薄几何体或高光出现爬行、闪烁或重影 |
| 缩放与设备像素比 | 更改视口大小、缩放比例和设备像素比 | 出现扭曲、模糊、拉伸输出,或内容超出视口 |
每个评估项都有一组常见的代码层面原因。当某一项失败时,首先检查这些原因:
- 颜色褪色或过暗:未设置为
renderer.outputColorSpace,颜色纹理缺失SRGBColorSpace,或数据纹理(法线、粗糙度)被错误标记为sRGBtexture.colorSpace = SRGBColorSpace - Z-fighting:共面几何体需要或位置微调,或近裁剪面设置过小与场景比例不匹配
polygonOffset - Shadow acne或悬浮阴影:和
shadow.bias未调整,或阴影相机视锥体远大于场景shadow.normalBias - 画布模糊或拉伸:渲染器尺寸未与画布CSS尺寸同步,从未调用,或缩放处理函数遗漏
setPixelRatiocamera.updateProjectionMatrix() - 黑色金属材质:设置但未设置
metalness: 1scene.environment - 透明度排序故障:大型透明网格需要设置、手动
depthWrite: false,或拆分为更小的网格renderOrder - 闪烁与爬行:缺失纹理各向异性、抗锯齿已禁用,或薄几何体需要增加厚度
Step 5: Fix
步骤5:修复
Fix in severity order: HIGH performance findings and failed visual rows first. When a finding maps to a React Doctor rule, fetch the canonical recipe instead of improvising:
text
https://www.react.doctor/prompts/rules/<plugin>/<rule>.mdFor Three.js-specific findings, apply the fix named in the triage list or the cause list above.
按严重程度顺序修复:先处理高优先级性能问题和未通过的视觉评估项。若某个问题对应React Doctor规则,请获取标准修复方案而非自行修改:
text
https://www.react.doctor/prompts/rules/<plugin>/<rule>.md针对Three.js特定问题,应用优先级划分列表或上述原因列表中指定的修复方案。
Step 6: Validate
步骤6:验证
Run and confirm the score did not regress. Re-check every visual rubric row that failed, using the same viewpoint and interaction as the original evidence, and confirm it now passes. Then verify behavior: the scene renders, animations play, and interactions respond. If browser dev tools are available, watch the memory profile while orbiting an idle scene; a rising heap during idle means a disposal leak survived.
npx react-doctor@latest --verbose --scope changed运行并确认分数未出现回归。重新检查所有未通过的视觉评估项,使用与原始证据相同的视角和交互方式,确认现在已通过。然后验证行为:场景可渲染、动画可播放、交互可响应。若浏览器开发者工具可用,在空闲场景中旋转相机时观察内存配置文件;空闲时堆内存持续上升意味着仍存在资源释放泄漏。
npx react-doctor@latest --verbose --scope changedChecks the scanner always misses
扫描器始终遗漏的检查项
Review these by hand on every audit:
- coverage for every imperatively created GPU resource
dispose() - Allocations and inside
setStateand RAF callbacksuseFrame - Event listeners and s on the canvas or window without cleanup
ResizeObserver - Raycasting against the full scene on every pointer move instead of a filtered target list
- Shadows or postprocessing enabled globally when one part of the scene needs them
- Color space configuration on the renderer and every color texture
每次审计都需手动检查以下内容:
- 所有命令式创建的GPU资源的覆盖情况
dispose() - 和RAF回调中的内存分配与
useFrame调用setState - 画布或窗口上的事件监听器与未进行清理
ResizeObserver - 每次指针移动时对整个场景进行射线检测,而非针对过滤后的目标列表
- 全局启用阴影或后期处理,但仅场景的某一部分需要这些功能
- 渲染器和每个颜色纹理的颜色空间配置",