材质系统总览

材质系统升级指南

Cocos Creator 从 2.x 开始就支持了材质系统,但我们在持续改进材质系统的设计和内置 Shader API,所以从 2.x 升级到 3.0 以及后续的版本升级中可能还存在一些调整。目前 Effect 资源还不支持自动升级,所以请大家参考下面的文档进行升级:

材质系统类图

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

Assets

EffectAsset

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

builtin-unlit.effect 为例,编译输出的 EffectAsset 结构大致如下:

  1. {
  2. "name": "builtin-unlit",
  3. "techniques": [{
  4. "name": "opaque",
  5. "passes": [{
  6. "program": "builtin-unlit|unlit-vs:vert|unlit-fs:frag",
  7. "properties": {
  8. "mainTexture": {
  9. "value": "grey",
  10. "type": 28
  11. },
  12. "tilingOffset": {
  13. "value": [1, 1, 0, 0],
  14. "type": 16
  15. },
  16. "mainColor": {
  17. "value": [1, 1, 1, 1],
  18. "editor": { "type": "color" },
  19. "type": 16
  20. },
  21. "colorScale": {
  22. "value": [1, 1, 1],
  23. "type": 15,
  24. "handleInfo": ["colorScaleAndCutoff", 0, 15]
  25. },
  26. "alphaThreshold": {
  27. "value": [0.5],
  28. "editor": { "parent": "USE_ALPHA_TEST" },
  29. "type": 13,
  30. "handleInfo": ["colorScaleAndCutoff", 3, 13]
  31. },
  32. "color": {
  33. "editor": { "visible": false },
  34. "type": 16, "handleInfo": ["mainColor", 0, 16]
  35. },
  36. "colorScaleAndCutoff": {
  37. "type": 16,
  38. "editor": { "visible": false, "deprecated": true },
  39. "value": [1, 1, 1, 0.5]
  40. }
  41. },
  42. "migrations": {
  43. "properties": {
  44. "mainColor": { "formerlySerializedAs": "color" }
  45. }
  46. }
  47. }]
  48. }],
  49. "shaders": [{
  50. "name": "builtin-unlit|unlit-vs:vert|unlit-fs:frag",
  51. "hash": 2093221684,
  52. "glsl4": {
  53. "vert": "// glsl 460 vert source, omitted here for brevity",
  54. "frag": "// glsl 460 frag source, omitted here for brevity",
  55. },
  56. "glsl3": {
  57. "vert": "// glsl 300 es vert source, omitted here for brevity",
  58. "frag": "// glsl 300 es frag source, omitted here for brevity",
  59. },
  60. "glsl1": {
  61. "vert": "// glsl 100 vert source, omitted here for brevity",
  62. "frag": "// glsl 100 frag source, omitted here for brevity",
  63. },
  64. "attributes": [
  65. { "tags": ["USE_BATCHING"], "name": "a_dyn_batch_id", "type": 13, "count": 1, "defines": ["USE_BATCHING"], "location": 1 },
  66. { "name": "a_position", "type": 15, "count": 1, "defines": [], "location": 0 },
  67. { "name": "a_weights", "type": 16, "count": 1, "defines": ["USE_SKINNING"], "location": 2 },
  68. { "name": "a_joints", "type": 16, "count": 1, "defines": ["USE_SKINNING"], "location": 3 },
  69. { "tags": ["USE_VERTEX_COLOR"], "name": "a_color", "type": 16, "count": 1, "defines": ["USE_VERTEX_COLOR"], "location": 4 },
  70. { "tags": ["USE_TEXTURE"], "name": "a_texCoord", "type": 14, "count": 1, "defines": ["USE_TEXTURE"], "location": 5 }
  71. ],
  72. "varyings": [
  73. { "name": "v_color", "type": 16, "count": 1, "defines": ["USE_VERTEX_COLOR"], "location": 0 },
  74. { "name": "v_uv", "type": 14, "count": 1, "defines": ["USE_TEXTURE"], "location": 1 }
  75. ],
  76. "builtins": {
  77. "globals": {
  78. "blocks": [
  79. { "name": "CCGlobal", "defines": [] }
  80. ],
  81. "samplers": []
  82. },
  83. "locals": {
  84. "blocks": [
  85. { "name": "CCLocalBatched", "defines": ["USE_BATCHING"] },
  86. { "name": "CCLocal", "defines": [] },
  87. { "name": "CCSkinningTexture", "defines": ["USE_SKINNING", "ANIMATION_BAKED"] },
  88. { "name": "CCSkinningAnimation", "defines": ["USE_SKINNING", "ANIMATION_BAKED"] },
  89. { "name": "CCSkinningFlexible", "defines": ["USE_SKINNING"] }
  90. ],
  91. "samplers": [
  92. { "name": "cc_jointsTexture", "defines": ["USE_SKINNING", "ANIMATION_BAKED"] }
  93. ]
  94. }
  95. },
  96. "defines": [
  97. { "name": "USE_BATCHING", "type": "boolean", "defines": [] },
  98. { "name": "USE_SKINNING", "type": "boolean", "defines": [] },
  99. { "name": "ANIMATION_BAKED", "type": "boolean", "defines": ["USE_SKINNING"] },
  100. { "name": "CC_SUPPORT_FLOAT_TEXTURE", "type": "boolean", "defines": ["USE_SKINNING", "ANIMATION_BAKED"] },
  101. { "name": "USE_VERTEX_COLOR", "type": "boolean", "defines": [] },
  102. { "name": "USE_TEXTURE", "type": "boolean", "defines": [] },
  103. { "name": "FLIP_UV", "type": "boolean", "defines": ["USE_TEXTURE"] },
  104. { "name": "CC_USE_HDR", "type": "boolean", "defines": [] },
  105. { "name": "USE_ALPHA_TEST", "type": "boolean", "defines": [] },
  106. { "name": "ALPHA_TEST_CHANNEL", "type": "string", "defines": ["USE_ALPHA_TEST"], "options": ["a", "r", "g", "b"] }
  107. ],
  108. "blocks": [
  109. {
  110. "name": "TexCoords",
  111. "defines": ["USE_TEXTURE"],
  112. "binding": 0,
  113. "members": [
  114. { "name": "tilingOffset", "type": 16, "count": 1 }
  115. ]
  116. },
  117. {
  118. "name": "Constant",
  119. "defines": [],
  120. "binding": 1,
  121. "members": [
  122. { "name": "mainColor", "type": 16, "count": 1 },
  123. { "name": "colorScaleAndCutoff", "type": 16, "count": 1 }
  124. ]
  125. }
  126. ],
  127. "samplers": [
  128. { "name": "mainTexture", "type": 28, "count": 1, "defines": ["USE_TEXTURE"], "binding": 30 }
  129. ]
  130. }
  131. ]
  132. }

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

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

