Appearance
Three.js 模型优化工程落地清单:指标对齐、体检门禁与自动优化
本文把两篇内容串成一条可执行的工程链路:
- 性能指标口径与阈值:/thoughts/threejs/threejs-performance-panel-core-metrics-analysis
- 模型优化策略与动作分层:/thoughts/threejs/threejs-model-optimization-comprehensive-solutions
目标是让项目从“上线后救火”变成“资产准入 + 自动策略 + 可观测验收”。
一、落地目标(必须明确)
- 指标口径统一:面板展示、体检门禁、策略触发使用同一套指标与阈值分层
- 优化前置:模型加载/解析完成后先体检,超标进入策略引擎,否则直接渲染
- 安全优先:启用优化时优先安全优化;任何优化动作都不应被引擎强制默认开启,必须可配置、可回退、可按设备分层
- 可观测闭环:每次加载的体检结果、触发动作、优化前后指标变化可追溯
二、工程模块拆分(建议按职责落地)
- 体检统计(Stats Collector):从 glTF/Three.js 对象计算指标(Triangles/Textures/DC/Heap/VRAM 等)
- 策略引擎(Policy Engine):根据设备档位与阈值,输出要执行的动作集合与顺序
- 优化执行器(Optimizer):实现 Draco/合并几何体/实例化/减面/降纹理/LOD 等动作(可分插件)
- 资源与缓存(Asset Cache):对优化产物做缓存与复用,避免重复优化
- 可观测(Observability):指标上报、错误采集、策略命中率、优化收益统计
三、放在底座还是放在项目:边界建议(推荐分层)
不要把整套优化“全放项目层”或“全放底座层”,更推荐做成“底座提供能力 + 项目提供策略”的分层架构,既能统一口径复用能力,又能避免引擎层强行做业务不可接受的降级。
底座(引擎/框架层)建议负责:
- 指标采集与口径:Triangles/Vertices/Textures/MaxTexture/Materials/DrawCall/FPS/Heap/VRAM Estimate
- 可观测闭环:traceId、体检报告、动作耗时、优化前后对比与上报
- 执行框架:Policy 接口、Optimizer 插件机制、缓存与幂等、开关与回退通道
- 默认内置安全优化能力:例如 Draco/KTX2 解码支持、合并/实例化工具能力(但不强制启用)
项目(业务层)建议负责:
- 设备档位与阈值:desktop/mobile 分层阈值与覆盖配置(与目标机型和画质目标强相关)
- 资产规则:白名单/黑名单(哪些模型/贴图不能动,哪些允许有损)
- 有损优化策略:减面/降纹理/通道精简/LOD 的触发条件与程度,以及画质验收与回退策略
- 交互与拆分约束:拾取/高亮/分件动画等导致“合并几何体/实例化”不可用的规则
四、指标对齐(面板指标 → 体检门禁 → 策略触发)
核心原则:面板阈值用于诊断与提示;门禁阈值用于触发策略;二者同源,门禁通常略更严格,且移动端更严格。
建议以以下四类作为“策略触发主指标”(优先覆盖最大收益):
- 三角面数(Triangles):GPU 算力压力
- DrawCall(桌面/移动分档):CPU 提交压力
- 纹理数量 + 最大纹理尺寸:显存/带宽压力
- 显存估算(VRAM Estimate):闪退/黑屏风险
五、默认阈值(可直接落地的第一版)
| 指标 | 触发(桌面) | 触发(移动) | 触发后优先动作 |
|---|---|---|---|
| 三角面数 | > 30 万 | > 15 万 | 减面 / LOD |
| DrawCall | > 300 | > 150 | 合并几何体 / 实例化 |
| 纹理数量 | > 10 | > 8 | 精简通道 / 复用 / 降纹理 |
| 最大纹理尺寸 | > 2048 | > 2048 | 降纹理 / 纹理压缩 |
| 显存估算 | > 300 MB | > 150 MB | 降纹理 / 精简通道 / 降级策略 |
六、指标到动作的映射(把“诊断”变成“可执行动作”)
| 指标异常 | 常见根因 | 可选动作(从上到下优先) | 关键取舍 |
|---|---|---|---|
| 三角面数高 | 模型细节过密、无 LOD、重复几何未实例化 | 实例化 → LOD → 适度减面 | 画质 vs 算力 |
| DrawCall 高 | mesh/材质碎片化、状态切换多 | 合并几何体 → 材质收敛 → 实例化 | 交互拆分 vs 合批收益 |
| 纹理数量多 | PBR 通道多、贴图重复、资产管理不规范 | 复用/合图 → 精简通道 → 降纹理 | 质感 vs 显存 |
| 最大纹理过大 | 贴图分辨率过高、未压缩 | 降纹理 → 纹理压缩(如 KTX2) | 清晰度 vs 显存/带宽 |
| 显存估算高 | 纹理大/多、FBO/后处理重 | 降纹理/压缩 → 精简通道 → 降级后处理 | 体验一致性 vs 稳定性 |
| JS Heap 高 | 资源释放不当、缓存膨胀、重复加载 | 资源回收 → 去重缓存 → 限制缓存上限 | 复用效率 vs 内存 |
| FPS 低 | 综合超标 | 优先排查 DrawCall 与纹理/三角面 | 先抓大头 |
七、落地清单(按实施顺序)
6.1 第 1 步:统一指标口径与采集位置
- 在
GLTFLoaderonLoad 后统一做体检统计,输出ModelStats - 统计至少包含:triangles、drawCalls(估算)、textureCount、maxTextureSize、vramEstimate(估算)
- 为每次加载生成
assetId与traceId,将“体检结果 + 策略决策 + 动作执行结果”串起来
6.2 第 2 步:设备档位识别与阈值选择
- 在运行时区分
deviceTier: desktop | mobile(第一版可仅按 UA/屏幕分辨率/内存粗分) - 允许通过配置覆盖阈值(项目可按目标机型调整)
6.3 第 3 步:策略引擎(输出动作列表)
- 启用优化后建议动作序列为“安全优先”
- Draco → 合并几何体 → 实例化 → 适度减面 → 降纹理 → LOD
- 策略输出时必须带上原因:命中的指标、阈值与设备档位
- 策略必须支持开关:
- 全局开关:是否启用有损优化
- 指标开关:是否对 triangles/texture/drawCall 单独启用策略
6.4 第 4 步:优化执行与缓存
- 对执行器做幂等:同一
assetId + 策略版本应命中缓存,避免重复优化 - 对有损优化提供回退:优化后若画质不满足或收益不足,可退回到安全优化结果
6.5 第 5 步:面板与策略的闭环展示
面板建议增加三类信息,降低排查成本:
- 体检摘要:triangles/drawCalls/textureCount/maxTextureSize/vramEstimate 的分档结果
- 策略摘要:是否触发、触发原因、执行了哪些动作、耗时
- 收益对比:优化前后关键指标变化(至少 triangles/drawCalls/纹理/显存估算)
6.6 第 6 步:验收标准(必须可量化)
- 稳定性:移动端不闪退、不黑屏
- 体验:目标机型 FPS 分档达到“正常”或以上,且波动可控
- 成本:优化额外耗时有上限(例如策略执行总耗时、首屏可接受范围)
- 质量:有损优化必须在可接受的画质阈值内(可用截图对比或主观评分)
6.7 第 7 步:灰度与回滚
- 先灰度策略开关:按用户分组或按页面分组启用
- 先启用安全优化:Draco/合并/实例化稳定后再逐步启用有损优化
- 保留紧急开关:出现质量问题可一键关闭有损优化或全量策略
八、最小可用输出(MVP 交付清单)
- ModelStats:triangles/textureCount/maxTextureSize/drawCalls/vramEstimate + deviceTier
- PolicyEngine:阈值分层 + 动作列表输出 + 开关能力
- Optimizer:至少实现 Draco + 合并几何体(或实例化)其中 1~2 个安全优化
- 面板展示:体检摘要 + 策略摘要
- 监控指标:策略触发率、优化耗时、优化收益(至少 2 项指标的前后对比)
九、可度量项清单(工程验收建议按此补齐)
这份清单覆盖了“最核心且普适”的可度量项,但不等同于“所有细节穷举”。不同业务形态(大场景/多模型/骨骼动画/后处理重)会引入额外度量项,建议把度量做成可扩展面板与事件上报。
8.1 资源与加载链路
| 指标 | 口径建议 | 采集位置 | 用途 |
|---|---|---|---|
| 资源下载耗时 | TTFB + download | 请求层/Loader | 判断网络与 CDN 命中 |
| 解码/解析耗时 | Draco/KTX2 解码 + glTF parse | Loader/Decoder | 判断 CPU 压力与 Worker 分配 |
| 首次可渲染时间 | 从开始加载到首帧渲染完成 | 应用层 | 体验指标 |
| 缓存命中率 | assetId+策略版本命中比例 | Asset Cache | 判断“重复优化”是否被抑制 |
8.2 渲染压力(与性能面板对齐)
| 指标 | 口径建议 | 用途 |
|---|---|---|
| Triangles/Vertices | 几何体统计 | GPU 算力压力 |
| DrawCall/Materials | 渲染批次统计 | CPU 提交压力 |
| Textures Count/Max Texture Size | 纹理统计 | 显存/带宽压力 |
| VRAM Estimate | 纹理解压体积近似 + 额外开销预留 | 闪退/黑屏风险 |
| FPS 与帧时间分位 | FPS + p50/p95/p99 frame time | 稳定性与卡顿定位 |
| JS Heap 与 GC 频次 | Heap size + GC 次数/耗时 | 卡顿与崩溃风险 |
8.3 策略与动作(必须可追溯)
| 指标 | 口径建议 | 用途 |
|---|---|---|
| 策略触发率 | 触发次数/总加载次数 | 判断阈值合理性 |
| 触发原因分布 | 按指标超标维度聚合 | 识别主要瓶颈 |
| 动作执行耗时 | 每个动作耗时与失败率 | 控制首屏成本 |
| 优化收益 | 优化前后关键指标差值 | 证明收益并迭代策略 |
| 回退比例 | 回退次数/触发次数 | 判断有损优化风险 |
8.4 画质与一致性(有损优化必须度量)
| 指标 | 口径建议 | 用途 |
|---|---|---|
| 画质分档 | 人工评分/截图对比/像素差(可选) | 控制降级边界 |
| 关键资产白名单 | 不允许降级的模型/贴图 | 避免核心资产被误伤 |
| 设备分层策略命中 | desktop/mobile 分层的命中与收益 | 验证分层合理性 |
十、优化动作权衡表(优缺点与适用边界)
这张表用于回答“为什么选这个动作、它的副作用是什么、什么时候不该用”。
| 动作 | 主要收益 | 主要代价/风险 | 适用场景 | 不适用/注意点 |
|---|---|---|---|---|
| Draco | 下载与解析压力下降 | 需要解码器,解码耗时增加 | 大模型、网络瓶颈明显 | CPU 过弱设备需评估解码开销 |
| 合并几何体 | DrawCall 明显下降 | 交互分件、材质/状态差异会限制合并 | mesh 碎片化、材质可收敛 | 影响拾取/高亮/独立动画时需谨慎 |
| 实例化 | 重复物体性能大幅提升 | 每实例属性管理复杂 | 同形多实例 | 不同材质/不同几何不适用 |
| 适度减面 | Triangles 下降 | 画质下降、法线细节损失 | GPU 算力瓶颈 | 细节模型、近景资产需保守 |
| 降纹理 | VRAM/带宽下降显著 | 文字/细节变糊 | 显存瓶颈、移动端兜底 | UI/文字贴图需白名单或单独规则 |
| 贴图通道精简 | 纹理数量与显存下降 | 质感下降、塑料感 | 质感要求可适当降低 | 产品对真实感敏感时慎用 |
| LOD | 远距离成本下降 | 切换抖动、资源管理复杂 | 大场景、远视距 | 近景为主或切换不可接受时慎用 |
十一、安全优化是否会影响动画(必须提前设定边界)
“安全优化”通常指对外观影响小,但不等同于对语义/结构/动画完全无影响。尤其是“合并几何体、实例化”这类动作,可能会改变节点结构或渲染表达方式,从而影响动画与交互能力。
| 动作 | 对骨骼动画(SkinnedMesh) | 对 Morph(变形) | 对节点级动画(平移/旋转/缩放) | 建议策略 |
|---|---|---|---|---|
| Draco(几何压缩) | 基本不影响 | 基本不影响 | 不影响 | 常作为首选动作,但仍建议默认关闭、由项目配置开启,并度量解码耗时 |
| 合并几何体 | 高风险:跨 skin 合并会破坏绑定关系 | 可能丢失或难以合并 morph targets | 高风险:丢失原节点层级与 pivot | 仅对静态、无骨骼/无 morph、且不依赖节点动画/分件交互的部分做合并 |
| 实例化 | 通常不适用于 SkinnedMesh | 不适用或实现成本高 | 节点动画需要改造成 per-instance 数据驱动 | 仅对“同形多实例的静态网格”启用;动画物体优先用其他手段 |
工程落地时建议在体检统计里补充资产特征(例如:hasSkins、hasMorphTargets、hasNodeAnimations),并把它作为策略引擎的硬约束:命中这些特征时自动禁用合并/实例化,避免引入后续动画相关的复杂问题。
架构思考
把优化做成“体检门禁 + 策略引擎”后,团队协作会发生变化:讨论不再围绕某次卡顿的个案,而围绕“指标口径、阈值分层、策略命中与动作副作用”持续迭代。工程化的价值就在于把经验变成可配置、可度量、可回退的系统能力。