2.4 高级网格操作与模型加载


2.4 高级网格操作与模型加载

本节摘要:真实项目的网格有两个来源——程序化拼装与外部模型文件,两种来源都绕不开"复用"这道选择题:合并、实例化还是克隆,三者决定第6站的绘制调用数量。本节给出三种手段的机理对比与选型结论,并走完 glTF 模型从加载到挂进场景树的完整流程。承接 2.3 的数据结构,通向第5章的绘制调用优化。

一、合并:把多个网格焊成一个

一栋楼有五百个构件,五百个网格就是五百次绘制调用。合并手段把多个网格的顶点数据拼进同一个缓冲,五百次调用变一次。代价同样清楚:合并后整体是一个包围盒,剔除粒度变粗;原始网格的身份消失,无法再单独移动其中任何一件。适合"形状各异但永远同进同退"的静态集合——建筑外壳、地形装饰、一次焊死的家具。

// 把四栋楼的构件合并成一个网格:绘制调用从几百降到一 const parts = [wallMesh, roofMesh, doorMesh, windowMesh]; const building = BABYLON.Mesh.MergeMeshes( parts, true, // 允许合并时移动网格到同一坐标系 true, // 合并后共享同一材质:想合并省调用就必须共享材质 undefined, false, // 不保留源网格 true // 多材质时按材质分组分别合并 ); building.freezeWorldMatrix(); // 静态合并件顺手冻结,第5章展开

二、实例化:一模一样的它复制一千遍

城市绿化要摆两千棵同一款树。克隆两千份顶点数据,显存直接乘两千;实例化让两千个网格共享同一份顶点缓冲,每个实例只存自己的变换——两 KB 换两百万顶点,这是引擎里性价比最高的复用手段。GPU 绘制时按实例逐个摆放,绘制调用的节省虽不及合并彻底(不同硬件实现有差异),显存与上传带宽的节省却是实打实的。

图 2-4 三种复用手段的取舍对照

图 2-4 三种复用手段的取舍对照

实例化的代码极短,收益却立竿见影:

// 一棵母树,两千个实例:共享顶点,各摆各的位置 const treeProto = BABYLON.MeshBuilder.CreateCylinder("tree", { height: 3, diameterTop: 0, diameterBottom: 0.6 }, scene); const treeTop = BABYLON.MeshBuilder.CreateSphere("crown", { diameter: 2 }, scene); treeTop.position.y = 2.2; treeTop.parent = treeProto; for (let i = 0; i < 2000; i++) { const t = treeProto.createInstance("tree" + i); // 实例只带变换,不带数据 t.position.set((Math.random() - 0.5) * 200, 0, (Math.random() - 0.5) * 200); t.rotation.y = Math.random() * Math.PI * 2; // 转个随机朝向,破除阵列感 t.scaling.setAll(0.8 + Math.random() * 0.4); // 大小微差 }

实例的局限要记住:它没有自己的材质与几何,想换贴图就得回到克隆;拾取倒是正常支持的,第4章会用到这一点。

三、glTF 装载:外部模型的进港流程

程序化拼装适合几何规则的东西,复杂资产来自美术的建模软件,交换格式首选 glTF——它按"面向 GPU 的数据结构"设计,顶点、索引、材质、节点树一一对应引擎概念,加载后无需转换即可直接消费。

装载分两步:异步加载,异步回调里接树:

// 异步加载模型:回调里拿到的是容器,不是网格本身 BABYLON.SceneLoader.ImportMeshAsync( "", // 目录名留空,用完整地址 "assets/", // 资源目录 "robot.glb", // 文件名:glb 是 glTF 的二进制打包 scene ).then((result) => { const root = result.meshes[0]; // 模型根节点:挂靠点 root.position.set(0, 0, 0); root.scaling.setAll(2); // 美术单位与场景单位经常不一致,先统一比例 // 遍历模型自带节点树:结构与 2.1 的场景树同构 for (const m of result.meshes) { console.log(m.name, m.getTotalVertices()); } // 模型内部父子关系原样保留:动画骨骼、部件联动都直接可用 }).catch((err) => console.error("加载失败", err));

加载期有两件事值得做在前面。第一,预热:模型首次出现在画面里时才编译材质、上传纹理,会造成 1.2 节说过的帧率尖刺,加载后先渲染一帧再进正式场景可以消化掉。第二,清点:加载完立刻打印顶点总数与材质数量,超预算的模型退回美术减面,比上线后优化便宜十倍。

⚠️ 常见坑:坐标轴约定不同——有的建模软件竖轴是 Z,引擎竖轴是 Y,模型进来看似"趴着";别急着旋转每个部件,转根节点一次到位。另一个坑是忘了处理加载失败分支,弱网下场景空转还以为代码错了。

三个高频追问

合并之后又想单独动其中一件怎么办? 合并不可逆,源网格已经销毁。正确姿势是合并前就把"可能单独动"的那件从名单里拿出来,或者按变动粒度分组分别合并——"按街区合并"的口诀正是这个意思。

实例支持拾取吗? 支持。实例有自己的变换与标识,4.2 节的射线查询能精确到具体实例;但它改不了材质与几何,要逐个换装的部分请回到克隆。

大模型加载能给进度条吗? 异步加载的进度回调可以驱动进度条。更稳的做法是把大模型拆成若干部件文件按需加载、先到先显,观感好过一根卡在八成的进度条,也顺带摊平了纹理上传的尖刺。

本节要点回顾

  • 合并:多个网格焊成一个缓冲,绘制调用归一,代价是失去个体控制,适合静态集合
  • 实例化:共享顶点数据只存变换,显存与带宽大省,适合大量重复物
  • 克隆:完整复制,最贵也最自由,只在真正各异时使用
  • 选型口诀:动的重复用实例、静的杂牌用合并、各异才克隆
  • glTF 是首选交换格式:数据结构与引擎同构,加载后记得统一比例、预热、清点
  • 加载失败要接住:弱网环境模型缺席是常态而非异常

第2章到此,流水线前四站的实体全部就位。下一章进入第5到7站:材质与着色——数据要变成颜色了。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U