Skip to main content

Chapter 13: Mesh Attributes and Formats

一个网格进入渲染管线时,GPU 接收的是一组顶点属性、索引、材质引用、变形数据和运行时状态。Chapter 11 已经讨论网格拓扑与数据结构,Chapter 12 已经把曲面、UV 和接缝放进几何表示中。本章继续追踪同一个问题:一个来自 DCC 工具的网格,怎样变成 shader 可以稳定读取、GPU 可以高效提交、跨平台 asset 管线可以验证的运行时数据。

本章使用一个贯穿材料:一个游戏角色的金属护臂 mesh。它有几何位置、法线、切线、两套 UV、顶点色、skin joint、skin weight,还有一个用于运行时变体控制的自定义属性。它从 DCC 工具导出,经过格式转换、压缩、校验、缓存,最后在一帧的 opaque pass 中由一个 draw call 绘制。视觉上,它可能出现法线贴图接缝、纹理拉伸、骨骼变形扭曲、实例颜色失效或加载卡顿;这些现象都可以回到属性语义、文件格式和管线处理顺序上定位。

读完本章,读者应能执行三类判断:定位某个视觉错误来自哪个 mesh attribute,比较 OBJ、FBX、glTF 在 asset 管线中的角色,设计一套面向高效渲染的属性布局和压缩策略。这里的核心结论先给出:mesh 格式负责资产交换和传输,顶点属性负责 shader 输入契约,运行时布局负责 GPU 访问成本。三者需要在同一条 asset 管线中统一校验。

本章的判断以数据结构、shader 输入输出、格式语义和可观察画面现象为依据。文中提到 RenderDoc、Nsight、Xcode GPU tools 或引擎自带 frame debugger 时,它们作为可选观察入口,用来复查 draw call、buffer 绑定和 shader 输入。

13.1 顶点属性的意义与使用

顶点属性是每个顶点携带给 GPU 的结构化输入。一次 draw call 通常绑定一个或多个 vertex buffer,再由 pipeline state 或 shader 反射信息说明每个 attribute 的语义、数据格式、stride、offset 和 slot。vertex shader 按这个契约读取数据,完成空间变换、法线变换、UV 传递、蒙皮、实例变体或程序化效果控制。

以金属护臂为例,最小可渲染路径只需要 POSITION 和索引。引擎可以把 position 送入 vertex shader,通过 MVP 矩阵得到 clip space 位置,随后进入 primitive assembly 和 rasterization。画面上能看到形状;材质光照还缺少方向信息,贴图还缺少采样坐标,角色动画还缺少骨骼影响。其他属性的作用来自这些缺口:normal 让光照知道表面朝向,tangent 让 normal map 的扰动方向能从纹理空间转到世界或视图空间,UV 让 texture sampler 得到稳定采样位置,color 可以提供局部遮罩或烘焙色,joint 和 weight 让顶点跟随骨骼矩阵变形,custom data 让引擎把批处理、材质变体或程序化控制放入同一次提交中。

属性含义需要和 shader 输入保持一一对应。下面的简化结构展示了一个护臂 mesh 在运行时可能采用的 CPU 侧描述。它属于引擎内部把常见属性打包成 GPU buffer 的一种示例。

struct ArmGuardVertex {
float position[3];
uint32_t normalPacked;
uint32_t tangentPacked;
uint16_t uv0[2];
uint16_t uv1[2];
uint8_t color[4];
uint16_t joints[4];
uint16_t weights[4];
uint8_t materialRegion;
uint8_t variantMask;
uint16_t padding;
};

这段结构中,position 保留为高精度浮点,因为它直接参与空间变换和包围盒计算。normal 和 tangent 使用打包格式,因为它们本质是单位方向,常见压缩可以在较小误差内恢复方向。UV 使用 16 位数据,因为大多数 UV chart 的范围受控。color、material region、variant mask 属于材质和批处理控制信息,通常对精度要求低,对带宽更敏感。joint 和 weight 的精度边界需要更谨慎,权重和关节索引错误会被骨骼矩阵放大,最后表现为顶点被拉向错误位置。

