本节摘要:静态场景的最大浪费是"每帧重算永远不变的东西"——世界矩阵、可见性名单、材质状态全在空转。冻结手段把这类计算明确标记为"不需要再做",收益直接体现在 1.2 节流水线的第2、3、5站。本节讲冻结三件套的原理与边界、分层冻结策略,以及动态物体与冻结共存的纪律。承接 5.1 与 5.2,收束 CPU 侧的优化手法。
一座数字展馆:两百件展品、六百盏静态灯位、一片地面,全部纹丝不动。引擎不知道它们不动——每帧照样遍历整棵场景图、重算全部世界矩阵、重建可见性名单。算出来的结果每帧都一样,这笔 CPU 时间就是纯烧钱。展馆越静,浪费比例越高;极端的纯静态场景,九成的每帧计算都在做无用功。
冻结的思路是给引擎"打标记":这个网格的矩阵不变了,别再算;这个场景的内容不变了,名单别再重建。三件套各有分工:
// 冻结三件套:从网格级到材质级逐级扩大战果 // 一 冻结世界矩阵:最常用的单件冻结,2.4 节合并件的标准收尾 building.freezeWorldMatrix(); // 此网格的世界矩阵定格,第2站跳过它的重算 // 二 冻结活动网格名单:场景内容基本不变时,第2站整段跳过 scene.freezeActiveMeshes(); // 可见性名单定格:遍历与剔除一次性买断 // 三 冻结材质状态:材质参数不再变时,第5站的状态准备跳过 mat.freeze(); // 着色器绑定与状态缓存放进冷柜
三件套的收益结构不同:冻结矩阵是逐网格的小额节省;冻结名单是全场景的大额买断,收益最大、限制也最狠;冻结材质省的是每次调用的状态准备,材质越多的场景收益越明显。
冻结不是免费的——它赌的是"真的不再变"。冻结之后还要移动那个网格,矩阵是旧的;冻结名单后新加的网格不渲染、动态网格位置不更新。所以边界纪律比手段本身更重要:
| 手段 | 冻结了什么 | 何时能用 | 反悔代价 |
|---|---|---|---|
| freezeWorldMatrix | 单个网格的矩阵 | 该网格不再移动 | unfreeze 后下一帧恢复,代价小 |
| freezeActiveMeshes | 全场景可见性名单 | 网格增删极少、无动态剔除 | 解冻重建名单,一次尖刺 |
| material.freeze | 材质的状态准备 | 材质参数不再调 | 改参数前必须先解冻,否则改了白改 |
第二行的"无动态剔除"常被忽略:相机转动时,哪些网格在视锥内是变的——名单冻结后引擎不重新剔除,等价于"全画"。网格数少、剔除省不了几个的场景(比如展馆的封闭展厅)赚;开阔大场景里网格上千、剔除本来能砍掉大半的场景反而亏。一句话:冻结名单适合"内容少而恒定",不适合"内容多而广阔"。
分层冻结策略把这些手段组合起来用。数字展馆按房间分层:每个房间一个 TransformNode 根(2.1 节的树),玩家离开房间就冻结该房间全部网格的矩阵,进入新房间先解冻;主名单保持动态,动态的只有玩家与一两件交互物。比全场景一把冻结安全,又比完全不冻省得多:
// 分层冻结:房间级开关 function freezeRoom(room) { room.getChildMeshes().forEach((m) => m.freezeWorldMatrix()); // 只冻矩阵不动名单 room.setEnabled(false); // 顺手整个关掉:连遍历都不进,比冻结更彻底 } function unfreezeRoom(room) { room.setEnabled(true); room.getChildMeshes().forEach((m) => m.unfreezeWorldMatrix()); } // setEnabled 与冻结的组合拳:看不见的房间一分钱不花,看得见的房间省下矩阵重算
setEnabled 其实是冻结之外的"零号手段":直接把子树摘出遍历,连剔除都不用做。层级结构的收益在这里再次兑现——一棵组织良好的场景树,让"整房间开关"变成一行代码。
场景里总有动的东西:旋转的产品、飘的旗、走的 NPC。共存纪律三条。其一,冻结只给静态物:动态网格一个都别冻,冻了就是 4.1 节"属性抢所有权"的另一种翻车。其二,动画驱动的"看似静态"要小心:呼吸起伏的展台每帧都在动,冻结矩阵后起伏卡死——判断标准是"属性是否每帧变",不是"看起来动不动"。其三,拾取与冻结无冲突(包围盒还在),但冻结名单后新增的可交互物不会出现在拾取结果里,登记要赶在冻结前。
⚠️ 常见坑:"改了材质颜色没反应"九成是材质还在冻着。调参前先解冻、调完再冻回去,把这一来一回写成工具函数,比记口诀可靠。
背景:封闭式数字展馆八个展厅约一千八百个网格,CPU 账吃紧——帧耗时 19 毫秒,遍历与矩阵重算占 7 毫秒。操作分三轮:第一轮全场景冻结名单,帧耗时立刻落到 11 毫秒,但转身时墙外物体照画不误(名单不重建、剔除失效),被迫回退;第二轮改分层方案,当前展厅保持动态、其余七个整树关闭,各展厅静态网格冻结矩阵;第三轮把两百件展品共用的几个材质统一冻结。结果:帧耗时稳定在 12 毫秒且视角行为正常,剔除照常工作。解读:第一轮的失败正是"名单冻结适合小而恒定"的反面教材——网格上千的封闭场景靠剔除吃饭,名单焊死等于放弃了最大的一笔节省;分层方案把"关得掉的关掉、冻得着的冻着"分开执行,两头收益都吃到。变式:开阔地形类场景连分层冻结都不必上,把预算花在 5.1 的合并与实例化上更划算。
冻结之后帧率没变化? 冻结省的是 CPU 侧的遍历账;瓶颈若在 GPU 着色或调用数,冻结自然无感。先看 5.4 节的分项耗时,确认省的地方正是超支的地方,再决定下一步。
动态物体多的场景还能冻什么? 动态网格不冻,但它的材质可以冻(参数不再变就行)、静态陪衬可以冻矩阵、看不见的房间可以整树关闭。"部分冻结"才是真实项目的常态,全有或全无都走极端。
CPU 侧手法到此配齐:三板斧、内存四件、冻结三件套。手法都讲了,但"该用哪招"要靠数据说话——下一节把引擎的仪表盘与排查流程装上,让优化从手艺变成流程。