Skip to content
云风 edited this page Aug 24, 2024 · 6 revisions

多媒体资产

游戏和其它软件最大的不同是:除了代码,开发游戏需要创作大量的非文本多媒体资产(asset)。图片、模型、动画、等等。使用一个游戏引擎,开发者最关心的事务之一是怎样生产和管理这些资产。

注:这里使用 Asset 来代表这些数据,不过由于一些历史原因,我们在引擎的代码中有些地方将其称为资源 (resource) 。

Ant 并没有一个 All in One 的开发工具,可以用来生成一切引擎用到的资产。几乎没有游戏引擎会有这样的工具。创作者可能会用 PhotoShop 制作图片、用 Blender 制作模型…… 开发一个专有工具取代这些专业软件的功能没有意义。专业资产创作软件向游戏引擎导入数据,就必须定义一种双方都认可的数据格式。

工作流

在使用 Ant 引擎时,和其它游戏引擎不同,并不需要经过一个资产库导入过程。

资产创作人员把资产的通用格式的原始文件放在 Ant 的 Package 中,通常就是复制到本地文件系统下。根据资产类别,可能还需要补充一些信息,例如,贴图直接复制一个 png 文件是不能用的,还需要补充一个 .texture 文件。

补充信息可以手工编写一个格式为 DataList 的文本,也可以用引擎自带的编辑器生成。

这些数据均可提交到版本管理系统,建议把代码和多媒体资产分为子模块做版本管理。因为 VFS 支持不同的本地文件映射形式,所以资产文件放在本地文件系统的哪个位置并不重要。

在程序第一次运行时,会触发资产编译模块的转换流程,将这些原始数据转换为引擎认可的形式,并将结果储存在本地缓存中。

贴图

贴图 (texture) 通常是以图片的形式存在。有些场合也会用到一些非 2D 图片,例如 cubemap 等。这些图片数据最终需要根据平台硬件不同转换为不同的数据格式,大多数情况下,我们会采用 ASTC 压缩格式,有时也保留无损信息。但最终的数据只存在于 VFS 的缓存中,使用者无需关心。我们使用了于 bgfx 配套的 bimg 库处理图片的数据源,常见的 png jpg 等数据格式都支持的不错。

引擎无法直接使用这些通用格式,因为还缺少一些信息。我们需要额外构造一个 .texture 文件。可以手写,也由可以工具生成。这是一个 DataList 格式的文本文件。下面是一个名为 solar-panel.texture 的贴图文件示例:

colorspace: sRGB        # 一般使用 sRGB ,少数情况使用 linear ,未来还会支持 HDR
compress:               # 发布时如何压缩图片,根据不同平台有不同配置
    android: ASTC6x6    # ASTC4x4 到 ASTC12x12 压缩比上升,同时质量有更大的损失
    ios: ASTC6x6
    windows: BC3        # BC3 为 R5G6B5A8 ,还可以选择动态调节每通道 bit 数的 BC7 。对于双通道可用 BC5 ,单通道使用 BC4 ……
# format : RGBA8        # 不填 compress 则需要定义格式
noresize: true          # 不要改变原始图片尺寸
normalmap: false        # 并非法线图
path: ../../../images/icons/item/solar-panel.png    # 图片原始文件的位置
sampler:                # 采样参数
    MAG: LINEAR         # 采样方式 LINEAR POINT ANISOTROPIC
    MIN: LINEAR
    U: WRAP             # CLAMP MIRROR BORDER WRAP
    V: WRAP
type: texture           # 贴图类型,通常为 texture
#maxsize : 1024         # 可用来设置发布时的尺寸
#mipmap : true          # 发布时生成 mipmap

它描述了一张贴图,其数据内容存在于另一个 png 文件中。其余的贴图元信息引擎需要了解,但在 png 文件中没有。

引用的这个 png 文件有一个 VFS 路径:../../../images/icons/item/solar-panel.png ,这里涉及一些引擎细节,一般的使用者不需要太关心,但这里还是展开一下:

贴图的编译过程

对于处于 C/S 模式下的 vfs 来说,运行时的程序和提供本地文件映射的 fileserver 分处两个不同的进程;如果是本地模式,同一个进程内也有两个不同的 vfs 实例。

对于引擎使用这张贴图的部分,它关联的 vfs 实例上,该 .texture 文件其实是一个目录,这个目录下有两个文件:

main.bin
source.ant