属性进入 shader 后会沿着不同路径传播。position 在 vertex shader 中生成 gl_Position 或等价输出;normal、tangent、UV、color 作为 varying 进入 fragment shader,并在光栅化阶段插值;joint 和 weight 通常只在 vertex shader 中使用;自定义属性可能在 vertex shader 中决定偏移,也可能作为 flat varying 传给 fragment shader 选择材质分区。由此可以得到一个排查顺序:画面轮廓错误先看 position、index、坐标空间和 skinning;光照方向错误先看 normal、tangent、handedness 和法线贴图解码;纹理错位先看 UV、sampler、mipmap 和 chart;批处理变体错误先看 custom attribute、instance data 和 shader 分支。

下面的图把属性从 asset 到 shader 的路径压缩到一条可检查链中。它聚焦属性解释位置,用于定位“某个属性在哪个阶段被解释”。

这条路径中,DCC 工具里的属性名称和运行时 shader 语义可能不同。asset conversion 需要把命名、类型、空间和单位统一成引擎约定。input layout 需要保证 buffer 的 stride、offset 和 format 与 shader 读取方式一致。frame debugger 可以观察某次 draw call 绑定的 vertex buffer、index buffer、shader 输入和材质资源,用来确认错误发生在转换前、上传后还是 shader 解码阶段。

属性错误常见于三个边界。第一个边界是重复顶点。一个空间位置在 UV seam、hard normal、材质分区或 skin weight 分裂处需要复制成多个顶点,因为 GPU 顶点属性以“顶点记录”为单位读取。第二个边界是坐标空间。normal、tangent、joint matrix、node transform 使用的空间一旦混用,shader 计算结果会稳定错误。第三个边界是插值语义。UV、normal、color 会随三角形插值,material id、region id 这类离散属性通常要用 flat 语义或放入 instance/material buffer,直接插值会产生中间值。

因此,顶点属性的设计动作可以归纳为四步:先确认 shader 真正需要哪些输入,再确认每个输入的空间、范围和精度,随后决定它位于 per-vertex、per-instance、per-material 还是 per-draw 数据中,最后用工具或校验脚本确认运行时绑定与 shader 声明一致。属性越多,单个顶点越宽,vertex fetch 带宽越高;属性越少,shader 可能需要额外计算或访问其他 buffer。这里没有固定答案,只有以画面正确性和瓶颈类型为依据的布局选择。

13.2 常见网格文件格式(OBJ/FBX/glTF 等)

网格文件格式的工程角色可以分成三类:编辑交换、离线中间层、运行时传输。OBJ、FBX、glTF 经常被放在同一个列表中比较,它们在 asset 管线中的位置却不同。正确比较方式应使用同一组维度:能表达哪些属性,能表达哪些场景关系,解析成本怎样,是否适合版本控制,是否适合直接加载到运行时 buffer。

OBJ 的强项是简单。一个常见 OBJ 文件会把几何位置、纹理坐标、法线和面索引写成文本记录。它适合静态模型交换、快速调试和教学示例。护臂 mesh 如果只需要静态外形、UV 和 normal,OBJ 可以清楚呈现 face 如何引用 position、UV 和 normal。OBJ 对现代游戏 asset 管线支持有限:材质通常依赖外部 MTL,层级、动画、蒙皮、PBR 材质、压缩、二进制 buffer、扩展元数据都需要额外约定。把 OBJ 当成最终运行时格式,会把大量转换和校验推迟到加载阶段。

FBX 的强项是 DCC 生态和复杂资产交换。角色护臂如果和骨架、动画、约束、材质槽、DCC 层级一起交付,FBX 能保存更多作者侧信息。它适合从 Maya、MotionBuilder、Blender 或其他工具进入离线 asset pipeline。工程上需要把 FBX 当作作者数据容器处理:导入器读取后,应转换成引擎自己的中间表示,再统一做坐标、单位、轴向、材质、骨骼、动画和属性校验。FBX 的复杂性意味着同一文件在不同导出器和导入器之间可能出现解释差异,所以它更适合作为管线输入,运行时应使用已经冻结的内部格式或传输格式。

