跳转到内容

网格导出

  • 入口MeshFile.SaveFileMeshUitls
  • 格式LAYAMODEL:0501.lm
  • 两条路:静态 writeMesh · 蒙皮 writeSkinnerMesh

首先枚举网格的顶点属性,据此构建顶点声明(vbDeclaration)并累加每顶点字节数。 除位置外,顶点可含法线、UV、颜色,蒙皮网格另含骨骼权重与索引。下面是实测的 Ellen_Body 网格——18727 顶点、30320 三角形、1 子网格、85 骨骼

Ellen_Body 网格详情

Ellen_Body 网格信息: 顶点流(Position/Normal/Tangent/UV/BlendWeight/BlendIndices)、索引格式、子网格数、骨骼数一览。

顶点流vbDeclaration字节受控开关(模型设置)
位置POSITION12必有
法线NORMAL12忽略顶点法线 可剥离
颜色COLOR16忽略顶点颜色 可剥离
UVUV8忽略顶点 UV 可剥离
UV1UV18光照贴图 UV(可自动生成)
骨骼权重 + 索引BLENDWEIGHT,BLENDINDICES16 + 4仅蒙皮网格;权重 Float32×4(16B),索引导出后压成 Uint8×4(4B)
切线TANGENT16忽略顶点切线 可剥离

导出工具模型设置

模型设置折叠区: 四个剥离开关(UV / 颜色 / 法线 / 切线)减小体积;自动生成 UV1 补光照贴图 UV。

模型导入设置

前提 · 模型导入设置:「读取/写入」需开启,导出时才能读到顶点数据;法线 / 切线 / 焊接顶点等设置也决定了上面表格里哪些顶点流存在。

顶点数据就绪后,先处理坐标系差异。 Unity 为左手系、LayaAir 为右手系,二者呈 X 轴镜像,空间数据需相应取反。各类数据处理如下:

数据处理
位置 / 法线 / 切线X 分量取反-vertice.x / -normal.x / -tangent.x
UVV 翻转uv.y × -1 + 1(Unity 纹理 V 向上,Laya 向下)
包围盒-max.x, min.y, min.z, -min.x, max.y, max.z(X 取反并交换 min/max)

网格按材质槽划分为多个子网格(SubMesh),每个子网格在索引缓冲中占一段区间。 区间记录起点 ibStart 与长度 ibLength(来自 mesh.GetIndices(i))。静态网格按原始顺序写出;蒙皮网格的区间在骨骼分组后重算。

以下两节为蒙皮网格专属。 LayaAir 单次 drawcall 的骨骼矩阵上限为 24 根(uniform 限制),而角色骨骼常达上百根。因此当子网格引用的骨骼超过 24 根时,需将其三角形拆分为多个骨骼数 ≤24 的分组、分批渲染。Ellen_Body 引用 85 根,需进行分组。

Ellen 骨骼层级树

Ellen 的骨骼树:Ellen_Root 展开是上百根骨骼节点(手指、脸部、头发都独立成骨),Ellen_Body 引用其中 85 根。

蒙皮网格渲染器组件

蒙皮网格渲染器: 导出时从这里读取 bones 列表(85 根)、根骨骼(Ellen Hips)与 sharedMesh,再做下面的分组。

分组示意:一个 SubMesh(30320 三角形、引用 85 根骨骼)按三角形引用的骨骼分堆,每堆 ≤24 根骨骼、各含一批三角形。

分组算法MaxBoneCount = 24):

  1. 取三角形的骨骼集合:每个三角形 3 个顶点共引用最多 12 根骨骼(triangleBoneIndex)。
  2. 尝试并入已有的堆(贪心):该堆已全包含这些骨骼 → 直接加入;否则若加入后骨骼总数 ≤ 24 → 扩充该堆并加入;均不满足 → 新建一堆。注:取「第一个可容纳」的堆,属贪心策略,不保证堆数最少(即不保证 drawcall 最少)。
  3. 顶点复制:同一个顶点若被分到不同的堆,必须复制一份——因为它的 4 个 boneIndex 要改写成「在该堆局部骨骼表里的下标」,各堆下标不同(checkPointcommonPoint 字典去重)。这就是导出后顶点数可能多于 Unity 原始数的原因。
  4. 索引重映射 + boneDic:顶点的全局 boneIndex 改写为堆内局部下标(changeBoneIndex);每个堆再写一张「局部 → 全局」骨骼映射表 boneDic,供运行时还原。

实证 · 顶点复制导致顶点数增加:Unity 原始 18727 顶点 —(24 骨骼分组 / 跨堆复制)→ 导出 .lm 19629 顶点 = 多出 902 个(被复制的顶点)。

导出后 .lm 网格信息

导出后的 .lm(在 LayaAir 中查看): 顶点数 19629(> Unity 的 18727)、85 骨骼、Uint16 索引;BLENDINDICES 已从 UInt32 压成 Uint8 × 4(对应代码里的 (byte)boneIndex)。

普通 MeshRendererwriteMesh,没有骨骼、不分组、不复制顶点;只有蒙皮网格才有上面那套。拿一块 ProBuilder 地块 pb_Mesh 和 Ellen 对比,一眼看清:

对比项静态 · pb_Mesh(地块)蒙皮 · Ellen_Body
导出方法writeMeshwriteSkinnerMesh
骨骼数085
导出前顶点72918727
导出后顶点729(不变)19629(+902)
顶点复制有(24 骨骼分组所致)
子网格11

静态网格导出前信息

导出前 · Unity: 地块网格 pb_Mesh,729 顶点、无骨骼,包围盒中心 (25,0,25)

静态网格导出后 .lm

导出后 · .lm: 顶点仍是 729(无复制)、Bones: 0;包围盒中心变 (-25,0,25)——X 已取反,实证坐标翻转。

bindpose(绑定姿势矩阵)记录蒙皮时骨骼相对网格的初始变换,同属空间数据,需在坐标系转换下做等价变换。 因对象是矩阵,处理步骤较 ② 更多:

bindpose → 取逆 → 分解 TRS → position.x 取反、quaternion.x / w 取反 → 归一化 + 重组 → 再取逆 → inverseGlobalBindPoses

可选步骤:顶点数过多时对网格做减面简化。 普通网格导出默认不简化,该机制主要用于粒子网格顶点超限时的兜底。提供两种策略:

策略说明
QEM(优先,高质量)UnityMeshSimplifier 库的二次误差度量算法(需另装 UnityMeshSimplifier 包并开启 UNITY_MESH_SIMPLIFIER 编译宏,MeshSimplifier.cs),保形好
抽取三角形(兜底)SimplifyByVertexSampling——库不可用时退化为抽取三角形,质量较低

同一个 Ellen 模型,导出前后并排对比——网格形状一致,证明顶点 / 三角形 / 骨骼绑定正确还原:

导出前 Unity 网格线框

导出前 · Unity: Scene 视图「着色线框」下的网格拓扑(顶点 / 三角形)。

导出后 LayaAir 渲染

导出后 · LayaAir: 同一 Ellen 网格 + 法杖在引擎中还原渲染,形状一致。

导出的 .lm 模型文件

产物 .lm 导出目录 Models/ 下,每个网格一个文件——如 Ellen-Ellen_BodyStaff-Staff_Body


本页基于插件 1.1.7 源码与实测整理。