Unity Shader是Unity为开发者提供的高级抽象渲染层,Unity希望通过这种方式让开发者更加轻松的控制渲染 如下图
Unity Shader都是使用ShaderLab来编写的,ShaderLab是Unity提供的编写Unity Shader的一种说明性语言。使用了一些嵌套在花括号内部的语义来描述一个Unity Shader文件的结构。这些结构包含了许多渲染所需要的数据,例如Properties语句中定义了着色器需要的属性,这些属性将会出现在材质面板。他定义了要显示一个材质所需要的全部东西,而不仅仅是着色器代码。
一个Unity Shader基本结构如下
Unity在背后会根据使用的平台来把这些结构编译成真正的代码和Shader文件,而开发者只需要和Unity Shader打交道。
Unity Shader结构
Shader名字:每个UnityShader文件的第一行都需要通过Shader语义来指定该Unity Shader名字,这个名字由一个字符串定义。
材质和Unity Shader的桥梁:Properties
Properties语义块中包含了一些列属性,这些属性会出现在材质面板中。Properties语义块的定义如下
声明这些属性是为了在材质面板方便的调整各种材质属性,如果我们需要在Shader中访问他们,就需要使用每个属性的名字。在Unity中这些属性的名字通常由一个下划线开始。显示的名称则是出现在材质面板的名字。我们需要为每个属性指定他的类型,常见的类型如下图。除此之外,还需要为每个属性指定一个默认值。
下面代码展示一个所有属性类型的例子
为了能在Shader中访问这些属性,我们需要在Cg代码中定义和这些属性类型相匹配的变量。即使我们不在Properties语义块中声明这些属性,也可以直接在Cg代码片中定义变量。此时,我们可以通过脚本向Shader中传递这些属性。
SubShader
每一个Unity Shader文件可以有多个SubShader语义块,但最少要有一个。当Unity需要加载UnityShader时候,会扫描所有的subShader语义块,然后选择第一个能够在目标平台上运行的SubShader。如果都不支持,就会使用Fallback语义指定的Unity Shader。提供这种语义原因在于,不同显卡具有不同的能力,旧显卡只能支持一定数目的操作指令,而一些更高级的显卡可以支持更多的指令数,那么我们希望在旧的显卡使用计算复杂度比较低的着色器,在高级显卡上使用复杂度比较高的着色器。
SubShader语义块中包含的定义通常如下
SubShader中定义了一系列Pass以及可选的状态和标签设置。每个Pass定义了一次完整的渲染流程,如果Pass数目过多,往往会造成渲染性能的下降。因此,尽量使用最小数目的Pass。状态和标签同样可以在pass中声明。不同的是,SubShader中的标签设置是特定的,也就是说,这些标签设置和Pass中使用的标签是不一样的。而对于状态设置来说,语法是相同的。但是如果我们在SubShader中进行了设置,那么将会用于所有的Pass。
状态设置
ShaderLab提供了一系列渲染状态的指令,这些指令可以设置显卡的各种状态,例如是否开启混合 深度测试等 下表给出常见的渲染状态设置。
当在SubShader中设置上述渲染状态时,会应用到所有的pass。如果不想这样,比如在双面渲染中,希望第一个pass中剔除正面来对背面渲染,在第二个pass中剔除背面来正面渲染,可以在pass语义块中单独进行上面设置。
SubShader标签
Tags是一个键值对。键和值都是字符串类型,这些键值对是对SubShader和渲染引擎之间的沟通桥梁,他们用来告诉Unity的渲染引擎:我们希望怎么样以及何时渲染这个对象。
标签结构如下:
SubShader的标签块支持的标签类型如表
上述标签只可以在SubShader中声明,不可以在Pass块中声明,Pass块虽然也可以定义标签,但这些标签不同于SubShader标签类型。
Pass语义块
可以在Pass中定义该Pass的名称 例如
通过这个名称,可以使用ShaderLab的UsePass命令来直接使用其他UnityShader中的Pass。例如
这样可以提高代码的复用性,需要注意的是,由于Unity内部会把所有的Pass名称转换大写字母表示,因此,在使用UsePass命令时必须使用大写形式的名字。
其次,我们可以对Pass设置渲染状态。SubShader的状态设置同样适用于Pass,除了上面提到的状态设置外,在Pass中还可以使用固定管线的着色器命令。Pass同样可以设置标签,但标签不同于SubShader的标签,这些标签也是用于告诉渲染引擎我们希望怎么来渲染该物体。
除了上面普通的Pass定义外,UnityShader还支持一些特殊的Pass,方便代码复用或者实现更复杂的效果,
UsePass:使用该命令复用其他Unityshader中的Pass
GrabPass:该pass负责抓取屏幕并将结果存储在一张纹理,以便于后续Pass处理。
Fallback
紧跟在各个SubShader语义后面的 可以是一个Fallback指令,用于告诉Unity 如果上面的SubShader在这块显卡上都不能运行,那么就使用这个最低级的Shader吧。
语义如下
Fallback还有影响阴影的投射,在渲染阴影纹理时,Unity会在每个Unity Shader中寻找一个阴影投射的Pass,通常情况下我们不需要自己专门实现一个Pass,这是因为Fallback使用的内置Shader中包含一个通用的Pass。
ShaderLab补充语义
CustomEditor 扩展编辑器界面 Catrgory对Shader中命令进行分组
UnityShader形式
Unity中 可以使用下面三种形式来编写Unity Shader,不管哪种形式,真正意义的shader代码都需要包含在ShaderLab语义块中。
1.表面着色器
Unity自己创造的一种着色器代码类型,需要的代码量很少,Unity在背后做了很多工作,但是渲染的带价比较大,在本质上和顶点片元着色器是一样的。表面着色器是Unity对片元顶点着色器的进一步抽象,他的价值在于Unity为我们处理了很多光照细节,使得我们不需要再操心这些光照的实现。
上述程序可以看出,表面着色器被定义在SubShader语义块中的CGPROGRAM和ENDCG之间,原因是表面着色器不需要开发者关心使用多少个Pass,每个Pass如何渲染,Unity会在背后为我们做好这些。
2.顶点片元着色器
在Unity中可以使用Cg/HLSL语言来编写顶点片元着色器,更加复杂但灵活性更高。
和表面着色器类似,顶点/片元着色器也要定义在CGPROGRAM和ENDCG之间,但不同的是,顶点/片元着色器是写在pass语义块内,而非SubShader内。原因就是,我们需要自己定义每个
Pass需要使用的Shader代码。
3固定函数着色器
固定函数着色器代码定义在Pass语义块,这些代码相当于Pass中的一些渲染设置,对于固定函数着色器来说,需要完全使用shaderLab的语法(渲染设置命令)来编写,而非Cg/HLSL
选择UnityShader形式
除非有明确的需求必须要使用固定函数着色器,需要在非常旧的设备,否则使用可编程管线的着色器。
如果你想和各种光源打交道,可以使用表面着色器,小心移动平台性能
如果需要使用光照数目很少,只有一个平行光,使用顶点/片元着色器更好
最重要的是,如果有很多自定义的渲染效果,那么就应该选择顶点/片元着色器