glTF 的强项是运行时传输。Khronos 的 glTF 2.0 规范 把 mesh primitive 的 attributes 设计成语义名到 accessor 的映射,常见语义包括 POSITIONNORMALTANGENTTEXCOORD_nCOLOR_nJOINTS_nWEIGHTS_n。这和 GPU 读取 buffer 的方式更接近:accessor 描述类型、数量和组件格式,bufferView 描述二进制片段,mesh primitive 再把语义绑定到具体 accessor。对于 Web、移动端预览、跨引擎交付和轻量运行时加载,glTF 的结构让 asset conversion 和 runtime loader 的边界更清晰。

下面的 glTF 片段展示护臂 mesh 的一个 primitive 如何表达属性。示例只保留关键字段,目的是说明语义名、accessor 和材质引用之间的关系。

{
"meshes": [
{
"name": "arm_guard",
"primitives": [
{
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TANGENT": 2,
"TEXCOORD_0": 3,
"TEXCOORD_1": 4,
"COLOR_0": 5,
"JOINTS_0": 6,
"WEIGHTS_0": 7,
"_MATERIAL_REGION": 8
},
"indices": 9,
"material": 2,
"mode": 4
}
]
}
]
}

这个片段能解释两个工程事实。第一,格式中的属性语义只说明“这段数据是什么”,还没有说明引擎最终怎样布局 GPU buffer;转换器仍然可以重新交错、拆分或压缩。第二,自定义属性需要进入命名和类型约定。glTF 要求应用自定义语义使用下划线前缀,工程中还需要规定 _MATERIAL_REGION 的取值范围、插值方式、缺省值和 shader 使用位置。

三种格式的选择可以落到一张工程判断表中。

格式主要角色适合保存的内容主要风险推荐落点
OBJ简单几何交换position、UV、normal、face、基础材质名缺少现代运行时语义,文本解析成本高静态模型调试、临时交换、教学样例
FBXDCC 复杂资产交换层级、骨架、动画、材质槽、作者侧元数据导入器差异、坐标单位差异、内容过宽离线导入输入
glTF运行时传输和跨平台分发二进制 buffer、材质、纹理、skin、animation、扩展属性扩展支持差异、引擎约定仍需转换Web、工具预览、轻量运行时加载
引擎内部格式最终运行时数据已校验 buffer、pipeline layout、压缩结果、缓存 key生态交换能力弱游戏或图形应用加载路径

护臂 mesh 的推荐路径通常是 FBX 或 DCC 原生文件进入离线管线,转换器生成 glTF 或引擎内部格式,运行时再加载已经压缩和校验的 buffer。OBJ 可以参与局部调试;骨骼权重、PBR 材质和自定义运行时语义应交给 FBX、glTF 或引擎内部格式。这个判断可以迁移到其他资产:作者侧格式保留创作信息,运行时格式保留渲染所需信息,中间转换承担约定统一和错误拦截。

13.3 属性压缩与优化编码策略

属性压缩的目标是降低存储、传输和 vertex fetch 成本,同时把误差控制在视觉和动画可接受范围内。压缩对象可以是顶点属性、索引、拓扑顺序、动画曲线、morph delta 或完整 bufferView。对于护臂 mesh,压缩收益主要来自三个位置:position 和方向属性的数据宽度,index buffer 的局部性,asset 传输时的二进制 buffer 大小。

Quantization 是把连续值映射到有限整数范围。position 可以以 mesh 局部包围盒为参考,把坐标从 float 映射到 16 位整数;UV 可以映射到 normalized unsigned short;color 可以映射到 normalized byte。这个过程需要保存反量化参数,shader 或加载器再把整数恢复到计算空间。position 的量化误差会影响轮廓、接缝和碰撞代理;UV 的量化误差会影响采样位置;weight 的量化误差会影响骨骼变形。设计压缩格式时,应先按属性用途划分误差敏感度,再决定 bit 数和解码位置。

