Unity Shader模板
- Standard Surface Shader:包含了标准光照模型(使用基于物理的渲染方法)的表面着色器模板
- Unlit Shader:不包含光照(但包含雾效)的基本的顶点/片元着色器
- Image Effect Shader:为实现各种屏幕后处理效果提供了一个基本模板
- Compute Shader:产生一种特殊的Shader文件,这类Shader旨在利用GPU的并行性来进行一些与常规渲染流水线无关的计算
ShadeLab
Unity Shader为控制渲染过程提供了一层抽象ShaderLab,是Unity提供的编写Unity Shader的一种说明性语言。它使用了一些嵌套在花括号内部的语义(syntax)来描述一个Unity Shader文件的结构。这些结构包含了许多渲染所需的数据,定义了要显示一个材质所需的所有东西,而不仅仅是着色器代码。
Unity Shader结构
Shader "Custom/MyShader"
{
Properties
{
Name("display name",PropertyType) = DefaultVal
}
SubShader
{
//可选的
[Tags]
//可选的
[RenderSetup]
Pass{
}
//other Passes
}
//其它SubShader
Fallback "OtherShader"
}
Shader名字
可以以"/"分割控制层次结构。
Properties:材质和Unity Shader的桥梁
在Unity中,这些属性的名字通常由一个下划线开始。显示的名称(display name)则是出现在材质面板上的名字。需要为每个属性指定它的类型(PropertyType),还需要为每个属性指定一个默认值。
Properties语义块支持的属性类型
属性娄型 | 默认值的定义语法 | 例子 |
---|---|---|
Int | number | _Int(“Int”,Int) = 2 |
Float | number | _Float(“Float”,Float) = 1.5 |
Range(min,max) | number | _Range(“Range”,Range(0.0,0.5)) = 1.5 |
Color | (number,number,number,number) | _Color(“Color”,Color = (1,1,1,1) |
Vector | (number,number,number,number) | _Vector(“Vector”,Vector = (1,2,3,4) |
2D | “defaultTex”{} | _2D(“2D”,2D = “”{} |
Cube | “defaultTex”{} | _Cube(“Cube”,Cube = “white”{} |
3D | “defaultTex”{} | _3D(“3D”,3D = “black”{} |
内置纹理名称:如"white"“black”“gray"或者"bump”
为了在Shader中可以访问到这些属性,我们需要在CG代码片中定义和这些属性类型相匹配的变量。需要说明的是,即使不在Properties语义块中声明这些属性。也可以直接在CG代码片中定义变量。此时,可以通过脚本向Shader传递这些属性。因此properties语义块的作用仅仪是为了让这些属性可以出现在材质面板。
SubShader
每一个Unity Shader文件可以包含多个SubShader语义块,但最少要有一个。当Unity需要加载这个Unity Shader时,Unity会扫描所有的SubShader语义块,然后选择第一个能够在目标平台上运行的SubShader。如果都不支持的话,Unity就会使用Fallback语义指的Unity Shader。
Sub Shader语义块中包含的定义通常如下:
SubShader
{
//可选的
[Tags]
//可选的
[RenderSetup]
Pass{
}
//other Passes
}
Sub Shader中定义了一系列Pass以及可选的状态([RenderSetup])和标签([Tags])设置。每个Pass定义了一次完整的渲染流程,但如果Pass的教目过多,往往会造成渲染性能的下降。因此,应尽量使用最小数目的Pass。状态和标签同样可以在Pass声明。不同的是,Sub Shader中的一些标签设置是特定的。也就是说,这些标签设置和Pass中使用的标签是不一样的。而对于状态设置来说,其使用的语法是相同的。但是,如果我们在SubShader进行了这些设置,那么将会用于所有的Pass。
状态设置
这些指令可以设置显卡的各种状态。
状态名称 | 设置指令 | 解 释 |
---|---|---|
Cull | Cull Back/Front/Off | 设置剔除模式:剔除背面/正面/关闭 |
ZTest | ZTest Less/Greater/LEqual/GEqual/Equal/NotEqual/Always | 设置深度测试时使用的函数 |
ZWrite | ZWrite On/Off | 开启/关闭深度写入 |
Blend | Blend SrcFactor DstFactor | 开启并设置混合模式 |
当在SubShader块中设置了上述渲染状态时,将会应用到所有的Pass。如果不想这样(例如在双面渲染中,希望在第一个Pass中剔除正面来对背面进行渲染,在第二个Pass中剔除背面来对正面进行渲染),可以在Pass语义块中单独进行上面的设置。
SubShader的标签
Sub Shader的标签(Tags)是一个键值对,它的键和值都是字符串类型。这些键值对是Sub Shader和渲染引擎之间的沟通桥梁。它们用来告诉Unity的渲染引擎:SubShader希望怎样以及何时渲染这个对象。
标签的结构如下:
Tags {TagName1 = “Valuel” “TagName2” = “Value2”}
SubShader的标签类型(仅可以在SubShader中声明)
标签类型 | 说明 | 例子 |
---|---|---|
Queue | 控制渲染顺序,指定该物体属于哪一个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染,也可以自定义使用的渲染队列来控制物体的渲染顺序 | Tags{“Queue”=“Transparent”} |
RenderType | 对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色嚣等。这可以被用于着色嚣替换(Shader Replacement)功能 | Tags{“RenderType” = “Opaque” } |
DisableBatching | 一些SubShader在使用Unity的批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动面。这时可以通过该标签来直接指明是否对该SubShader使用批处理 | Tags {“DisableBaiching” = “True”} |
ForceNoShadowCasting | 控制使用该SubShader的物体是否会投射阴影 | Tags{ “ForceNoShadowCasting” = “true” |
IgnoreProjector | 如果该标签值为"True",那么使用该SubShader的物体将不会受Projector的影响。通常用于半透明物体 | Tags{“IgnoreProjector”=“True”} |
CanUseSpriteAtlas | 当该SubShader是用于精灵(sprites)时,将该标签设为"False" | Tags{ “CanUseSpriteAtlas” = “false” |
PreviewType | 指明材质面板将如何预览该材质。默认情况下,材质将显示为一个球形,我们可以通过把该标签的值设为"Plane""SkyBox"来改变顸览类型 | Tags{ “PreviewType” = “Plane” |
Pass语义块
Pass话义块包含的语义如下:
Pass{
[Name]
[Tags]
[RenderSetup]
//other code
}
名称
定义该Pass的名称,例如:
Name “MyPassName”
通过这个名称,可以使用ShaderLab的UsePass命令来直接使用其他Shader中的Pass。例如:
UsePass “MyShader/MYPASSNAME”(内部会把所有的名称转换成大写字母的表示)。
Pass标签
这些标签也是用于告诉渲染引擎希望怎样来渲染该物体。
Pass的标签类型
标签类型 | 说明 | 例子 |
---|---|---|
LightMode | 定义该Pass在Unity的渲染流水线中的色 | Tags{“LightMode”= “ForwardBase”} |
RequireOptions | 用于指定当满足某些条件时才渲染该Pass,它的值是一个由空格分隔的字符串目前,Unity支持的选项有:SoftVegetation | Tags{“RequireOptions” = “SoftVegetation” } |
Unity Shader还支持一些特殊的Pass:
- UsePass:可以使用该命令来复用其他Unity Shader中的Pass
- GrabPass:该Pass负责抓取屏幕并将结果存储在一张纹理中。以用于后续的Pass处理
Fallback
Fallback “name” //如果上面所有的SubShader在这块显卡上都不能运行,那么就使用这个最低级的Shader
Fallloack Off //如果一块显卡跑不了上面所有的SubShader,那就不要管它了
Fallback还会影响阴影的投射。在渲染阴影纹理时,Unity会在每个Unity Shader中寻找一个阴影投射的Pass。通常情况下,不需要自己专门实现一个Pass,这是因为Fallback使用的内置Shader中包含了这样一个通用的Pass。因此,为每个Unity Shader正确设置Fallback是非常重要的。
Unity Shader形式
可以使用下面3种形式来编写Unity Shader。
Shader "MyShader"{
Properties{
//所需的各种属性
}
SubShader {
//真正意义上的Shader代码出现在这里
//最面着色器(Surface Shader)或者
//顶点/片元着色器(Vertex/Fragment Shader)或者
//固定函数着色器(Fixed Function Shader)
}
SubShader {
//和上一个SubShadar类似
}
}
表面着色器
表面着色器(Surface Shader)是Unity自己创造的一种着色器代码类型。它需要的代码量很少,Unity在背后做了很多工作,但渲染的代价比较大。它在本质上和顶点/片元着色器是一样的。也就是说。当给Unity提供一个表面着色器的时候,它在背后仍旧把它转换成对应的顶点/片元着色器。可以理解成,表面着色器是Unity对顶点/片元着色器的更高一层的抽象。它存在的价值在于。Unity为我们处理了很多光照细节,使得我们不需要再操心这些"烦人的事情"。
Shader "Custom/MySurfaceShader" {
Properties {
}
SubShader {
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Lambert
struct Input {
float4 color : COLOR;
};
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = 1;
}
ENDCG
}
FallBack "Diffuse"
}
表面着色器被定义在Sub Shader语义块(而非Pass语义块)的CGPROGRAM和ENDCG之间。原因是,表而着色器不需要开发者关心使用多少个Pass、每个Pass如何渲染等问题,Unity在背后为我们做好这些事情,我们要做的只是告诉它:“使用这些纹理去填充颜色,使用这个法线纹理去填充法线,使用Lambert光照模型,其他的不要来烦我!”。
CGPROGRAM和ENDCG之I可的代码是使用CG/HLSL编写的。
顶点/片元着色器(Vertex/Fragment Shader)
Shader "Unlit/FirstUnlitShader"
{
Properties{
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
float4 vert (float4 v : POSITION) : SV_POSITION{
return UnityObjectToClipPos(v);
}
fixed4 frag() : SV_Target{
return fixed4(1,0,0,1);
}
ENDCG
}
}
}
顶点/片元着色器是写在Pass语义块内,可能需要编写更多的代码,但带来的妤处是灵活性很高。
选择哪种Unity Shader形式
- 除非有非常明确的需求必须要使用固定函数着色器,例如需要在非常旧的设备上运行游戏(这些设备非常少见).否则使用可编程管线的着色器,即表面着色器或顶点/片元着色器。
- 如果想和各种光源打交道,可能更喜欢使用表面着色器,但需要小心它在移动平台的性能表现。
- 如果需要使用的光照数目非常少,例如只有一个平行光,那么使用顶点/片元着色器是一个更好的选择。
- 如果有很多自定义的渲染效果,选择顶点/片元着色器。
Unity Shader与传统Shader
- 在传统的Shader中,仅可以编写特定类型的Shader,例如顶点着色器、片元着色器等。而在Unity Shader中,可以在同一个文件里同时包含需耍的顶点着色器和片元着色器代码。
- 在传统的Shader中,无法设置一些渲染设置,例如是否开启混合、深度测试等,这些是开发者在另外的代码中自行设置的。而在Unity Shader中,通过一行特定的指令就可以完成这些设置。
- 在传统的Shader中,需要编写冗长的代码来设置着色器的输入和输出,要小心地处理这些输入输出的位置对应关系等。而在Unity Shader中,只需要在特定语句块中声明一些属性,就可以依靠材质来方便地改变这些属性。而且对于模型自带的数据(如顶点位置、纹理坐标、法线等),Unity Shader也提供了直接访问的方法,不需要开发者自行编码来传给着色器。
Unity Shader缺点
- 可以编写的Shader类型和语法都被限制了。对于一些类型的Shader.例如曲面细分着色器、几何着色器等,Unity的支持就相对差一些。
- 一些高级的Shader语法Unity Shader也不支持。