Unity Shader基础
1 Unity Shader概述
1.1 Unity中配合使用材质和Shader的常见流程
- 创建一个材质
- 创建一个Unity Shader
- 把材质赋给需要渲染的对象
- 在材质面板中调整Unity Shader的属性,以得到满意的效果
Unity Shader定义了渲染所需的各种代码、属性和指令,而材质则允许我们调节这些属性,并将其赋值给相应的模型
1.2 Unity提供的Shader模板
- Standard Surfacel Shader:产生一个包含标准光照模型的表面着色器模板
- Unlit Shader:产生一个不包含光照的基本的顶点/片元着色器
- Image Effect Shader:为实现各种屏幕后处理效果提供一个基本模板
- Compute Shader:产生一种特殊的Shader文件,利用CPU的并行性来进行一些与渲染流水线无关的计算
通常使用Unlit Shader来生成一个基本的顶点/片元着色器模板
Shader需要与材质结合才能发挥作用,在材质下方选择需要执行的Unity Shader,材质中才会出现Unity Shader的各种属性
(颜色、纹理、浮点数、滑动条等),当把材质赋给场景中一个对象时,就可以看到调整属性所发生的视觉变化
2 ShaderLab
在Unity中,所有的Shader都是使用ShaderLab来编写的,ShaderLab是Unity提供的编写Unity Shader的一种说明性语言
// 一个Unity Shader的基础结构
Shader "ShaderName"{
Properties{
// 属性
}
SubShader{
// 显卡A使用的子着色器
}
SubShader{
// 显卡B使用的子着色器
}
Fallback "VertexLit"
}
3 Unity Shader的结构
3.1 为Shader取名
在Unity Shader文件的第一行通过Shader语义来指定该Unity Shader的名字,由字符串定义
通过在字符串中加“/”可以控制Unity Shader在材质面板中出现的位置
Shader "Costom/MyShader"{} // 在材质面板中的位置Shader->Costom->MyShader中出现
3.2 材质和Shader的桥梁:Properties
Properties{
Name ("display name", PropertyType) = DefaultValue
Name ("display name", PropertyType) = DefaultValue
// 更多属性
}
声明这些属性是为了在材质面板中能够更方便德调整各种材质属性
想要在Shader中访问它们,就需要使用每个属性的名字(Name),属性名通常由一个下划线开始,显示的名称(display name)则是出现在材质面板上的名字,同时,需要为每个属性指定它的类型(PropertyType)
Property语义块支持的属性类型
属性类型 | 默认的定义语法 | 例子 |
---|---|---|
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”
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 Sliders
_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"
}
3.3 重量级成员:SubShader
当Unity需要加载这个Unity Shader时,Unity会扫描所有的SubShader语义块,然后选择第一个能够在目标平台上运行的SubShader。如果都不支持的话,Unity就会使用Fallback语义块指定Unity Shader
每一个Unity Shader文件可以包含多个SubShader语义块,但最少要有一个
原因:不同显卡的能力不同,高级的显卡能支持更多的指令数,可以计算复杂度较高的着色器
SubShader{
// 可选的
[Tags]
// 可选的
[RenderSetup]
Pass{
}
// Other Passes
}
SubShader中定义了一系列Pass以及可选的状态([RenderSetup])和标签([Tags])
3.3.1 状态设置RenderSetup
ShaderLab提供了一系列渲染状态的指令,这些指令可以设置显卡的各种状态,例如混合/开启深度测试等
常见的渲染状态设置选项
状态名称 | 设置指令 | 解释 |
---|---|---|
Cull | Cull Back|Front|Off | 设置剔除模式,剔除背面/正面/关闭剔除 |
ZTest | ZTest Less Greator|LEqual|GEqual|Equal|NotEqual|Always | 设置深度测试时使用的函数 |
ZWrite | ZWrite On|Off | 开启/关闭深度写入 |
Blend | Blend SrcFactor DstFactor | 开启并设置混合模式 |
当在SubShader块中设置了上述渲染状态时,将会应用到所有的Pass,如果不想这样,可以在Pass语义块中单独进行上面的设置
3.3.2 SubShader的标签Tags
是一个键值对,键和值都是字符串类型,这些键值对时SubShader和渲染引擎之间的沟通桥梁。它用来告诉Unity的渲染引擎:SubShader希望怎样以及何时渲染这个对象
Tags {"TagName1" = "Value1" "TagName2" = "Value2"}
标签类型 | 说明 | 例子 |
---|---|---|
Queue | 控制渲染顺序,指定一个物体属于哪个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染,我们也可以自定义使用的渲染队列来控制物体的渲染顺序 | Tags {“Queue” = “Transparent”} |
RenderType | 对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色器等。这可被用于着色器替换(Shader Repalcement)等功能 | Tags {“RenderType” = “Opaque”} |
DisableBatching | 一些SubShader使用Unity的批处理功能可能会出现问题,例如使用了模型空间下的坐标进行顶点动画。这时可以通过该标签来直接指明是否对该SubShader进行渲染 | Tags {“DisableBatching” = “True”} |
ForceNoShadowCasting | 控制使用该SubShader的物体是否会投射阴影 | Tags {“ForceNoShadowCasting” = “True”} |
IgnoreProjector | 如果该标签值为”True“,那么使用该SubShader的物体将不会受Projector(投影机)的影响,通常用于半透明物体 | Tags {“IgnoreProjector” = “True”} |
CanUseSpriteAtlas | 当该SubShader是用于精灵Sprite时,则将该标签设为False | Tags {“CanUseSpriteAtlas” = “True”} |
PreviewType | 指明材质面板将如何预览该材料,默认情况下,材质将显示为一个球形,我们可以通过把该标签的值设置为”Plane“”SkyBox“来改变预览类型 | Tags {“PreviewType” = “Plane”} |
注意:上述标签可以在SubShader中声明,而不可以在Pass语义块中声明,Pass块虽然也可以定义标签,但这些标签是不同于SubShader的标签类型
3.3.3 Pass语义块
Pass{
[Name]
[Tags]
[RenderSetup]
// Other Code
}
- 定义Pass的名称:
Name "MyPassName"
- 设置渲染状态
与SubShader的状态设置一样,除此之外,还可以使用固定管线的着色器命令(见后)
- Pass标签
Pass同样可以设置标签,但与SubShader的标签不同,这些标签也用于告诉渲染引擎如何渲染物体
标签类型 | 说明 | 例子 |
---|---|---|
LightMode | 定义该Pass在Unity的渲染流水线中的角色 | Tags {“LightMode” = “ForwardBase”} |
RequireOptions | 用于指定当满足某些条件时才渲染该Pass,它的值是一个由空格分隔的字符串。目前Unity支持的有:SoftVegetation,后面版本中会增加更多 | Tags {“RequiredOptions” = “SoftVegetation”} |
- 定义一些特殊Pass
- UsePass:通过这个名称可以使用ShaderLab中的UsePass命令来直接使用其他UnityShader中的Pass
UsePass "MyShader/MYPASSNAME"
可以提高代码复用性,注意:Unity内部会把所有的Pass的名称转换成大写字母的表示,所以使用UsePass时必须使用大写
- GrabPass:该Pass负责抓取屏幕并将结果存储在一张纹理中,以用于后续的Pass处理
3.4 留一条后路:Fallback
用于告诉Shader:如果上面所有SubShader能在该显卡上运行,那么就使用这个最低级的Shader吧
Fallback "name"
Fallback off
举例:
Fallback "VertexLit"
事实上Fallback还会影响阴影的投射,在渲染阴影纹理时,Unity会在每个Unity Shader中寻找一个阴影投射的Pass,通常情况下我们不需要自己专门实现一个Pass,Fallback使用的内置Shader中包含一个通用的Pass。因此,为每个UnityShader正确设置Fallback时非常重要的
4 Unity Shder的形式
Shader "MyShader"{
Properties{
// 所需的各种属性
}
SubShader{
// 真正意义上的Shder代码会出现在这里:
// 表面着色器(Surface Shader)
// 顶点/片元着色器(Vertex/Fragment Shader)
// 固定函数着色器(Fixed Function Shader)
}
SubShader{
// 与上一个Shader类似
}
}
4.1 表面着色器
Unity自己创造的着色器类型,代码量很少但是Unity在背后做了很多工作,渲染代价比较大。本质上与顶点/片元着色器相同,Unity内部会将该着色器转换为对应顶点/片元着色器,表面着色器是对顶点/片元着色器更高一层的抽象
Shader "Custom/Simple Surface Shader"{
SubShader{
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#progma surface surf Lambert
struct Input{
float color : COLOR;
};
void surf {Input IN, inout SurfaceOutput o}{
o.Albedo = 1;
}
ENDCG
}
Fallback "Diffuse"
}
-
表面着色器定义在CGPROGRAM和ENDCG之间,不关心使用多少个Pass、每个Pass如何渲染等问题,Unity会在背后做好这些事情。我们只需要告诉表面着色器使用哪些纹理填充颜色,使用哪个法线纹理去填充法线,使用Lambert光照模型等
-
CGPROGRAM和ENDCG之间的代码使用的是CG/HLSL编写的,需要将CG/HLSL嵌入ShaderLab中。但是这里的CG/HLSL和原生的CG/HLSL由细微的区别,经过Unity封装过
4.2 顶点/表面着色器
顶点着色器也是用CG/HLSL来编写,定义在CGPROGRAM和ENDCG
Shader "Custom/Simple Surface Shader"{
SubShader{
Pass{
CGPROGRAM
#progma vertex vert
#progma fragment frag
float4 vert(float4 v : POSITION) : SV_POSITION{
return mul (UNITY_MATRIX_MVP, v);
}
fixed4 frag() : SV_Target{
return fixed4(1.0, 0.0, 0.0, 1.0);
}
ENDCG
}
}
}
顶点/片元着色器的代码需定义在Pass语义内,我们需要自定义每个Pass需要使用的Shader代码,虽然代码量更大,但是灵活性更高
4.3 固定函数着色器
较旧的设备不支持可编程管线着色器,这时就需要固定函数着色器来完成渲染,往往只可以实现一些简单的效果
Shader "Custom/Simple Surface Shader"{
Properties{
_Color ("Main Color", Color) = (1, 0.5, 0.5, 1)
}
SubShader {
Pass {
Material {
Diffuse[_Color]
}
Lighting On
}
}
}
固定函数着色器的代码被定义在Pass内,相当于Pass的一些渲染设置,需要完全使用ShaderLab语法
5 一些相关问题
5.1 Unity Shader != Shader
Unity中Shader实际是ShderLab文件,以.Shader作为后缀的一种文件
传统Shader | Unity Sha |
---|---|
仅可以编写特定类型的Shader,如顶点着色器、片元着色器等 | 可以在同一个文件里同时包含需要的顶点着色器和片元着色器 |
无法设置一些渲染设置,如是否开启混合、深度测试等 | 可以通过一行特定的指令完成 |
需要编写冗长的代码来设置着色器的输入和输出,需要小心的处理输入和输出的位置对应关系 | 只需在特定语句块中声明一些属性,就可以依赖材质改变这些属性 |
由于Unity Shader的高度封装性,可以编写的Shade类型和语法被限制了,对应一些特定类型的Shder如曲面细分着色器和几何着色器等相关功能就不支持
5.2 Unity Shader和CG/HLSL的区别
ShaderLab中可以在CGPROGRAM和ENDCG之间嵌入CG/HLSL语言
5.3 可以使用CH/HLSL来写Shder吗
可以,但是可以发布的目标平台就只有Mac OS X,OpenGL ES 2.0 或者Linux,不支持仅支持DirectX的平台