Normal encoding 利用单位向量的约束减少存储。常见做法包括 signed normalized 10 位或 16 位组件、octahedral encoding、tangent frame 重建等。护臂的金属表面使用 normal map 时,normal 和 tangent 的误差会进入 BRDF 计算,最后表现为高光抖动、接缝或阴影方向异常。压缩 normal 时,应同时检查模型轮廓、法线贴图和实际光照下的材质表现。tangent 的 w 分量通常用于表达 bitangent handedness,压缩时应保留这个符号或提供等价重建规则。

Index compression 和 mesh reorder 面向 GPU 访问模式。index buffer 决定顶点复用顺序,影响 post-transform cache 命中;vertex order 决定解码和 vertex fetch 的空间局部性;triangle order 还会影响 overdraw。meshoptimizer 这类工具会把顶点缓存优化、overdraw 优化、vertex fetch 优化和压缩编码组合起来。glTF 生态中的 EXT_meshopt_compression 就以 bufferView 为粒度压缩属性、索引、morph target 和动画数据,并强调顶点与索引数据的 locality。Google 的 Draco 也面向 3D mesh 和 point cloud 的存储与传输压缩,支持点、连接信息、纹理坐标、颜色、法线和通用属性。

压缩策略需要和加载路径一起设计。Draco 往往提供较强压缩比,解码和重建成本也需要进入加载预算。meshopt 风格压缩通常更接近 GPU buffer 数据流,适合快速解码和 Web 传输。引擎内部还可能使用平台相关格式,把解码结果直接写入最终 vertex buffer 或 staging buffer。选择时要看瓶颈:下载体积受限时优先压缩比,首帧加载受限时优先解码速度,运行时带宽受限时优先 vertex layout 和 attribute width,CPU 预处理受限时优先离线缓存。

下面的压缩检查顺序可以应用到护臂 mesh,也可以迁移到更大的场景资产。

  1. 先固定视觉基线:使用未压缩 mesh 渲染同一相机、同一材质、同一动画姿态,保存可复查画面或自动截图。
  2. 再分属性压缩:position、normal、tangent、UV、color、joint、weight 分别压缩并复查画面,定位误差来源。
  3. 随后加入 index reorder:观察 vertex count、index count、post-transform cache 指标、draw call 时间和 overdraw 热区。
  4. 再加入传输压缩:比较文件体积、解码时间、峰值内存、最终 GPU buffer 大小和首帧可见时间。
  5. 最后冻结平台 profile:为 Web、移动端、桌面端分别记录格式、精度、扩展支持和 fallback 策略。

压缩失败时,画面症状能反推属性。轮廓边缘出现抖动,优先检查 position 量化范围和 bounds;法线贴图接缝增强,优先检查 tangent space、normal encoding 和 UV seam 顶点复制;角色摆动时护臂被拉扯,优先检查 weight 归一化、joint index 精度和骨骼矩阵顺序;加载峰值内存过高,优先检查解码临时 buffer 和多份 fallback buffer。压缩是一组围绕误差、带宽、解码和平台支持的取舍。

13.4 为高效渲染设计自定义 Attributes

自定义 attribute 的设计目标是把渲染决策前移到数据中,使 shader、batching、instancing、skinning、morph 和 procedural data 能以稳定契约读取。它的风险也来自同一点:一旦自定义语义缺少文档和校验,资产、shader、工具和运行时 loader 会对同一段数据产生不同解释。

护臂 mesh 中的 _MATERIAL_REGION 可以表示金属、皮革、磨损边缘或发光铆钉区域。fragment shader 根据 region 选择不同的材质参数或纹理层。这个属性如果按普通 varying 插值,三角形内部会出现非整数中间值,材质边界产生混合污染。更稳定的做法是把它设计成 per-face 或 per-primitive 数据,或在拆分 submesh 后使用 material id;如果必须保存在 vertex attribute 中,需要保证边界顶点复制,并在 shader 中按整数或 flat 语义处理。

