引用官方文档中对ShaderLab的解释:
Unity中所有Shader文件都通过一种陈述性语言进行描述,称为“ShaderLab”, Shader文件中用一种嵌套式语法来描述着色器行为,如材质面板中显示的属性、使用的混合模式等等,实际的着色器代码(如HLSL/Cg)定义在CGPROGRAM与ENDCG之间。
着色器文件的根命令为Shader,每个文件都必须调用该命令进行定义,语法如下:
Shader "name"
{
[Properties]
Subshaders
[Fallback]
[CustomEditor]
}
下面对ShaderLab语言进行简单介绍。
Unity着色器可通过该关键字定义一个参数列表,该参数列表可在Unity材质面板中进行配置,定义方法如下。
Properties { Property [Property ...] }
数字与滑动条
name ("display name", Range (min, max)) = number
name ("display name", Float) = number
name ("display name", Int) = number
通过Range进行定义会在面板中显示为滑动条,基本类型则为可填数字。
着色与向量
name ("display name", Color) = (number,number,number,number)
name ("display name", Vector) = (number,number,number,number)
定义颜色值为RGBA,向量为四维向量。
纹理
name ("display name", 2D) = "defaulttexture" {}
name ("display name", Cube) = "defaulttexture" {}
name ("display name", 3D) = "defaulttexture" {}
定义2D或3D纹理,及cubemap。
示例
// properties for a water shader
Properties
{
_WaveScale ("Wave scale", Range (0.02,0.15)) = 0.07 // sliders
_ReflDistort ("Reflection distort", Range (0,1.5)) = 0.5
_RefrDistort ("Refraction distort", Range (0,1.5)) = 0.4
_RefrColor ("Refraction color", Color) = (.34, .85, .92, 1) // color
_ReflectionTex ("Environment Reflection", 2D) = "" {} // textures
_RefractionTex ("Environment Refraction", 2D) = "" {}
_Fresnel ("Fresnel (A) ", 2D) = "" {}
_BumpMap ("Bumpmap (RGB) ", 2D) = "" {}
}
Shader中调用
Unity着色器代码中调用属性值,需要定义一个与属性名称与类型都匹配的变量,如:
Shader "Unlit/TestShader"
{
//属性声明
Properties{
_Color("Tint", Color) = (1,1,1,1)
}
//着色器
SubShader{
Pass{
CGPROGRAM
...
//在CG代码中,需要定义一个与属性名称与类型都匹配的变量
fixed4 _Color;
...
ENDCG
}
}
}
Unity的着色器由一个子着色器列表组成,当Unity需要显示的个网格时,会从子着色器列表选择第一个子着色器来渲染。
Subshader { [Tags] [CommonState] Passdef [Passdef ...] }
Subshader语句由可选的Tags与通用状态,及一个渲染通道列表。当Unity选择某个子着色器进行渲染时,它会将每个通道都渲染一次(由于光照作用可能还会更多),由于每次渲染都是开销巨大的操作,因此应该尽可能降低通道数量。
示例:
// ...
SubShader {
Pass {
Lighting Off
SetTexture [_MainTex] {}
}
}
// ...
语法:
Pass { [Name and Tags] [RenderSetup] }
Pass命令包含一个渲染状态设置命令列表,一个通道可以定义其名称(Name)及任意数量的标签(Tags)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EM0LiYxw-1572692889763)(Image/SubShaderStruct.png)]
Cull
用法:
Cull Back/Front/Off
ZTest
用法:
ZTest Less/Greater/LEqual/GEqual/Equal/NotEqual/Always
作用:深度缓存测试模式
说明:控制深度测试如何执行,默认为LEqual,代表通过比较深度来更改颜色缓存的值。例如当取默认值的情况下,如果将要绘制的新像素的z值小于等于深度缓存中的值,则将用新像素的颜色值更新深度缓存中对应像素的颜色值,其实就是较为流行的Z-Buffer算法。
备注:需要注意的是,当ZTest取值为Off时,表示的是关闭深度测试,等价于取值为Always,而不是Never!Always指的是直接将当前像素颜色(不是深度)写进颜色缓冲区中;而Never指的是不要将当前像素颜色写进颜色缓冲区中,相当于消失。
ZWrite
用法:
ZWrite On/Off
作用:设置深度缓存写模式
说明:控制是否将当前对象的像素点写入深度缓存,或画实心体,可打开该项;当绘制半透明特效,则可关闭。
备注:在On的情况,还要看ZTest是否通过,通过才会写入深度缓存。
Offset
用法:
Offset Factor, Units
作用: 设置深度偏移量,Factor为Z相对XY所在平面最大斜率的缩放参数,Units为最小可分辨深度缓存值的缩放值参数,该命令可以强制将一个多边形绘制到另一个多边行之上(即便它们的位置是一样的),如Offset 0, -1可忽略多边形斜率情况下,将多边形拉近至摄像机;可以这么理解:
每一个Fragment的深度值都会增加如下所示的偏移量:
offset = (m * factor) + (r * units)
m是多边形的深度的斜率(在光栅化阶段计算得出)中的最大值,一个多边形越是与近裁剪面(near clipping plan)平行,m就越接近0。
r是能产生在窗口坐标系的深度值中可分辨的差异的最小值,r是由具体实现OpenGL的平台指定的一个常量。
一个大于0的offset会把模型推到离你(摄像机)更远一点的位置,相应地,一个小于0的offset会把模型拉近。
备注:不理解该参数的作用的话,可参考unity shader Offset Factor, Units详解
ColorMask
用法:
ColorMask RGB / A / 0 / any combination of R, G, B, A
作用: 设置颜色通道,ColorMask 0表示关闭所有颜色通道的渲染,默认模式为所有通道(RGBA)渲染。
Blending用于制作透明物体,语法如下:
Blend Off: 关闭blending (默认)
Blend SrcFactor DstFactor: 配置并开启混合,生成的颜色乘以系数SrcFactor,绘制到屏幕上的颜色将乘以系数DstFactor,两数相加,公式如下:
float4 result = SrcFactor * fragment_output + DstFactor * pixel_color
Blend SrcFactor DstFactor, SrcFactorA DstFactorA: 功能同上,只是使用不同系数混合Alpha通道。
BlendOp Op: 混合操作,采用不同操作混合颜色(上面是加法)。
BlendOp OpColor, OpAlpha: 同上,只是RGB通道与Alpha通道采用不同操作。
当使用多个Render Target时,以上命令稍做改动,如下:
其中N是Render Targer的下标(0-7),该特性可用于大部分APIs/GPUs(DX11/12, GLCore, Metal, PS4)
Blend运算(Op)和系数因子(Factor)详见:Unity ShaderLab:Blending
UsePase命令用于设置命名其它着色器中的通道。用法如下:
UsePass "Shader/Name"
通过该命令可实现着色器通道复用,减少代码重复。对于需要复用的通道,需要定义通道名称以供调用,通过Name关键字定义名称。
Name "MyPassName"
GrabPass命令用于捕获当前屏幕至一张纹理中(当前对象绘制前的屏幕内容),在GrabPass命令的后序通道定义中可以用该纹理。
示例:
Shader "GrabPassInvert"
{
SubShader
{
// Draw ourselves after all opaque geometry
Tags { "Queue" = "Transparent" }
// Grab the screen behind the object into _BackgroundTexture
GrabPass
{
"_BackgroundTexture"
}
// Render the object with the texture generated above, and invert the colors
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 grabPos : TEXCOORD0;
float4 pos : SV_POSITION;
};
v2f vert(appdata_base v) {
v2f o;
// use UnityObjectToClipPos from UnityCG.cginc to calculate
// the clip-space of the vertex
o.pos = UnityObjectToClipPos(v.vertex);
// use ComputeGrabScreenPos function from UnityCG.cginc
// to get the correct texture coordinate
o.grabPos = ComputeGrabScreenPos(o.pos);
return o;
}
sampler2D _BackgroundTexture; //通过该变量进行访问
half4 frag(v2f i) : SV_Target
{
half4 bgcolor = tex2Dproj(_BackgroundTexture, i.grabPos);
return 1 - bgcolor;
}
ENDCG
}
}
}
以上示例中有两个通道,第一个通道取得当前对象后面的屏幕纹理,第二个通过访问该纹理内容。访问该纹理同样要在CGPROGRAM块中定义一个相同类型的变量,如以上代码中的sampler2D _BackgroundTexture。
子着色器通过Tags来告诉渲染引擎何时、如何渲染。定义方式如下:
Tags { "TagName1" = "Value1" "TagName2" = "Value2" }
Tags定义一个名称-取值对的列表,在子着色器的标签内部用于确定渲染顺序和其它参数,以下标签在Unity必须定义于子着色器中,不能在通道当中。
Queue
可以通过Queue标签来定义对象的渲染顺序,一个着色器决定了从属于它的所有对象的渲染队列(如所有透明着色器应该在不透明对象之后绘制等)。有五种预定义渲染队列:
Background: 该渲染队列最早渲染,典型应用就是背景。
Geometry (默认):大部分对象都使用该方式,不透明几何体也使用该渲染队列。
AlphaTest: Alpha通道检测的几何体使用该渲染队列,由于绘制完所有实心体后再渲染alpha-tested对象会更加高效,因此这是一个独立的渲染队列。
Transparent:该渲染队列在Geometry和AlphaTest队列后渲染,按从后往前的顺序,任意alpha-blended对象(无法写入深度缓存)应该放入此队列(琉璃、粒子特效等)
Overlay:该渲染队列用于重叠效果,最后渲染的对象应放入该队列。
示例:
Shader "Transparent Queue Example"
{
SubShader
{
Tags { "Queue" = "Transparent" }
Pass
{
// rest of the shader body...
}
}
}
如果有特殊渲染要求,可队列之间进行嵌入,GPU进行着色时会按照渲染队列数值进行渲染。Background,Geometry,AlphaTest,Transparent,Overlay的整型数值为1000,2000,2450,3000,4000。如果使用下列命令进行嵌入:
Tags { "Queue" = "Geometry+1" }
此时该物体(2001)的渲染时机会先于transparent但后于opaque/ Geometry,这可将某种渲染嵌入队列之间,如水纹渲染就位于Geometry和transparent之间。自定义一个位于2500的序列,此时渲染不透明的游戏物体可获得最好性能。对透明物体的距离(次序)进行排序,然后按照次序高低进行渲染,推荐透明物体渲染队列在2500之上。而天空盒的渲染一般位于2000和3000之间。
RenderType
RenderType标签将着色器分类至几种预定义分组,如不透明着色器,alpha-tested着色器等
DisableBatching
当Drawcall合批时,某些着色器会失败,那是由于合批将所有几何体转换至世界空间,因此“模型空间”会丢失(没有参考物)。该标签可用于处理该情况,有三种取值:True表示关闭合批操作,False表示不关闭合批操作,LODFading表示当使用LOD时关闭合批,常被用于树木。
ForceNoShadowCasting
ForceNoShadowCasting标签为True时,用该子着色器进行渲染的对象将没有阴影,当引用其它着色器用于透明物体时,但又不想继承其它子着色器的shadow通道,该标签便十分有用了。
IgnoreProjector
IgnoreProjector标签为True时,则使用该着色器的对象将不受任何投影类型材质或者贴图影响,对于半透明物体十分有用。
CanUseSpriteAtlas
如果该着色器片段仅用于处理图片时,并且设置该标签的值为False,当想把图片打包成图集时,这个命令不会起作用
PreviewType
一般情况下,点击某个材质可在材质属性面板上预览到材质。默认材质都是球型的,使用该标签可将预览模型改为“2D平面”或者“3D天空盒”。
除了内置的标签,可以自定义标签,并通过Material.GetTag进行访问。
当所有子着色器都定义完后,便可以定义备用着色器,即当所有的子着色器都没法在当前硬件运行时,试图使用其它着色器。定义方式如下:
Fallback "name"
没有备用着色器时,可声明以下语句,这样即使所有子着色器都无法在当前硬件平台运行时,也不会报警告。
Fallback Off
示例:
Shader "example" {
// properties and subshaders here...
Fallback "otherexample"
}
Category可对任意命令进行逻辑分组,常用于“继承”渲染状态,如着色器可能有多个子着色器,且它们都要求将Fog设置为Off,Blending设置为additive,可以使用如下分类:
Shader "example" {
Category {
Fog { Mode Off }
Blend One One
SubShader {
// ...
}
SubShader {
// ...
}
// ...
}
}
Category块仅影响着色器解析,等同于将分类内部任意状态设置至所有子模块中,并不影响着色器执行速度。