Unity Shader入门(二)
本人是一个初学Shader的小白,写此博客为了记录学习Shader过程中的一些概念以及问题,主要参考书籍《Unity Shader入门精要》如果有哪些不对的地方欢迎各位大佬指导!!!
Unity Shader基础
1、Unity Shader概述
总体来说,在Unity中我们需要配合使用材质和Unity Shader才能达到需要的效果。Unity Shader定义了渲染所需的各种代码(如顶点着色器和片元着色器)、属性(如使用哪些纹理)和指令(渲染和标签设置等),而材质则允许我们调节这些属性,并将其最终赋给相应的模型。
材质的创建流程以及选择使用哪个Shader这里就不做解释了。
就目前我自己了解熟悉的,Unity一共提供了4种Unity Shader模板供我们选择:
Standard Surface Shader:会产生一个包含了标准光照模型的表面着色器模板
Unlit Shader:会产生一个不包含光照(但包含雾效)的基本的顶点/片元着色器
Image Effect Shader:(不熟悉)
Compute Shader:(不熟悉)
2、 ShaderLab
在Unity中,所有的Unity Shader都是使用ShaderLab来编写的。
一个Unity Shader的基础结构如下所示:+
Shader "ShaderName"{
Properties{
//属性
}
SubShader{
//显卡A使用的子着色器
}
SubShader{
//显卡B使用的子着色器
}
Fallback "VertexLit"
}
3、Unity Shader的结构
- Shader名字:
Shader "Custom/MyShader"
- Shader属性(property):
声明这些属性是为了在材质面板中能给方便地调整各种材质属性。
Properties{
Name("display name",PropertyType)=DefaultValue
Name("display name",PropertyType)=DefaultValue
}
下面代码给出了一个展示所有属性类型的例子:
Shader "Custom/ShaderLab"
{
Properties
{
//Number and Sliders
_Int("Int",Int)=2
_FLoat("Float",Float)=1.5
_Range("Range",Range(0.0,5.0))=3.0
//Color 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"
}
如下图给出了上述代码在材质面板的显示结果!
- SubShader
每一个Unity Shader文件可以包含多个SubShader语义块,但最少要有一个。当Unity需要加载这个Unity Shader时,Unity会扫描所有的SubShader语义块,然后选择第一个能给在目标平台上运行的SubShader,如果都不支持的话,Unity就会使用Fallback语义指定的Unity Shader。这样做的原因在于不同的显卡具有不同的能力。
SubShader语义块中包含的定义如下:
SubShader
{
//可选的
[Tags]
//可选的
[RenderSetup]
Pass
{
}
//other Passes
}
SubShader定义了一系列Pass以及可选的状态([RenderSetup])和标签([Tags])设置。每个Pass定义了一个完整的渲染流程,但如果Pass的数目过多,往往会造成渲染性能的下降。
状态设置:
ShaderLab提供了一系列渲染状态的设置指令,这些指令可以设置显卡的各种状态,这些渲染状态将会运用到Pass语句块内,如果不想这样,可以在每个Pass语句块内设置渲染状态。
SubShader的标签:
SubShader的标签是一个键值对(Key/Value Pair),它的键和值都是字符串类型。这些键值对是SubShader和渲染引擎之间的沟通桥梁,它们用来告诉Unity的渲染引擎:我们希望怎样以及何时渲染这个对象。例如可以控制物体渲染顺序,对着色器进行分类等。
标签的结构如下:
Tags{"TagName1"="Value1" "TagName2"="Value2"}
注意区分SubShader的标签和Pass语句快的标签不可以通用
Pass语义块:
Pass语义块包含的语义如下:
Pass
{
[Name]
[Tags]
[RenderSetup]
//otherCode
}
首先,我们可以在Pass中定义该Pass的名称,例如:
Name "MyPassName"
通过这个名称,我们可以使用ShaderLab的UsePass命令直接使用Unity Shader中的Pass。例如:
UsePass "MyShader/MyPASSNAME"
这样可以提高代码的复用性。需要注意的是,由于Unity内部会把所有Pass的名称转换成大写字母表示,因此,在使用UsePass命令必须使用大写形式的名字。
其次,我们可以对Pass设置渲染状态。SubShader的状态设置同样使用于Pass。还有一些其他的渲染状态可用于Pass语句块。
Pass可以设置标签,但它的标签不同于SubShader的标签。但这些标签也是告诉渲染引擎我们希望怎样来渲染该物体的。
除了上面普通的Pass定义外,Unity Shader还支持一些特殊的Pass,以便进行代码的复用或实现复杂的效果。
GrapPass:该Pass负责抓取屏幕并将结果存储在一张纹理中,以用于后续的Pass处理。
- Fallback
紧跟在各个SubShader语义块后面的,可以是一个Fallback指令,它用于告诉Unity,如果上面所有的SubShader在这块显卡上不能运行,那么就使用最低级的Shader。
它的语义如下:
Fallback "name"
//或者
Fallback off
4、Unity Shader的形式:
在Unity中,我们可以使用下面3种形式来编写Unity Shader。而不管使用哪种形式,真正意义上的Shader代码都需要包含在ShaderLab语义块中,如下所示:
Shader "MyShader"
{
Properties
{
//所需的各种属性
}
SubShader
{
//真正意义上的Shader代码会出现在这里
//表面着色器(Surface Shader)或者
//顶点/片元着色器(Vertex/Fragment Shader)或者
//固定函数着色器(Fixed Function Shader)
}
SubShader
{
//和上一个SubShader类似
}
}
Unity的宠儿:表面着色器
表面着色器是Unity自己创造的一种着色器代码类型。它需要的代码量很少,Unity在背后做了很多工作,但渲染的代价比较大。它在本质上和下面要讲到顶点/片元着色器是一样的。也就是说,当给Unity提供一个表面着色器的时候,它在背后仍旧把它转换成对应的顶点/片元着色器。我们可以理解为表面着色器是Unity对顶点/片元着色器的更高一层的抽象。它存在的价值在于,Unity为我们处理很多的光照细节。
一个非常简单的表面着色器示例代码如下:
Shader "Custom/Simple Surface Shader"
{
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"
}
从上述程序中可以看出,表面着色器被定义在SubShader语义块(而非Pass语义块)中的CGPROGRAM和ENDCG之间。原因是,表面着色器不需要开发者关心使用多少个Pass,每个Pass如何渲染等问题,Unity会在背后为我们做好这些事情。
其中CGPROGRAM和ENDCG之间的代码使用Cg/HLSL编写的,也就是说,我们需要把Cg/HLSL嵌套在ShaderLab语言中。值得注意的是,这里的Cg/HLSL是Unity经封装后提供的。
总聪明的孩子:顶点/片元着色器
在Unity中我们可以使用Cg/HLSL语言来编写顶点/片元着色器。他们更加复杂,但灵活性也更高。
一个非常简单的顶点/片元着色器代码如下:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Custom/Simple VertexFragment Shader"
{
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.0,0.0,1.0);
}
ENDCG
}
}
}
和表面着色器类似,顶点/片元着色器的代码也需要定义在CGPROGRAM和ENDCG之间,但不同的是,顶点/片元着色器是写在Pass语句块内的,而非SubShader内。
被抛弃的角落:固定的函数着色器
上面的两种Unity Shader形式都使用了可编程渲染管线。而对于一些较旧的设备,就需要使用固定函数着色器来完成渲染。这些函数往往只可以完成一些较简单的效果。
一个非常简单的固定函数着色器示例代码如下:
Shader "Tutorial/Basic"
{
Properties
{
_Color("Main Color",Color)=(1.0,0.5,0.5,1)
}
SubShader
{
Pass
{
Material
{
Diffuse[_Color]
}
Lighting On
}
}
}
可以看出,固定函数着色器的代码被定义在Pass语义块中,这些代码相当于Pass中的一些渲染设置。
对于固定函数着色器来说,我们需要完全使用ShaderLab的语法(即使用ShaderLab的渲染设置指令)来编写。
因为现在绝大多数GPU支持可编程的渲染管线,这种固定管线的编程方式已经逐渐被抛弃。