另一个例子是 _VARIANT_MASK。同一个护臂模型可能有干净版、磨损版、阵营色版和节日版。variant mask 可以配合 material buffer 或 texture array 选择参数。它适合放在 per-instance data 中,因为同一 mesh 的多个实例可能使用不同变体;如果把它放入 per-vertex attribute,每个顶点重复存储同一个值,会增加 vertex buffer 宽度。这个例子说明自定义数据的第一判断应是变化频率:per-vertex、per-face、per-instance、per-material、per-draw、per-frame 分别对应不同 buffer 和绑定成本。

为 batching 设计 attribute 时,需要把提交合并和 shader 分支成本放在一起判断。material id 能减少 draw call;shader 里根据 id 访问不同材质资源时,可能引入分支、纹理数组限制或缓存分散。instance transform 能把多个护臂实例合并到一次 instanced draw;per-instance buffer 的 stride、更新频率和对齐会影响上传成本。skinning attribute 让角色在 GPU 上变形;joint 数量、weight 数量和矩阵读取方式会影响 vertex shader ALU 与 buffer 访问。morph attribute 适合表情或软变形;每个目标都会增加额外 attribute 或 texture/buffer 读取。

自定义 attribute 的工程规范至少要包含六项:语义名、数据类型、取值范围、变化频率、插值方式、缺省值。语义名解决工具和 shader 对齐问题;数据类型决定压缩和 buffer layout;取值范围用于校验和 debug 可视化;变化频率决定它属于 vertex buffer、instance buffer 还是 material buffer;插值方式决定 rasterization 后是否仍有语义;缺省值让缺失资产能稳定 fallback。

一个可维护的属性表可以这样写入引擎文档或 asset profile。表格中的内容比名称本身更关键,因为它规定了数据如何被读取。

属性变化频率推荐存储shader 使用点校验条件
_MATERIAL_REGIONper-face 或 per-vertexsubmesh、primitive id 或整数 attributefragment shader 选择材质区域取值属于材质表,边界顶点已复制
_VARIANT_MASKper-instanceinstance buffervertex 或 fragment shader 选择变体mask 位与变体表一致
_BEND_FACTORper-vertexcompressed vertex attributevertex shader 程序化弯曲范围在 profile 约定内
_LOD_GROUPper-mesh 或 per-submeshmetadata 或 draw dataCPU/GPU culling 和 LOD 选择与 LOD 资源数量一致
_WIND_PHASEper-instance 或 per-vertexinstance buffer 或 attributevertex shader 动画相位同一批次范围稳定

自定义 attribute 还需要工具链支持。DCC 导出时要明确属性从哪里来:顶点色通道、UV 通道、自定义 property、几何节点输出或脚本生成结果。转换器要把作者侧名称映射到运行时语义。校验器要检查缺失、类型、范围、插值风险和 shader 依赖。调试工具要提供可视化方式,例如把 _MATERIAL_REGION 显示成伪彩色,把 _BEND_FACTOR 显示成灰度,把 instance variant 显示成覆盖层。

高效渲染中的自定义 attribute 设计可以使用一个稳定顺序:先确认视觉或性能目标,再确认数据变化频率,再选择存储位置,再规定格式和精度,再写入转换校验,最后在 frame capture 中确认 draw call 的 buffer 绑定和 shader 输入。这样设计出的 attribute 会成为 asset、shader、batching 和 debug 之间的契约。

13.5 跨平台 Asset 管线中的 Mesh 处理

跨平台 asset 管线要把作者侧网格变成多个平台都能加载的运行时数据。它同时处理格式转换、单位、坐标系、切线空间、材质绑定、属性压缩、缓存 key、版本控制、fallback 和错误报告。护臂 mesh 从 DCC 到运行时的一条完整路径,可以拆成导出、导入、规范化、校验、优化、打包、加载和复查。