Material

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

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

代码示例:

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

有了这些信息后,Material 就可以被正确初始化,正确初始化的标志是生成渲染使用的 Pass 对象数组,可用于具体模型的渲染。

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

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

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

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

  1. const comp = someNode.getComponent(MeshRenderer);
  2. comp.material = mat;
  3. comp.setMaterial(mat, 0); // 与上一行作用相同

根据子模型的数量,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,从而实现独立的定制。

  1. const comp2 = someNode2.getComponent(MeshRenderer);
  2. const mat2 = comp2.material; // 拷贝实例化,mat2 是一个 MaterialInstance,接下来对 mat2 的修改只会影响 comp2 的模型

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

对于 MaterialInstance,可以修改 defines 或 states,只提供重载即可:

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

每帧动态更新 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。

Builtins

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

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

Standard

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

参数说明
tilingOffset模型 UV 的平铺和偏移量,xy 对应平铺,zw 对应偏移
albedo/mainColor漫反射颜色,指定模型的主要基色
albedoMap/mainTexture漫反射贴图,如果有指定,这项会和漫反射颜色相乘
albedoScale模型的漫反射强度,用于控制漫反射颜色对于最终颜色的影响权重
alphaThreshold启用 alpha test 后的测试阈值。输出的 alpha 值低于此值的像素会被 discard 掉
normalMap法线贴图,用于增加表面细节
normalStrenth法线贴图强度,控制凹凸质感的强弱
pbrMap
R(AO)
G(Roughness)
B(Metallic)
PBR 材质参数贴图:环境遮挡、粗糙度和金属度
采样结果会和常数项相乘
metallicRoughnessMap
G(Roughness)
B(Metallic)
独立的粗糙度和金属度贴图
采样结果会和常数项相乘
occlusionMap独立的环境遮挡贴图
采样结果会和常数项相乘
occlusion环境遮挡常数
roughness粗糙度常数
metallic金属度常数
emissive自发光颜色,独立于光照计算,由模型本身直接发散出的颜色
emissiveMap自发光贴图
如果有指定,这项会和自发光颜色相乘,因此需要把自发光颜色(默认是黑色)调高才会有效果
emissiveScale自发光强度
用于控制自发光颜色对于最终颜色的影响权重

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

宏定义说明
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是否使用自发光贴图