Unity Shader读书笔记(2)
Unity Shader读书笔记(2)
Unity 中的Shader
Unity一共提供了4中Unity Shader模板给我们选择——Standard Surface Shader、Unlit Shader、Image Effect Shader和Compute Shader。(其实现在的Unity已经不仅仅提供这些Shader,但书中只提到了这些。如果有机会我学习到就补上去。)
Standard Surface Shader
它会产生一个包含标准光照模型的表面着色器模板。
Unlit Shader
它会产生一个不包含光照(但包含雾效)的基本顶点/片元着色器。
Image Effect Shader
它会给我们实现各种屏幕后处理效果的基本模板。
Compute Shader
它会产生一个特殊的文件,这类Shader在利用GPU的并行性来进行一些和渲染无关的计算。
一个单独是Shader是无法发挥任何的作用,它必须和材质结合起来。
UnityShader的结构
Shader的名称
Unity Shader文件的第一行需要通过Shader的语义来指定该Unity Shader的名称。比如下面的代码:
1
Shader "MyUnityShader/FirstShader"
通过名称中添加 '/' 符号可以控制Unity Shader在材质面板中出现的位置。上面的名称在材质面板中的位置就是Shader—>MyUnityShader—>FirstShader。
Shader的Properties
Shader的Properties是材质和Unity Shader的桥梁。Properties的语义块包含了一系列的属性(property),这些属性会出现在材质面板上。
Properties语义块通常的定义如下:
1
2
3
4
5Properties{
Name ("display name", PropertyType) = DefaultValue
Name ("display name", PropertyType) = DefaultValue
// 更多属性
}具体案例:
1
2
3
4
5
6Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color",Color) = (.25, .5, .5, 1)
_BoundColor("BoundColor",Color) = (.25, .5, .5, 1)
}我们通过对这些属性进行调整来操作材质的变化。一般而言我们对这些属性的命名都是以 ‘_’ 开头如上面的"_Color"。然后我们还要对这些属性指定类型如上面的“_Color”就是Color类型的。具体的Unity Shader属性类型可以去参考Unity官网的描述ShaderLab:定义材质属性 - Unity 手册 具体的例子官网中也有写到。
PS: 在书中还有一个属性类型在我给的官网中链接中没有提及,但是你如果在官网中搜索这个类型还是可以找到对应的代码例子。我这里先补上去
1
2
3
4Properties
{
_RangeValue ("Range",Range(0.0,5.0)) = 3.0
}为了能在Shader中访问到这些属性,我们要在CG代码中定义和这些属性类型匹配的变量。其实我们不需要在Properties语义块中声明这些变量也可以在CG代码块中直接定义变量,我们也可以通过脚本向Shader传递并修改这些属性的值。所以Properties语义块的作用其实就是为了我们可以在材质面板中对其进行数值修改的操作。
SubShader
每一个Unity Shader文件至少要一个SubShader语义块。当Unity需要加载这个Unity Shader如果存在多个SubShader,Unity会扫描所有的SubShader语义块,然后选择第一个可以在目标平台运行的SubShader。如果都不支持那么Unity就会使用Fallback语义指定的Unity Shader。之所以Unity Shader支持多个SubShader是因为不同的显卡有着不同的能力。
SubShader语义块中包含的定义通常如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14SubShader{
//可选的
[Tags]
// 可选的
[RenderSetup]
Pass{
[Name]
[Tags]
[RenderSetup]
// other codes
}
// Other Passes
}SubShader语义块的具体例子(这个例子的确有点不好但是足够了。后面我有时间再换一个好一点的例子吧):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
float4 _Color;
float4 _BoundColor;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = float2(1, 0);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed3 col = _Color.rgb;
return fixed4(col,1);
}
ENDCG
}
}SubShader中定义了一系列Pass以及可选的状态([RenderSetup])和标签([Tags])设置,每个Pass定义了一次完整的渲染流程,但如果定义Pass的数目过多就会造成渲染性能下降。我个人认为所谓的完整的渲染流程就是设定了顶点着色和片元着色器。如果我们设置了RenderSetup,那么RenderSetup会影响所有的Pass语义块。标签则不同SubShader中的一些标签是特定的,这些标签的设置和Pass中使用的标签是不一样的。更加详细的关于RenderSetup的消息可以查看官网ShaderLab:命令 - Unity 手册。而关于Tags可以查看ShaderLab:向子着色器分配标签 - Unity 手册。
我们可以在Pass中定义名称方便之后我们使用shaderLab的UsePass命令直接使用其他Unity Shader中的Pass。例如:
1
Name "MYPASSNAME"
然后我们就可以使用ShaderLab的UsePass命令才直接使用其他Unity Shader中的Pass。例如:
1
UsePass "MyShader/MYPASSNAME"
通过这样的方式就可以提高代码的复用性。需要注意的是,由于Unity内部会把所有Pass的名称转换成大写字母的表述,因此在使用UsePass命令时必须使用后大写形式的名字。但是我们命名的时候可以小写。比如上面的例子中,我们也可以这样写:
1
Name "MyPassName"
调用的时候仍然可以用UsePass "MyShader/MYPASSNAME"来调用。我们依然可以给单独的Pass设定RenderSetup。此外我们还可以使用固定管线的着色器。Pass中也存在Tags。这个Tags和SubShader中的Tags不一样,但是作用也是告诉渲染引擎如何渲染该物体。更多的信息可以看官网的介绍ShaderLab:内置渲染管线中的预定义通道标签 - Unity 手册。
除了我们手动定义的Pass以外,Unity Shader还支持一些特殊的Pass,以方便代码复用和实现更加复杂的效果。更多的信息可以查看官网中的通道命令的信息:ShaderLab:命令 - Unity 手册。
Fallback
Fallback是在SubShader语义块后面的信息。它用于告诉Unity如果上面的SubShader都不能运行那么就使用这个Shader。
1
Fallback "name"
我们还可以关闭Fallback,这样如果所有的Subshader都不能在显卡中运行的话,那么引擎就不会进行渲染。
Fallback还会影响阴影的投射。在渲染阴影纹理时,Unity会在每个Unity Shader中寻找一个阴影投射的Pass。而我们不需要自己专门做一个Pass,因为FallBack使用的内置Shader中包含了这样一个通用的Pass。
ShaderLab的其他语义
如果我们不满足于Unity内置的属性类,想要自定义材质面板的编辑界面,我们可以使用CustomEditor语义来扩展编辑界面,或者是用Category语义来对Unity Shader中的命令进行分组。(我没有找到官网上的对应的例子,只能查到一些差不多相似。但是这些其实很少用到的。官网例子:自定义编辑器 - Unity 手册)
Unity Shader的形式
表面着色器
表面着色器(Surface Shader)是Unity自己创造的一种着色器类型。它的优点是所需的代码量少,缺点是渲染的代价大。本质上,它和顶点/片元着色器一样。Unity在背后仍然将其转换为顶点/片元着色器。所以我们可以说其是顶点/片元着色器更高的抽象。我们使用它就不用考虑很多的光照细节(Unity在背后会帮我们做好)
顶点/片元着色器
在Unity中,我们可以使用CG/HLSL语言来编写顶点/片元着色器。它们更加复杂但是灵活性也强。但是要注意的是Unity Shader中的CG/HLSL语言是Unity封装后的语言。所以它们和真正的CG/HLSL语言还是有一定不同。
固定函数着色器
上面两种Unity Shader形式都使用了可编程的渲染管线。但旧设备或许不支持。例如iPone 3。这时候我们就要使用固定函数着色器来完成渲染。由于先大多数GPU都支持可编程的渲染管线,这种固定管线的编程方式已经被逐渐淘汰了。实际上,在Unity5.2中,所有固定函数着色器都会在背后被Unity编译成对应的顶点/片元着色器。所以真正意义上的固定函数着色器已经不存在了。
如何选择着色器
如果你有明确的需求必须要使用固定函数着色器(比如支持一些旧的设备),否则还是使用可编程的着色器。
如果你想和各种光源打交道,那么可以使用表面着色器。只要你注意它的性能,尤其是移动端上的性能表现。
如果你需要使用的光照数目少,那么使用顶点/片元着色器。如果你有很多自定义的效果那么也是用顶点/片元着色器比较好。
PS:值得注意的是,Unity Shader 和真正意义上的Shader还是不一样的。但是我现在不想去探讨这些的区别。对我现在而言意义不大,大家可以直接去搜索文章看看就明白了。