导出阶段负责保留作者意图。DCC 文件中可能存在对象层级、modifier、隐藏 mesh、非冻结 transform、多材质 slot、自定义属性、骨骼绑定和动画约束。导出 profile 应规定单位、轴向、三角化时机、UV 通道、normal 生成方式、tangent 生成方式、skin weight 上限、morph target 命名和自定义属性来源。对于护臂这种硬表面和角色绑定混合资产,导出 profile 还要确认 hard edge、UV seam、weighted normal、tangent space 和 skin influence 数量。

导入阶段负责把外部格式转成引擎中间表示。FBX、OBJ、glTF 的解析结果先进入中间表示,再生成最终运行时 buffer。中间表示要显式保存 mesh primitive、attribute streams、index buffer、material slot、skin binding、morph target、bounds、metadata 和 source diagnostics。这样后续步骤可以使用同一套逻辑处理不同来源文件。比如 OBJ 缺少 skin 数据时,导入器应给出静态 mesh;FBX 提供骨架时,导入器应把 joint 名称映射到 skeleton asset;glTF 提供 accessor 时,导入器应把 accessor 的 component type 和 count 转成内部属性描述。

规范化阶段负责消除平台和工具差异。常见动作包括单位转换、坐标轴转换、handedness 修正、matrix decomposition、三角化、顶点拆分、属性补全、normal/tangent 重算、UV chart 检查、skin weight 归一化、morph target 顺序固定和材质槽重排。每个动作都可能改变最终顶点数量和属性值,所以管线应记录处理日志,并把关键结果写入缓存 key。缓存 key 至少应覆盖源文件 hash、导入 profile、压缩 profile、工具版本和目标平台。

校验阶段负责在离线管线中拦截错误。护臂 mesh 的校验可以包括:position 是否存在且 bounds 合理;index 是否越界;UV 是否包含 NaN 或极端范围;normal 是否接近单位长度;tangent 是否和 normal 近似正交;skin weight 是否归一;joint index 是否能映射到 skeleton;morph target count 是否一致;自定义属性是否符合 profile;材质引用是否能解析。校验失败应报告到 asset 名称、primitive、attribute、顶点范围或面范围,方便作者或工具开发者定位。

优化和打包阶段负责把中间表示变成目标平台数据。桌面平台可能保留较高精度 position 和更宽的材质变体数据;移动端可能采用更激进的 attribute quantization、纹理压缩和 meshopt 压缩;Web 端可能优先 glTF GLB、KTX2 texture 和可并行解码 buffer。运行时最终希望得到的是接近 GPU 读取方式的数据:vertex buffer、index buffer、material binding、texture handles、skeleton palette、morph buffer、bounds 和 draw packet。打包结果还要记录 fallback,例如缺少某扩展时使用未压缩 buffer,或缺少某 shader feature 时禁用某个自定义 attribute 路径。

运行时加载阶段要控制内存生命周期。加载器读取包体后,可能先把压缩 buffer 解码到临时内存,再上传到 GPU staging buffer,随后创建 device-local buffer,并释放 CPU 临时数据。对于大型场景,streaming 系统还要按 chunk、LOD、可见性和材质依赖决定加载顺序。护臂 mesh 单体规模有限;角色装备数量增加时,重复材质、重复 skeleton palette、重复 vertex layout 都会影响内存和提交成本。asset 管线应尽量让相同 layout 的 mesh 共享 pipeline state,让相同材质参数进入统一 material buffer。

下面的管线图把跨平台 mesh 处理压缩成可复查阶段。它的核心用途是定位错误归属:作者数据、转换规则、压缩策略、运行时加载和 GPU 绘制分别有自己的证据。

这条链路中的每个阶段都要输出可观察证据。导出阶段有 profile 和 source diagnostics;导入阶段有中间 mesh dump;规范化阶段有顶点数量、bounds、坐标转换和切线生成记录;校验阶段有明确错误;优化阶段有压缩误差、体积、解码时间和 vertex layout;打包阶段有平台 profile 和 fallback;运行时阶段有加载日志、GPU buffer 创建记录和 frame capture。证据越早产生,错误越少进入运行时。