其中,main.bin 是一个二进制文件,它根据不同平台有所不同,在 windows 下,是一个 BC3 压缩的压缩数据;在 ios 下是 ASTC6x6 格式的压缩数据,等等。而 source.ant 则是转换后的元信息,还是 datalist 格式,但内容有所不同,方便引擎直接使用:

flag: "+luwvw-lSg"
info:
  bitsPerPixel: 8
  cubeMap: false
  depth: 1
  format: BC3
  height: 128
  numLayers: 1
  numMips: 1
  storageSize: 16384
  width: 128

也就是说,在游戏运行的时候,从它绑定的 VFS 上是看不到原来的 solar-panel.texture 文件的;取而代之的是,有 solar-panel.texture 这个目录,引擎可以打开 solar-panel.texture/main.binsolar-panel.texture/source.ant

而在另一侧(fileserver 进程中,或是本地模式下的资产编译模块中),有另外一个 vfs 实例。它的映射规则让它可以看到 solar-panel.texture 这个文件。这个资产编译模块,通过调用外部工具,可以将 solar-panel.texture 以及它说依赖的 solar-panel.png 图片文件编译成上面所述的那个目录。这个目录存放在本地缓存中。编译只发生在第一次加载这个 .texture 文件,如果这个文件没有发生过变动,就不会多次编译。编译结果会被软连接到另一端的 vfs 的 solar-panel.texture 目录。

模型

我们使用 .gltf/.glb 做模型文件的交换格式。这种格式主流的建模软件都支持。它不光可以表示模型还可以描述动画贴图等资产类型,这里暂且介绍模型部分。

模型文件的管理方式和贴图类似,但由于一些历史原因稍有不同(未来可能会统一)。模型文件并没有类似 .texture 的专门元信息描述文件。而直接使用 .glb 作为后缀。.glb 文件就是模型文件,大多数情况下,我们可以直接用其它创作软件生成的 .glb 文件使用。它的信息是完整的,所以不需要额外的元信息描述。

但一些情况下,我们需要对 .glb 文件做一些调整。这个调整工作是通过引擎自带的图形化编辑器完成的:例如删掉 .glb 文件中一些不要的数据,增加一些额外信息,等等。 gltf 格式本身支持这种修改( .glb 的元信息是基于 json 的,有足够的动态性),但我们不希望直接修改第三方创作软件生成的文件,因为那样,通过我们的编辑器二次加工后,就很难再送回原始创作软件再次修改而不丢失信息。

所以,我们增加了一个叫做 .patch 的文件,它是由引擎编辑器生成的(并非手写),也是 datalist 格式。在 /pkg/ant.test.simple/resource/miner-1.glb.patch 就是这样一个示例:

---
file: animations/animation.ozz
op: replace
path: /animations
value:
  work: $path ./miner.bin

它的工作原理比上面的材质编译略复杂一些,这里大略写一下细节供感兴趣的同学了解:

资产的编译链

gltf 格式其实包罗万象,它不仅仅用来表示模型,还可以描述骨骼、动画、贴图等等几乎一切 3d 渲染需要的东西。一个 gltf 文件(glb 是它的二进制形式) 包含了非常多的信息。而对于 Ant 引擎的角度看,我们希望不同类别的资产数据位于 vfs 上的多个文件中,一切皆是文件,而不是数据包。gltf 却像一个数据包。

所以,glb 在编译时,会被资产编译工具展开成一个目录,把里面的数据分别提取出来,存在不同的文件中。在游戏运行时,就像上面贴图的例子中,一张图片变成了两个文件那样,一个 glb 源文件会变成更多的文件,存放在 .glb 的同名目录下。

而 .patch 指的是对这个目录的加工。所以在 .glb 编译完成后,.patch 会触发进一步的编译过程。这里的 patch 文件的功能是修正前一步 glb 编译结果目录中的那个 animations/animation.ozz 文件。这个文件也是一个 datalist 格式的文本,而所有 datalist 都是树结构的,此处的 path: /animations 指定的是这个 datalist 文件对应的树结构中的一个节点。接下来的 value 字段中的 $path ./miner.bin 会先用源文件所在路径和后面的 ./miner.bin 合并计算出绝对路径,然后把这个节点的内容替换 (replace) 为 work: /pkg/ant.test.simple/resource/miner.bin

最终,资产编译模块,将 miner-1.glb 和 miner-1.glb.patch 两个文件编译成了一个目录:

