# 效果和材质
材质(Material)是一种用于描述物体如何被绘制的抽象,其主要目的是为艺术家提供一个界面,艺术家可以使用这个界面修改物体的表面属性,例如颜色、纹理、粗糙度等,而渲染器则可以利用这些数据忠实还原艺术家的动机。
为了使得材质能够更加通用,小游戏框架抽象了另一个名为“效果(Effect)”的概念,其可以认为是一个材质模板,通过**光照模式lightMode
和宏定义definition
**来对模板的各个功能进行开关。
# 效果-Effect
在真正的渲染过程中,物体的渲染实际上是由前面章节提到的着色器,也提过了着色器中可以用光照模式和宏定义来区分不同的分支,而效果,即engine.Effect
,则是基于着色器并且加上了一些其他可以管理渲染效果的管理器。
# 创建Effect
在着色器一节,我们实际上已经创建了一个Effect,但当时着重于描述着色器本身,在编写完shader后,就可以来关注.effect
文件本身了,将这个文件用编辑器打开,可见:
{
"shaderProperties": [
{
"key": "_Color",
"type": "Vector4",
"default": [
1,
1,
1,
1
]
}
],
"textures": [
{
"key": "_MainTex",
"type": "Texture2D",
"default": "white"
}
],
"defaultRenderQueue": 2000,
"passes": [
{
"lightMode": "ForwardBase",
"vs": "./custom.vertex.hlsl",
"ps": "./custom.pixel.hlsl",
"multiCompile": [
[
"USE_INSTANCING",
"__"
],
[
"USE_FEATURE_A",
"USE_FEATURE_B"
]
],
"compileFlags": ["Fog"],
"useMaterialRenderStates": true,
"renderStates": {
"blendOn": false
}
}
],
"editorInfo": {
"assetVersion": 2
}
}
显而易见,这其实就是一个json,开发者可以编辑里面的内容来定制一个Effect。
# 配置Passes
首先是passes
,其定义了有这个效果有多少个可渲染的Pass,Pass可以理解为一个渲染的批次,所有进入渲染的物体都可以在一个Pass中按照特定的状态被渲染一遍,如果有多个Pass,那么自然就可以被渲染多边,而决定某个批次的渲染是否要用到某些Pass最重要的就是lightMode
。
lightMode
即光照模式,是一种给开发者区分同一物体将在何种模式下被渲染的参数,内置的有ForwardBase
、ForwardAdd
、ShadowCaster
等,有一定图形编程经验的开发者对这些一定不陌生,实际上在实际的渲染驱动中,每一批次的渲染总是针对某一种lightMode
的,而在后面要讲到的内置管线中,默认也是会有个ShadowCater
-> ForwardBase
-> ForwardAdd
的顺序。当然,可以有多个Pass使用同一种lightMode
,这种情况下,它们都会在同一个批次被渲染。
决定了lightMode
,就需要指定该Pass使用的着色器了,着色器分为顶点着色器vs
和像素着色器ps
,这个在着色器一节已经论述过。
# 配置宏定义
每个Pass中都会有multiCompile
和compileFlags
,这些都表示了宏定义,它们既会影响到着色器中对应的宏,也会影响到变体的生成。
变体,即着色器在应用了不同的宏开关组合下生成的不同的最终代码,比如这么一段着色器代码:
#ifdef USING_BLACK
return float4(0, 0, 0, 1);
#else
return float4(1, 1, 1, 1);
#endif
其有一个宏USING_BLACK
,则应用或不应用这个宏将分分别生成return return float4(0, 0, 0, 1);
和return float4(1, 1, 1, 1);
两份代码,这就是两个变体。
了解了何为变体,就要知道如何才能使用宏来生成变体,让我们这个示例Effect里的变体定义:
"multiCompile": [
[
"USE_INSTANCING",
"__"
],
[
"USE_FEATURE_A",
"USE_FEATURE_B"
]
],
"compileFlags": ["Fog"],
multiCompile
用于用户自己定义的宏,每一个元素都是一个数组,数组中的字符串代表哪些组合可以联合生成变体,比如第二个元素中,就定义了USE_FEATURE_A
和USE_FEATURE_B
可以联合生成变体,而第一个元素中的__
则代表USE_INSTANCING
这个宏将单独生成变体。
compileFlags
则表示小游戏框架的内置变体,比如这里使用了内置的Fog
,开启后可以在着色器中使用APPLY_FOG(In, outColor);
来应用雾。
注意这里只是配置了某个Pass支持哪个宏定义,而实际的开关则是由全局、物体、材质三方共同决定的,开发者一般只需要控制下面会论述到的材质部分,当然也可能有进阶需求,那么就可以参考渲染器和渲染系统的相关部分。
支持的内置变体可见文章末尾的附录。
# 配置渲染状态
对于Pass而言,宏之外最重要的就是renderStates
和useMaterialRenderStates
。
renderStates
顾名思义即渲染状态,渲染状态直接对应底层图形API都会提供的一系列概念,比如混合blend
、深度depthTest
等,比如示例里就定义了blendOn: false
,即表明要关掉混合。
所有支持的渲染状态可见文章末尾的附录,这里先不赘述,这里主要要说明一个规则,即useMaterialRenderStates
这个参数的作用。
在小游戏框架的设计中,渲染状态是由Effect决定默认值,Material决定最终值的,而useMaterialRenderStates
则就是用于决定Material是否能够覆盖Effect默认值的,如果为false
,则总是使用Effect提供的默认值,如果为true
,则由下面会讲到Material的渲染状态的规则来决定。
# 配置UniformBlock
在Pass之外,Effect的另一部分就是其中的shaderProperties
和textures
,它们决定了这个Effect的各个着色器可以使用哪些Material维度的Uniform,而这个基本是对齐着色器中定义的cbuffer Material
的,在这个示例中,我们定义了:
"shaderProperties": [
{
"key": "_Color",
"type": "Vector4",
"default": [
1,
1,
1,
1
]
}
],
"textures": [
{
"key": "_MainTex",
"type": "Texture2D",
"default": "white"
}
],
所有的纹理类型Uniform都写在textures
内,其他的则写在shaderProperties
内,示例中定义了一个名为_Color
的Vector4
向量,默认值为1,1,1,1
,而后定义了一个名为_MainTex
的纹理,类型为Texture2D
,默认值为内置的white
。
这个定义在着色器中对应的cbuffer为:
cbuffer Material
{
float4 _Color;
float4 _MainTex_ST;
}
DECLARE_TEXTURE(_MainTex);
支持的Uniform类型和可用默认值可见文章末尾的附录。
# 渲染队列
由defaultRenderQueue
定义,将在下面的Material一节中详细论述。
# 材质-Material
有了Effect,就可以来创建材质Material了,和Effect不同,Material一般是在IDE中创建的:
创建完成后,在右侧的Inspector中可以有一些可供我们配置的属性。
# Effect相关
首先是Effect,之前说过可以认为Effect是一种材质模板,那么Material就可以认为是Effect的一个实例,所以首先要给其指定使用的Effect:
在这里面的Effect一栏,我们可以选择需要使用的Effect,如果是要选择自己创建的,则要选择Local Effect,然后在弹出的窗口中选择,这里我选择了方才创建的custom.effect
:
如此,Material便使用了方才的Effect。在设置好了Effect后,还有其他的两个选项renderQueue
和useInstance
。
renderQueue
即渲染顺序,决定了使用了这个Material的物体将在什么顺序被渲染,其值越大顺序越往后。这个值默认来自于Effect的defaultRenderQueue
。
注意,约定
renderQueue >= 2500
的物体为透明物体,这将影响到物体按照深度排序的规则。
useInstance
这个参数则是决定了使用该Material的物体是否能够开启GPU实例化。
GPU实例化(Instancing)是一种用于提升渲染性能的技术,其可以一批绘制大量物体,但要求物体的Mesh和Material均“完全一致”。
勾选useInstance
是简单的,但并不代表物体能够真的被实例化,需要满足很多约束条件,详见GPU实例化。
除了自定义Effect,小游戏框架还提供了许多内置的Effect,这些内置的Effect可以参考末尾附录。
# 宏定义
在Effect中,我们介绍过可以在Effect中定义一些宏来使用。而其中一部分宏的开启就可以在Material中完成,在Inspector的第二部分,即*Render Definitions一栏:
如图,点击“+”便可以增加开启一个宏,这里我开启了一个宏USE_INSTANCING
。
# 渲染状态
在宏定义之后是渲染状态:
Material的渲染状态和Effect中的定义完全一致,只不过其是用于覆盖Effect中的默认状态的,如同前面章节所言,这个覆盖只能在Effect开启了useMaterialRenderStates
的时候方能生效。
比如这里我就开启了blendOn
,并覆盖了原先默认的blendOn: false
。
# UniformBlock
最后就是针对UniformBlock的配置,也即对应Effect中的shaderProperties
和textures
。开发者可以再这里对Effect中设置好的默认值进行初始值的覆盖:
比如在这里,我就将_Color
的值覆盖为了1,0,0,1
,并且如果想,也可以将_MainTex
设置为开发者想设置的贴图。
# 在Runtime中调整
IDE中只能设置Material的初始化状态,而材质中的某些参数在运行时却是可能经常变更的,最常见的就是Uniform,当然渲染状态或者宏也可能会有一些调整。
为了让开发者能够进行方便得修改、获取这些参数,小游戏框架提供了一些方法:
// 获取和修改一个float型变量
material.getFloat(key);
material.setFloat(key, value);
// 获取和修改一个vector型变量
material.getVector(key);
material.setVector(key, value);
// 获取和修改一张贴图
material.getTexture(key);
material.setTexture(key, Texture);
// 获取和修改某个渲染状态
material.getRenderState(key);
material.setRenderState(key);
# 附录
# 内置宏
内置的宏和对应在着色器内的使用方法,请见着色器中对应的章节。
# 渲染状态
内置的渲染状态以及类型如下,其中所有的枚举值请见API文档。
interface IPassRenderStates {
// blend
blendOn: boolean;
blendSrc: engine.EBlendFactor;
blendDst: engine.EBlendFactor;
blendFunc: engine.EBlendEquation;
// cull
cullOn: boolean;
cullFace: engine.ECullMode;
// depth write
depthWrite: boolean;
// depth test
depthTestOn: boolean;
depthTestComp: engine.ECompareFunc;
// stencil write
stencilWriteMask: number;
// stencil test
stencilTestOn: boolean;
stencilRef: number;
stencilReadMask: number;
stencilComp: engine.ECompareFunc;
stencilPass: engine.EStencilOp;
stencilFail: engine.EStencilOp;
stencilZFail: engine.EStencilOp;
// PrimitiveType
primitiveType: engine.EPrimitiveType;
}
# 内置Uniform类型
在Effect可定义Uniform的类型列表如下:
Float:浮点类型
Vector2:2维浮点数组
Vector3:3维浮点类型
Vector4:4维浮点类型
Texture2D:2D纹理,可设默认值为“white”、“black”、“red”、“yellow”、“green”、“blue”、“transparent”
TextureCube:立方体纹理,可设默认值为“white”、“black”、“transparent”
# 内置Effect
内置的Effect可以分为不同的种类,有3D物体的,有粒子的,还有UI的等等,这些Effect将在不同的特定章节中论述:
3D物体的主要和光照有关,详见光照章节内相关内容。
粒子的详见粒子章节内相关内容。
UI的详见UI章节内相关内容。