模型
引擎画 mesh,而它只加载一种 mesh 格式:.esmesh。.gltf / .glb 是源文件,不是资源
——把它带进项目会跑一次导入,写出场景真正能引用的那些资源。
本页讲的就是这次导入。画出结果的那个组件见组件参考; 材质整体见材质。
导入一个模型
Section titled “导入一个模型”三个入口,同一次导入:
| 方式 | 会发生什么 |
|---|---|
把 .gltf/.glb 拖进内容浏览器 |
源文件(连同它引用的文件)被拷进项目并导入。 |
| 直接放进项目目录 | 编辑器会发现并导入它 —— git checkout、访达里拷贝、Blender 重新导出,都算。 |
estella import-gltf <file> [outDir] --project <dir> |
命令行里的同一次导入。--scale <n> 决定大小。 |
产物落在源文件旁边,而不是内容浏览器当前显示的目录:重新导入必须覆盖同一批文件。
导入写出什么
Section titled “导入写出什么”以带两个 primitive、一张内嵌图的 robot.gltf 为例:
| 产物 | 是什么 |
|---|---|
robot_0_0.esmesh、robot_1_0.esmesh |
每个 primitive 一个几何文件,命名 <stem>_<mesh>_<primitive>(只有一个时就是 <stem>)。 |
robot_0.png |
文件内嵌的图片。已经在磁盘上的图片只被引用,不会复制。 |
robot_m0.esmaterial |
仅当源材质说了 Mesh2D 装不下的东西时才写 —— 见材质。 |
robot_Walk.estimeline |
源里每个动画一个,命名 <stem>_<动画名> —— 见动画。 |
robot.esprefab |
各部分怎么组装:节点层级,每个 Mesh2D 带上自己的 mesh、贴图、tint 和开关。 |
把 prefab 拖进场景。单个 .esmesh 也是资源(Mesh2D.mesh 字段可以直接引用),
但 prefab 才是作者组装好的那个模型。
glTF 以米为单位。一个世界单位是一个设计像素。所以一个 1.7 米高的角色进来只有 1.7 个单位高 —— 几个像素 —— 除非你另行指定。
导入器不猜这个系数。模型进来很小的时候它会说出来,而修正它的那个数字就是源文件导入设置里的
Scale(在内容浏览器里选中 .gltf)。保存它会重新导入这个模型。
Scale 落在 prefab 根节点的 Transform 上,不会烘进几何 —— .esmesh 保持与源一致,
而那个数字始终看得见、改得动。
以下情况会重新跑一次导入:
- 源文件变了(重新导出覆盖了旧的);
- 在内容浏览器里对它选择 Reimport;
- 保存了一项导入设置。
资源 uuid 会保留,所以引用这些产物的场景和 prefab 继续有效。
一个 glTF 材质按「谁装得下」拆开。
落在 Mesh2D 组件上 —— base color,因为引擎的 mesh 通路本来就是
texture(uv) × 顶点色 × tint:
| glTF | 组件字段 |
|---|---|
baseColorTexture |
texture |
baseColorFactor |
color |
alphaMode 不是 BLEND |
opaque |
doubleSided: false |
cullBackfaces |
带 NORMAL 属性 |
lit: true |
落在 .esmaterial 里 —— 着色,那是逐材质的常量和采样器,组件没有地方放。
只在源文件用到它们时才写,基于内建的 Model shader:
| glTF | 材质参数 |
|---|---|
normalTexture |
u_normalMap |
emissiveFactor / emissiveTexture |
u_emissive / u_emissiveMap |
occlusionTexture(含 strength) |
u_occlusionMap / u_occlusionStrength |
alphaMode: MASK + alphaCutoff |
u_alphaCutoff |
metallicFactor / roughnessFactor / metallicRoughnessTexture |
u_metallic / u_roughness / u_metallicRoughnessMap |
金属度与粗糙度即使等于引擎自己的默认值也会写出来:glTF 把两个系数都默认成 1(完全粗糙的 金属),而 shader 把金属度默认成 0 —— 省略它们的产物会描述出一个源文件没有说过的表面。
材质还会把这次 draw 的混合、深度和剔除重述一遍 —— 因为材质会替换它们, 这样一个被要求自我遮挡的模型,在有了材质之后仍然自我遮挡。
源里的每个动画会在 mesh 旁边写出一个 .estimeline,prefab 的根挂一个 TimelinePlayer
指向第一个,且 playing 是关的 —— 播哪一个是场景的决定。打开 playing,或调用 timeline
API,它就开始播。
一条 clip 驱动的是它当初针对的那些节点,按名字路径在 prefab 根之下寻址。同一父节点下 重名的兄弟节点会在导入时被改成不同的名字,好让一条轨道不会驱动错对象;prefab 里显示的就是 那个名字。
LINEAR、STEP、CUBICSPLINE 都会带过来,含切线。
绑定好的模型会带着绑定进来:mesh 携带它的骨骼索引、权重和绑定姿态,prefab 给实体挂一个
MeshSkin,按那些矩阵的顺序列出骨骼实体。移动骨骼 —— 手动,或者由上面的动画驱动 ——
mesh 就会变形。
蒙皮 mesh 自己的 transform 会被忽略,这是 glTF 的要求:它的骨骼已经摆在世界里了, 所以移动 mesh 实体不会有任何效果。要移动的是骨架的根,或者骨骼本身。
| 限制 | 为什么 |
|---|---|
| 每个 mesh 最多 64 根骨骼 | 姿态走 uniform 块,64 根是 4KB —— 在 WebGL2 对一个块的保证范围内。超过的 mesh 按不变形绘制。 |
| 骨骼数必须与绑定姿态一致 | MeshSkin 列出的骨骼数对不上时按不变形绘制,而不是让顶点去解析错误的矩阵。 |
EXT_meshopt_compression 与 KHR_draco_mesh_compression 在导入时解码,所以压缩过的导出件
产出与未压缩件相同的 .esmesh。运行时什么都不变:压缩是源文件的属性,不是产物的属性。
只报告、不导入的东西
Section titled “只报告、不导入的东西”导入器不会无声丢弃任何东西 —— 它带不过来的,都会在导入时作为提示出现。
| 不导入 | 为什么 |
|---|---|
morph target、weights 动画通道 |
在多个形状之间混合需要那些 target 本身,而它们没有被带过来。 |
第二套 UV(TEXCOORD_1)、KHR_texture_transform |
UV 按作者写的原样使用。 |
非三角形 primitive(mode ≠ 4) |
渲染器画的是三角形列表。 |
normalTexture.scale |
法线贴图按全强度应用。 |
值得知道的限制
Section titled “值得知道的限制”- 点光的距离在 XY 平面上算(2D 光照模型固有);方向光是精确的,而那通常正是着色一个模型需要的。
- 自己写了 vertex stage 的材质不会被重定向到 mesh 几何上 —— 那样它会无视 per-object transform。 只写 fragment 的材质两边都能用。
- 没有
NORMAL属性的几何仍然会受光,落在 2D 表面那个常量法线上 —— 平,但不是黑的。