一个Unity Shader的基础结构如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
Shader "ShaderName"{
Properties{
//属性
}
SubShader{
//显卡A使用的子着色器
}
SubShader{
//显卡B使用的子着色器
}
Fallback"VertexLit"
}

Unity会根据目标平台来把这些结构编译成真正的代码和Shader文件。

Shader名

每一个Unity Shader文件的第一行都需要通过“Shader”语义来指定该Unity Shader的名字。

1
Shader"Custom/MyShader"{ }

shader_name

Properties

Properties语义块中包含了一系列属性(Property)这些属性将会出现在材质面板中。

Properties语义块定义:

1
2
3
4
5
Properties{
Name {"display name",PropertyType} = DefaultValue
Name {"display name",PropertyType} = DefaultValue
//更多属性
}

Name:Shader声明的属性名,供Shader内部使用。这些属性名通常由一个下划线开始。

display name:材质面板中显示的名字。

PropertyType:属性的本质类型。

属性类型 默认值的定义语法 例子
Int number _Int (“Int”, Int) = 2
Float number _Float (“Float”, Float) = 1.5
Range(min,max) number _Range (“Range”, Range(0.0,5.0)) = 3.0
Color (number,number,number,number) _Color (“Color”, Color) = (1,1,1,1)
Vector (number,number,number,number) _Vector (“Vector”, Vector) = (2,3,6,1)
2D “defaulttexture”{} _2D (“2D”, 2D) = “” {}
Cube “defaulttexture”{} _Cube (“Cube”, Cube) = “white” {}
3D “defaulttexture”{} _3D (“3D”, 3D) = “black” {}

2D、Cube、3D属于纹理类型,它们的默认值是通过一个字符串后跟一个花括号来指定的。

字符串可以为空,也可以设置为内置的纹理名称:“white”、“black”、“gray”或者“bump”。

花括号内部原本是用于指定一些纹理属性。在老版本Unity固定管线中比较有用。新版本Unity移除了这些纹理选项。只能通过编写顶点着色器代码来代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Shader "Custom/ShaderLabProperties" {
Properties {
//Numbers and Sliders
_Int ("Int", Int) = 2
_Float ("Float", Float) = 1.5
_Range ("Range", Range(0.0,5.0)) = 3.0
//Colors and Vectors
_Color ("Color", Color) = (1,1,1,1)
_Vector ("Vector", Vector) = (2,3,6,1)
//Textures
_2D ("2D", 2D) = "" {}
_Cube ("Cube", Cube) = "white" {}
_3D ("3D", 3D) = "black" {}
}
Fallback "Diffuse"
}

shaderlab_properties

有时,我们想要在材质面板上显示更多类型的变量,可以使用Unity编辑器拓展来拓展材质面板。

Redify.Shader

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
Shader "Unity Shaders Book/Chapter 3/Redify" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200

CGPROGRAM
#pragma surface surf Lambert addshadow
//自定义编辑器中要访问的属性
#pragma shader_feature REDIFY_ON

sampler2D _MainTex;

struct Input {
float2 uv_MainTex;
};

void surf (Input IN, inout SurfaceOutput o) {
half4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;

//应用属性
#if REDIFY_ON
o.Albedo.gb *= 0.5;
#endif
}
ENDCG
}
CustomEditor "CustomShaderGUI"
}

Editor文件夹内的CustomShaderGUI脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CustomShaderGUI : ShaderGUI
{
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
// render the default gui
base.OnGUI(materialEditor, properties);

Material targetMat = materialEditor.target as Material;

// see if redify is set, and show a checkbox
bool redify = Array.IndexOf(targetMat.shaderKeywords, "REDIFY_ON") != -1;
EditorGUI.BeginChangeCheck();
redify = EditorGUILayout.Toggle("Redify material", redify);
if (EditorGUI.EndChangeCheck())
{
// enable or disable the keyword based on checkbox
if (redify)
targetMat.EnableKeyword("REDIFY_ON");
else
targetMat.DisableKeyword("REDIFY_ON");
}
}
}

Redify material

需要注意的是,这个自定义编辑器中访问的Shader属性是写在Shader的Cg代码片(CGPROGRAM)内的。我们的Properties语义块的作用仅仅是为了让这些属性可以出现在材质面板中

SubShader

每一个Unity Shader文件可以包含多个SubShader语义块,但最少要有一个。