animations
animations/animation.ozz
animations/Armature.skinbin
animations/ArmatureAction.bin
animations/skeleton.bin
animations/work.ozz
debris.prefab
hitch.prefab
images
images/miner_color.png
images/miner_color.texture
images/miner_color.texture/main.bin
images/miner_color.texture/source.ant
images/miner_light.png
images/miner_light.texture
images/miner_light.texture/main.bin
images/miner_light.texture/source.ant
images/miner_Metallic-miner_Roughness.png
images/miner_Metallic-miner_Roughness.texture
images/miner_Metallic-miner_Roughness.texture/main.bin
images/miner_Metallic-miner_Roughness.texture/source.ant
images/miner_normal.png
images/miner_normal.texture
images/miner_normal.texture/main.bin
images/miner_normal.texture/source.ant
materials
materials/Material.002.material
materials/Material.002.material/attribute.ant
materials/Material.002.material/di.bin
materials/Material.002.material/fs.bin
materials/Material.002.material/source.ant
materials/Material.002.material/varying.def.sc
materials/Material.002.material/varying.di.def.sc
materials/Material.002.material/vs.bin
materials/Material.002_PTpt.material
materials/Material.002_PTpt.material/attribute.ant
materials/Material.002_PTpt.material/di.bin
materials/Material.002_PTpt.material/fs.bin
materials/Material.002_PTpt.material/source.ant
materials/Material.002_PTpt.material/varying.def.sc
materials/Material.002_PTpt.material/varying.di.def.sc
materials/Material.002_PTpt.material/vs.bin
materials.names
mesh.prefab
meshes
meshes/BezierCurve.001_P1.ibbin
meshes/BezierCurve.001_P1.meshbin
meshes/BezierCurve.001_P1.vb2bin
meshes/BezierCurve.001_P1.vbbin
meshes/Cylinder.001_P1.ibbin
meshes/Cylinder.001_P1.meshbin
meshes/Cylinder.001_P1.vb2bin
meshes/Cylinder.001_P1.vbbin
meshes/Cylinder.005_P1.ibbin
meshes/Cylinder.005_P1.meshbin
meshes/Cylinder.005_P1.vb2bin
meshes/Cylinder.005_P1.vbbin
meshes/Cylinder_P1.ibbin
meshes/Cylinder_P1.meshbin
meshes/Cylinder_P1.vb2bin
meshes/Cylinder_P1.vbbin
meshes/Plane_P1.ibbin
meshes/Plane_P1.meshbin
meshes/Plane_P1.vb2bin
meshes/Plane_P1.vbbin
translucent.prefab
work.prefab

其中的 animations/animation.ozz 被 .patch 做了稍许修改。

动画

Ant 的骨骼动画模块使用的是 Ozz-animation

我们有两种方式生成动画资产,一种是使用专门的工具如 blender 以 gltf 格式导出动画文件;另一种是用我们的编辑器直接编辑动画。用引擎编辑器创作的动画后缀名为 .anim ,主要用来描述一些有规则的刚体动画。这里是一个文件示例,它由引擎的编辑器创作:

---
duration: 3
name: work
sample_ratio: 30
target_anims:
  ---
  clips:
    ---
    amplitude_pos: 8
    amplitude_rot: 0
    direction: 2
    random_amplitude: false
    range: {2, 79}
    repeat_count: 1
    rot_axis: 2
    tween: 6
    type: 2
  inherit: {false, false, false}
  target_name: Bone.002
  ---
  clips:
    ---
    amplitude_pos: 0
    amplitude_rot: -120
    direction: 2
    random_amplitude: false
    range: {12, 67}
    repeat_count: 8
    rot_axis: 2
    tween: 1
    type: 1
  inherit: {false, false, false}
  target_name: Bone.003
  ---
  clips:
    ---
    amplitude_pos: 0
    amplitude_rot: 27
    direction: 2
    random_amplitude: false
    range: {2, 79}
    repeat_count: 1
    rot_axis: 3
    tween: 6
    type: 2
  inherit: {false, false, false}
  target_name: Bone.004
  ---
  clips:
    ---
    amplitude_pos: 0
    amplitude_rot: 180
    direction: 2
    random_amplitude: false
    range: {1, 78}
    repeat_count: 1
    rot_axis: 2
    tween: 4
    type: 2
  inherit: {false, false, false}
  target_name: Bone.001
type: ske