跨平台处理的最终判断顺序可以这样执行:先检查源资产是否表达了需要的属性,再检查导出 profile 是否把它们写出,随后检查导入器是否把格式语义映射到中间表示,再检查规范化是否改变了空间、单位、切线或顶点拆分,接着检查压缩是否引入可见误差,最后在 frame capture 中确认 buffer layout、shader 输入、材质绑定和 draw call 状态。这个顺序覆盖了本章所有对象:顶点属性、文件格式、压缩编码、自定义 attribute 和运行时 asset 管线。

最小自检任务

给定一个角色护臂 mesh,在 DCC 中能正确显示。导出到运行时后出现三个问题:第一,法线贴图在 UV seam 附近产生明显高光断裂;第二,角色手臂弯曲时护臂边缘被拉向错误骨骼;第三,开发者想把同一护臂用于四个阵营配色,并希望减少 draw call。请设计一条排查和处理路径,要求说明应检查哪些属性、选择哪类格式或中间表示、如何压缩,以及自定义 attribute 应放在哪里。

答案要点

先把问题拆回属性路径。高光断裂优先检查 NORMALTANGENTTEXCOORD_0、UV seam 顶点复制和 tangent space 生成规则;运行时应在同一材质和同一 normal map 下比较未压缩版本与压缩版本,确认错误来自导出、切线重算还是 normal/tangent 压缩。护臂被拉向错误骨骼时,优先检查 JOINTS_0WEIGHTS_0、weight 归一化、joint 名称到 skeleton 的映射、inverse bind matrix 顺序和压缩精度。四个阵营配色属于 per-instance 或 per-material 变化,推荐把 variant id 或 mask 放入 instance buffer 或 material buffer;如果每个顶点存一份,会增加 vertex fetch 带宽。格式路径上,DCC 侧可以用 FBX 或原生文件保留骨架和作者信息,导入后转成引擎中间表示,再生成 glTF GLB 或内部运行时包体。压缩顺序应先固定未压缩视觉基线,再分别压缩 position、normal、tangent、UV、skin weight,最后加入 index reorder 和传输压缩。最终用 frame capture 复查 draw call 的 vertex buffer layout、instance buffer、shader 输入、material binding 和绘制结果。

本章知识点总结

  • 属性契约:顶点属性是 shader 输入契约,必须同时规定语义、格式、空间、精度和绑定位置。
  • 路径定位:轮廓、光照、纹理、蒙皮和批处理错误可以分别回到 position、normal、UV、joint weight 和 custom data 检查。
  • 重复顶点:UV seam、hard normal、材质边界和 skin weight 分裂处会产生重复顶点,这是 GPU 属性读取模型的直接结果。
  • 格式分工:OBJ 适合简单几何交换,FBX 适合 DCC 复杂资产输入,glTF 适合运行时传输和跨平台分发。
  • 中间表示:外部格式解析结果应进入引擎中间表示,再统一执行规范化、校验、优化和打包。
  • 量化压缩:Quantization 需要围绕属性取值范围和误差敏感度设计,并保留反量化参数。
  • 方向编码:Normal 和 tangent 压缩必须在实际法线贴图和光照下复查,因为误差会进入材质计算。
  • 索引优化:Index reorder 和 vertex reorder 会影响顶点复用、vertex fetch 局部性和压缩效果。
  • 压缩取舍:下载体积、解码速度、GPU 带宽、峰值内存和平台扩展支持共同决定压缩方案。
  • 自定义语义:自定义 attribute 必须记录语义名、数据类型、取值范围、变化频率、插值方式和缺省值。
  • 变化频率:per-vertex、per-instance、per-material、per-draw 数据应放入不同存储位置,减少重复和错误插值。
  • 管线证据:跨平台 mesh 管线需要在导出、导入、规范化、校验、优化、打包、加载和 frame capture 阶段留下可复查证据。