当Unity需要加载这个Unity Shader时,Unity会扫描所有的SubShader语义块,然后选择第一个能够在目标平台上运行的SubShader。如果都不支持的话,Unity就会使用Fallback语义指定的Unity Shader

SubShader的意义是在旧的显卡使用计算复杂度较低的着色器,在高级的显卡使用复杂度高的着色器。

SubShader的语义块定义:

1
2
3
4
5
6
7
8
9
10
11
SubShader{
//可选的
[Tags]
//可选的
[RenderSetup]

Pass{

}
//其他的Passes
}

SubShader中定义了一系列Pass以及可选的状态([RenderSetup])标签([Tags])设置。

每个Pass定义了一次完整的流程,Pass过多会造成渲染性能的下降。

RenderSetup和Tags同样可以在Pass中声明。不同的是,SubShader中的一些Tags是特定的,这个Tag和Pass中使用的不一样。而RenderSetup在Pass中和SubShader中语法都一样,只不过SubShader的RenderSetup作用于所有的Pass。

状态(RenderSetup)设置

常见的状态设置选项

状态名称 设置指令 解释
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的Tags

SubShader的Tags是一个键值对(Key/Value Pair),它的键和值都是字符串类型。它们用来告诉渲染引擎:我希望怎样以及何时渲染这个对象。

标签的结构:

1
Tags { "TagName1" = "Value1" "TagName2" = "Value2" }

SubShader的标签块支持的标签类型:

标签类型 说明 例子
Queue 控制渲染顺序,指定该物体属于哪一个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染,我们也可以自定义使用的渲染队列来控制物体的渲染顺序 Tags { “Queue” = “Transparent” }
RenderType 对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色器等。这可以被用于着色器替换(Shader Replacement)功能 Tags { “RanderType” = “Opaque” }
DisableBatching 一些SubShader在使用Unity的批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画。这时可以通过该标签来直接指明是否对该SubShader使用批处理。 Tags { “DisableBatching” = “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” }

上述标签仅可以在SubShader中声明,不可以在Pass块中声明。Pass块中的标签是不同于SubShader标签的类型。

Pass语义块

Pass语义块的定义

1
2
3
4
5
6
Pass{
[Name]
[Tags]
[RenderSetup]
//Other Code
}

首先,我们可以Pass中定义该Pass的名称,例如:

1
Name "MyPassName"

通过这个名称可以使用ShaderLab的UsePass命令来直接使用其他UnityShader中的Pass。例如:

1
UsePass "MyShader/MYPASSNAME"

Unity内部会把所有Pass的名称转换成大写字母表示。因此,在使用UsePass命令时必须使用大写形式的名字。

我们可以对Pass设置RenderSetup,它可使用和上述SubShader相同的状态设置,除此之外在Pass之中还可以使用固定渲染管线着色器命令。

Pass中的标签不同于SubShader的标签。这些标签也是用于告诉引擎我们希望怎样来渲染物体。

标签类型 说明 例子
LightMode 定义该Pass在Unity渲染流水线中的角色 Tags { “LightMode” = “ForwardBase” }
RequireOptions 用于指定当满足某些条件时才渲染该Pass,它的值是一个由空格分开的字符串。目前,Unity支持的选项有:SoftVegetaion。 Tags { “RequireOptions” = “SoftVegetation” }

除了上面普通的Pass定义之外,Unity Shader还支持一些特殊的Pass

  • UsePass:使用该命令来复用其他UnityShader中的Pass
  • GrabPass:该Pass负责抓取屏幕并将结果存储在一张纹理中,用于后续的Pass处理。

FallBack

如果SubShader在当前显卡都不能运行,可以使用Fallback指令来让Unity返回一些适应性较广的Shader预设。

1
2
3
Fallback "name"
//或者
Fallback Off

我们可以通过一个字符串来指定这个预设。也可以直接关闭Fallback。

1
Fallback "VertexLit"//顶点照明

Fallback还会影响阴影的投射。在渲染阴影纹理时,Unity会在每个UnityShader中寻找一个阴影投射的Pass。通常情况下,我们不需要自己专门实现一个PassFallback的预设中包含了投射阴影的通用Pass

ShaderLab其他语义

CustomEditor:拓展编辑器界面

1
CustomEditor "Editor_Extension_Script_Name"

引号内指定应用此Shader一些参数来拓展编辑器的编辑器拓展脚本的名称。

Category:对UnityShader中的命令进行分组。