improve-threejs

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Improve 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
useFrame
or a
requestAnimationFrame
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.
对Three.js或React Three Fiber(R3F)代码库进行审计,并修复最影响性能与体验的问题:每帧运行的冗余操作、从未释放的GPU资源、每次渲染时重建的场景图对象,以及用户可见的视觉缺陷。React Doctor提供机器验证的代码扫描;本工具则补充了通用React扫描器所缺乏的帧循环判断与视觉检查能力。
核心原则:问题严重程度取决于是否涉及渲染循环。
useFrame
requestAnimationFrame
回调中的代码每秒运行60次,因此此处的微小低效,其影响远超过设置面板中的重大低效。需根据代码运行位置而非规则默认等级来排序所有问题。

Workflow

工作流程

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 (
useFrame
hooks,
requestAnimationFrame
, the
<Canvas frameloop>
setting).
Build a hot-path map: every
useFrame
body, every RAF callback, every pointer-move handler. These files get the strictest review in Step 3.
扫描前先确定技术栈:是原生Three.js还是R3F,使用了哪些辅助库(drei、postprocessing、rapier),以及渲染循环的位置(
useFrame
钩子、
requestAnimationFrame
<Canvas frameloop>
设置)。
构建热路径地图:所有
useFrame
函数体、所有RAF回调、所有指针移动处理器。这些文件将在步骤3中接受最严格的审查。

Step 2: Scan

步骤2:扫描

Run React Doctor read-only to collect structured evidence:
bash
npx react-doctor@latest --verbose
For a regression check after making changes, run with
--scope changed
and confirm the score did not drop.
运行React Doctor只读模式以收集结构化证据:
bash
npx react-doctor@latest --verbose
若要在修改后检查是否出现回归,可添加
--scope changed
参数运行,并确认分数未下降。

Step 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
file:line
before reporting it.
HIGH severity, runs every frame or leaks GPU memory:
  • Allocation inside
    useFrame
    :
    new Vector3()
    ,
    new Color()
    , or fresh arrays passed to Three.js APIs each frame. Fix: hoist a scratch object to module scope or
    useMemo
    , then mutate it in place
  • setState
    inside
    useFrame
    : re-renders the React tree on every frame. Fix: mutate refs directly; reserve state for discrete changes like selection or visibility
  • Missing disposal: geometries, materials, textures, or render targets created imperatively and never disposed. Fix: call
    dispose()
    in the cleanup function, or move the object into R3F's declarative tree so it owns the lifecycle
  • Object reconstruction in render: geometry or material instances created without
    useMemo
    , or inline
    args
    arrays whose identity changes each render, forcing R3F to rebuild the underlying object
MEDIUM severity, per-render or per-interaction waste:
  • Unstable scene-graph props: inline
    new THREE.Vector3()
    or fresh material objects as props (plain arrays like
    position={[x, y, z]}
    are fine; R3F handles them)
  • Missing instancing: hundreds of identical meshes rendered individually instead of through
    <Instances>
    or
    InstancedMesh
  • Wasted frames:
    frameloop="always"
    on a scene that only changes on interaction. Fix:
    frameloop="demand"
    plus
    invalidate()
  • Uncached asset loading: textures and models loaded outside
    useLoader
    ,
    useTexture
    , or
    useGLTF
    , losing caching and Suspense integration
LOW severity, hygiene: React Doctor findings on non-canvas UI code, missing
<Preload>
, oversized textures.
结合热路径地图重新排序扫描器的发现结果,然后寻找扫描器无法识别的Three.js特定问题。在报告前需确认每个问题对应的
file:line
位置。
高严重程度:每帧运行或泄漏GPU内存
  • useFrame
    内的内存分配
    :每帧都向Three.js API传递
    new Vector3()
    new Color()
    或新数组。修复方案:将临时对象提升到模块作用域或通过
    useMemo
    缓存,然后在原地修改
  • useFrame
    内调用
    setState
    :每帧都会重新渲染React树。修复方案:直接修改refs;仅将state用于离散变更,如选择或可见性切换
  • 未释放资源:通过命令式创建的几何体、材质、纹理或渲染目标从未被释放。修复方案:在清理函数中调用
    dispose()
    ,或将对象移入R3F的声明式树中,由其管理生命周期
  • 渲染时重建对象:未通过
    useMemo
    缓存的几何体或材质实例,或内联
    args
    数组每次渲染时标识变更,导致R3F重建底层对象
中严重程度:每次渲染或交互时的冗余操作
  • 不稳定的场景图属性:将内联
    new THREE.Vector3()
    或新材质对象作为props(纯数组如
    position={[x, y, z]}
    无问题,R3F会处理)
  • 未使用实例化:数百个相同网格单独渲染,未通过
    <Instances>
    InstancedMesh
    实现实例化
  • 冗余帧渲染:场景仅在交互时变更,却设置
    frameloop="always"
    。修复方案:设置
    frameloop="demand"
    并配合
    invalidate()
  • 未缓存的资源加载:在
    useLoader
    useTexture
    useGLTF
    外部加载纹理和模型,丢失缓存与Suspense集成能力
