材质系统总览

    材质系统控制着每个模型最终的着色流程与顺序,在引擎内相关类间结构如下:

    EffectAsset 是由用户书写的着色流程描述文件,详细结构及书写指南可以参考 。
    这里主要介绍引擎读取 EffectAsset 资源的流程:
    在编辑器导入 EffectAsset 时,会对用户书写的内容做一次预处理,替换 GL 字符串为管线内常量,提取 shader 信息,转换 shader 版本等。

    以 为例,编译输出的 EffectAsset 结构大致如下:

    这里的信息量不小,但大多数时候这些细节都不需要普通开发者关心,重要的是:

    • 对任意目标平台,所有着色必要的基础信息全部都在这里提前准备好,以保证跨平台和最高的运行效率。
    • 同时在最后构建时会针对当前平台剔除所有冗余的信息,以保证最好的空间利用率。

    Material 资源可以看成是 EffectAsset 在场景中的资源实例,它本身的可配置参数包括:

    • effectAsseteffectName:effect 资源引用,指定使用哪个 EffectAsset 所描述的流程进行渲染。(必备)
    • technique:指定使用 EffectAsset 中的第几个 technique,默认为第 0 个。
    • defines:宏定义列表,指定开启哪些宏定义,默认全部关闭。
    • states:管线状态重载列表,指定对渲染管线状态(深度模板透明混合等)有哪些重载,默认与 effect 声明一致。

    代码示例:

    1. mat.initialize({
    2. effectName: 'pipeline/skybox',
    3. defines: {
    4. USE_RGBE_CUBEMAP: true
    5. }
    6. });

    根据所使用 EffectAsset 的信息,可以进一步设置每个 Pass 的 uniform 等参数。

    1. mat.setProperty('cubeMap', someCubeMap);
    2. console.log(mat.getProperty('cubeMap') === someCubeMap); // true

    这些属性都是在材质资源对象本身内部生效,并不涉及场景。

    要将 Material 应用到特定的模型上,需要将其挂载到一个 上,所有需要设定材质的 Component(MeshRenderer、SkinnedMeshRenderer 等)都继承自它。

    根据子模型的数量,Renderable 也可以引用多个 Material 资源:

    1. comp.setMaterial(mat, 1); // 赋给第二个 submodel

    同一个 Material 也可挂载到任意多个 Renderable 上,一般在编辑器中通过拖拽的方式即可自动赋值。

    1. const comp2 = someNode2.getComponent(MeshRenderer);
    2. comp2.material = mat; // the same material above

    而当场景中某个模型的 Material 需要自定义一些属性时,会在从 Renderable 获取 Material 时自动做拷贝实例化,创建对应的 MaterialInstance,从而实现独立的定制。

    Material 与 MaterialInstance 的最大区别在于,MaterialInstance 从一开始就永久地挂载在唯一的 Renderable 上,且只会对这个模型生效,而 Material 则无此限制。

    1. mat2.recompileShaders({
    2. USE_EMISSIVE: true
    3. mat2.overridePipelineStates({
    4. rasterizerState: {
    5. cullMode: GFXCullMode.NONE
    6. });

    每帧动态更新 uniform 值是非常常见的需求,在类似这种需要更高效接口的情况下,可以手动调用对应 pass 的接口:

    1. // 初始化时保存以下变量
    2. const pass = mat2.passes[0];
    3. const hColor = pass.getHandle('albedo');
    4. const color = new Color('#dadada');
    5. // 每帧更新时:
    6. color.a = Math.sin(director.getTotalFrames() * 0.01) * 127 + 127;
    7. pass.setUniform(hColor, color);

    除此之外的其他任何修改(effect 或 technique 的变化等)都需要重新创建 Material 并赋值给目标 RenderableComponent。

    编辑器内置了几种常见类型的材质,包括无光照的 unlit、基于物理光照的 standard、skybox、粒子、sprite 等。

    作为参考,下图是 材质各着色参数的组装流程:

    Standard

    以下是对应参数和宏定义的完整列表:

    相对应的,还有控制这些参数的宏定义:

    宏定义说明
    USE_BATCHING是否启用动态 VB 合并式合批
    USE_INSTANCING是否启用动态 instancing
    HAS_SECOND_UV是否存在第二套 UV
    ALBEDO_UV指定采样漫反射贴图使用的 uv,默认为第一套
    EMISSIVE_UV指定采样自发光贴图使用的 uv,默认为第一套
    ALPHA_TEST_CHANNEL指定透明测试的测试通道,默认为 A 通道
    USE_VERTEX_COLOR如果启用,顶点色会与漫反射项相乘
    USE_ALPHA_TEST是否开启透明测试(镂空效果)
    USE_ALBEDO_MAP是否使用漫反射贴图
    USE_NORMAL_MAP是否使用法线贴图
    USE_PBR_MAP是否使用 PBR 参数三合一贴图(按 glTF 标准,RGB 通道必须分别对应遮挡、粗糙和金属度
    USE_METALLIC_ROUGHNESS_MAP是否使用金属粗糙二合一贴图(按 glTF 标准,GB 通道必须分别对应粗糙和金属度
    USE_OCCLUSION_MAP是否使用遮挡贴图(按 glTF 标准,只会使用 R 通道
    USE_EMISSIVE_MAP是否使用自发光贴图