它最终会经由 ozz-animation 库的代码编译为它的私有格式;用 blender 等第三方创作工具生成的包含动画的 gltf 文件也一样,同样会被编译为相同格式的数据。

在游戏运行时,引擎从 vfs 中可见的是处理后的数据文件,可以直接被 ozz-animation 模块使用。

特效

Ant 曾经实现过一套特效模块,但因为工具开发的工作量问题,转向了开源的 Effekseer 。所以,引擎编辑器不再能创作特效,而使用 Effekseer 的编辑器。

Effekseer 工具生成的是 .efk 文件,Ant 引擎不需要二次加工。

音乐和音效

Ant 继承了 fmod 。未来可能集成更多其它类似模块作为 fmod 的替代品。

和特效一样,fmod 有自带的创作工具,它生成的资产是 .bank 文件,Ant 引擎不需要二次加工。

材质

Ant 的材质文件放在 .material 文件中,/pkg/ant.test.simple/resource/skybox.material 是一个示例:

fx:
    vs: /pkg/ant.resources/shaders/sky/skybox/vs_skybox.sc
    fs: /pkg/ant.resources/shaders/sky/skybox/fs_skybox.sc
    setting:
        lighting: off
        no_predepth: true
properties:
    s_skybox:
        stage: 0
        texture: /pkg/ant.resources.test/sky/colorcube2x2.texture
    u_skybox_param: {1.0, 0.0, 0.0, 0.0}    #intensity, NOTUSE, NOTUSE, NOTUSE
state:
    ALPHA_REF: 0
    CULL: CW
    DEPTH_TEST: GEQUAL
    MSAA: true
    WRITE_MASK: RGBA

材质文件也需要经过编译,变成一个目录。其中,它引用了 .sc 着色器脚本。其编译流程的原理,可以参看前面贴图的编译部分。因为这里涉及着色器 (shader) 脚本,所以资产编译模块需要调用 bgfx 的 shaderc 完成编译。

材质文件更接近代码,是手工编写的。但有些 gltf 文件中也包含一些别的工具生成好的材质,编辑器可以通过 patch 机制替换为 Ant 引擎支持的材质,并引用其中的部分参数。

Patch

Patch 是 Asset 机制的一部分。但多用于修改材质。例如,当你需要以半透明方式渲染一个已经制作好的 prefab ,就需要定义一个半透明材质:在 state 中设置 BLEND: ALPHA 。因为引擎暂时不提供运行时修改材质的 state 的方法,所以必须离线生成。一般,我们可以复制一份 prefab ,并添加一项。

例如,有一个 floor.glb 文件,系统默认生成了 /mesh.prefab 如下:

---
data:
  scene: {}
policy:
  ant.scene|scene_object
---
data:
  material: $path materials/Material.material
  mesh: $path meshes/Cube.002_P1.meshbin
  scene: {}
  visible: true
  visible_masks: main_view|selectable|cast_shadow
mount:
  /scene/parent: 1
policy:
  ant.render|render
tag:
  Cube.037

我们可以看到,材质 material 并没有直接写在这个文件中,而是引用了内部的 materials/Material.material 。而它也是一个目录,真正的数据在 materials/Material.material/source.ant

这个文件的后半段(我们关心的 state 部分)是这样的:

state:
  ALPHA_REF: 0
  CULL: CCW
  DEPTH_TEST: GREATER
  MSAA: true
  WRITE_MASK: RGBAZ

我们需要先复制一个 mesh.prefab ,叫做 trans.prefab 。同时复制 materials/Material.material 生成一个 materials/Material_trans.material` 。然后在 materials/Material_trans.material的 state 字段添加一项BLEND: ALPHA。另外,需要添加 render_layer: translucent` 。

这一系列工作可以通过增加一个 floor.glb.patch ,剩下的工作让系统自动完成。这个 .patch 可以写成:

---
file: mesh.prefab
op: copyfile
path: trans.prefab
---
file: materials/Material.material
op: copyfile
path: materials/Material_trans.material
---
file: trans.prefab
op: replace
path: /2/data/material
value: materials/Material_trans.material
---
file: trans.prefab
op: add
path: /2/data/render_layer
value: translucent
---
file: materials/Material_trans.material
op: add
path: /state/BLEND
value: ALPHA

以上是介绍原理。这个过程比较麻烦,推荐使用编辑器的对应功能,或是写一个自动化脚本为所有需要的 glb 文件自动添加。

Clone this wiki locally