低严重程度:代码整洁性: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:
AreaCheckFail when
Render sanityThe scene reaches a stable frame after loadBlack canvas, WebGL context errors, or content that never appears
GeometryMove the camera along seams, edges, and boundariesGaps, missing faces, visible backfaces, or two surfaces flickering at the same depth (z-fighting)
Transparency and depthCross depth-order boundaries with overlapping or transmissive surfacesWrong sort order, halos, opaque surfaces that should transmit, or flicker at grazing angles
TexturesView mapped surfaces close, far, and at grazing anglesMissing textures, stretching, seams, moiré, shimmer, or washed-out colors from a wrong color space
Materials and lightingChange light and view direction on lit surfacesSurfaces that ignore light direction, or reflective metals with no environment to reflect
ShadowsMove casters, receivers, and the light through their rangeAcne, detached or floating shadows, flicker at rest, or shadows that outlive their caster
CameraFollow the primary subject through movement and transitionsSubject leaves frame, camera clips into geometry, or foreground blocks the play area
Scale and contactCompare object scale and resting contact against surroundingsObjects float above, sink into, or intersect their support surface, or sit at implausible scale
Image stabilityPan the camera slowly at supported resolutionsSilhouettes, thin geometry, or highlights that crawl, sparkle, or ghost
Resize and DPRChange viewport size, zoom, and device pixel ratioDistortion, 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:
    renderer.outputColorSpace
    not set to
    SRGBColorSpace
    , color textures missing
    texture.colorSpace = SRGBColorSpace
    , or a data texture (normal, roughness) wrongly marked sRGB
  • Z-fighting: coplanar geometry needing
    polygonOffset
    or a position nudge, or a near plane set far too small for the scene scale
  • Shadow acne or floating shadows:
    shadow.bias
    and
    shadow.normalBias
    untuned, or a shadow camera frustum far larger than the scene
  • Blurry or stretched canvas: renderer size not synced to canvas CSS size,
    setPixelRatio
    never called, or a resize handler that forgets
    camera.updateProjectionMatrix()
  • Black metals:
    metalness: 1
    with no
    scene.environment
    set
  • Transparency sorting glitches: large transparent meshes needing
    depthWrite: false
    , manual
    renderOrder
    , or a split into smaller meshes
  • Shimmer and crawl: missing texture anisotropy, antialiasing disabled, or thin geometry needing thicker forms
检查场景实际渲染效果。每个视觉问题都需要证据:截图、帧捕获或可复现的观察结果,绝不能仅凭阅读代码猜测。若有开发服务器和浏览器,加载应用后捕获第一个稳定帧,然后在移动相机和交互后再次捕获。若无浏览器,则检查下方列出的代码层面原因,并将每个问题标记为从代码推断得出。
应用迷你评估标准。仅当证据显示存在失败条件时,才判定对应项失败:
领域检查内容判定失败的情况
渲染合理性场景加载后能稳定渲染出画面画布黑屏、WebGL上下文错误,或内容始终无法显示
几何体沿接缝、边缘和边界移动相机出现缝隙、缺失面、可见背面,或同一深度的两个表面闪烁(z-fighting)
透明度与深度让重叠或透射表面穿过深度顺序边界排序错误、光晕、本应透射却不透明的表面,或掠射角下出现闪烁
纹理近距离、远距离及掠射角查看映射表面纹理缺失、拉伸、接缝、摩尔纹、闪烁,或因颜色空间错误导致颜色褪色
材质与光照在受光表面上改变光照和视角方向表面无视光照方向,或反射金属无环境可反射
阴影移动阴影投射物、接收物和光源,使其覆盖整个范围出现shadow acne、分离或悬浮的阴影、静止时闪烁,或阴影在投射物消失后仍存在
相机跟随主要对象完成移动与过渡对象移出画面、相机切入几何体,或前景遮挡操作区域
缩放与接触对比对象缩放比例与静止时和周围环境的接触情况对象漂浮在支撑表面上方、陷入其中或与其相交,或缩放比例不合常理
图像稳定性在支持的分辨率下缓慢平移相机轮廓、薄几何体或高光出现爬行、闪烁或重影
缩放与设备像素比更改视口大小、缩放比例和设备像素比出现扭曲、模糊、拉伸输出,或内容超出视口
每个评估项都有一组常见的代码层面原因。当某一项失败时,首先检查这些原因:
  • 颜色褪色或过暗
    renderer.outputColorSpace
    未设置为
    SRGBColorSpace
    ,颜色纹理缺失
    texture.colorSpace = SRGBColorSpace
    ,或数据纹理(法线、粗糙度)被错误标记为sRGB
  • Z-fighting:共面几何体需要
    polygonOffset
    或位置微调,或近裁剪面设置过小与场景比例不匹配
  • Shadow acne或悬浮阴影
    shadow.bias
    shadow.normalBias
    未调整,或阴影相机视锥体远大于场景
  • 画布模糊或拉伸:渲染器尺寸未与画布CSS尺寸同步,从未调用
    setPixelRatio
    ,或缩放处理函数遗漏
    camera.updateProjectionMatrix()
  • 黑色金属材质:设置
    metalness: 1
    但未设置
    scene.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>.md
For 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
npx react-doctor@latest --verbose --scope changed
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
并确认分数未出现回归。重新检查所有未通过的视觉评估项,使用与原始证据相同的视角和交互方式,确认现在已通过。然后验证行为:场景可渲染、动画可播放、交互可响应。若浏览器开发者工具可用,在空闲场景中旋转相机时观察内存配置文件;空闲时堆内存持续上升意味着仍存在资源释放泄漏。

Checks the scanner always misses

扫描器始终遗漏的检查项

Review these by hand on every audit:
  • dispose()
    coverage for every imperatively created GPU resource
  • Allocations and
    setState
    inside
    useFrame
    and RAF callbacks
  • Event listeners and
    ResizeObserver
    s on the canvas or window without cleanup
  • 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()
    覆盖情况
  • useFrame
    和RAF回调中的内存分配与
    setState
    调用
  • 画布或窗口上的事件监听器与
    ResizeObserver
    未进行清理
  • 每次指针移动时对整个场景进行射线检测,而非针对过滤后的目标列表
  • 全局启用阴影或后期处理,但仅场景的某一部分需要这些功能
  • 渲染器和每个颜色纹理的颜色空间配置",