Unity Shader学习笔记(一) - ShaderLab概述
本文最后更新于 2025年1月11日 下午
返璞归真。
Unity Shader入门精要
本笔记无基础部分,直接从实践开始。
材质与Shader概述
材质
材质作用于GO(准确来说,作用于MeshRenderer
、Particle System
等组件),依赖于Shader,被Shader定义。
通过材质Inspector的Shader下拉栏,可切换此材质依赖的Shader。
右下角为材质展示框,展示框右上角的按钮从左到右依次为:播放动画(不常用)、切换材质预览模型(除球体外还有立方体、圆柱体)、切换光照类型(双光源、单光源)、切换反射探针(用于金属度、光滑度高的镜面材质预览)。
Shader
Unity包含下列内置Shader类型:
- Standard Surface Shader:标准表面着色器,为经过改良的PBR着色器。
- Unlit Shader:不包含光照,但包含雾效(在全局雾中使用此Shader的物体是否在雾中进行混合)的着色器。
- Image Effect Shader:屏幕后处理着色器。
- Compute Shader:计算着色器。
- Raytracing Shader:实验性,光追着色器,用于替代原本在Compute Shader中实现的光线投射算法。仅在HDRP中生效。
Unity Shader本质上是文本文件,具有Import Settings面板。
在Default Map设置中,我们可以设置使用此Shader的材质的某些纹理属性所使用的默认纹理。在下半部分的面板上,我们可以看到与此Shader有关的信息。
对于表面着色器,可以单击Show generated code
查看由表面着色器生成的VS和FS。单机Compile and show code
可以查看此着色器针对不同图形API编译成的Shader代码,用于更底层地分析、优化Shader。
ShaderLab
ShaderLab为Unity中编写Unity Shader的说明性语言。其使用**语义(Syntax)**描述文件结构。ShaderLab定义了材质所需的所有属性,而非仅仅着色器代码。
其基础结构如下:
1 |
|
下面逐项解释。
结构
ShaderName
Unity Shader文件的第一行通过Shader语义指定该着色器的名字。使用斜杠可以控制此Shader位于哪个子级菜单中。完成名称定义的Unity Shader,可以在材质检视器的Shader下拉栏找到此Shader。
Properties
属性在Properties
语义块中定义,此处定义的属性将会出现在材质面板。
1 |
|
注意,属性之间只需要换行,无需分号。
在上面的示例代码中:
Name
为属性本身在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”{} |
我们可以通过重载默认材质编辑面板的形式让材质检视器支持布尔值等更多类型。
Property
语义块的作用仅仅是让Shader内部属性得以在检视器中显示。
SubShader
每个Unity Shader可以包含至少一个SubShader语义块。Unity Shader被加载后,Unity会选择第一个能够在目标平台上运行的SubShader。如果一个可运行的SubShader都没有,就会使用Fallback
语义指定的Unity Shader。
其结构如下:
1 |
|
RenderSetup
定义了渲染状态。如下:
状态 | 设置指令 | 解释 |
---|---|---|
Cull | Cull Back/Front/Off | 剔除背面/正面/关闭剔除 |
ZTest | ZTest Less Greater/LEqual/GEqual/Equal/NotEqual/Always | 深度测试设置 |
ZWrite | ZWrite On/Off | 开启/关闭深度写入 |
Blend | Blend SrcFactor DstFactor | 开启、设置混合模式 |
Tag
定义了渲染标签。其声明结构为Tags { “TagName1” = “Value1” “TagName2” = “Value2” }
。如下:
标签 | 说明 | 例子 |
---|---|---|
Queue | 指定渲染顺序。可以使用内置的渲染顺序组,也可以自定义渲染顺序(数字)。 | Tags { “Queue” = “Transparent” } |
RenderType | 指定着色器类型,例如透明/不透明。为着色器替换功能提供便利。 | Tags { “RenderType” = “Opaque” } |
DisableBatching | 是否使用批处理。在某些特殊情况下,使用批处理时会导致渲染错误(如使用模型空间坐标进行顶点动画)。 | Tags { “DisableBatching” = “True”} |
ForceNoShadowCasting | 是否投射阴影。 | Tags { “ForceNoShadowCasting” = “True”} |
IgnoreProjector | 是否忽略Projector 组件的影响。Projector 组件用于构造贴画、弹孔、动态阴影等。 |
Tags { “IgnoreProjector” = “True” } |
CanUseSpriteAtlas | 当此SubShader作用于Sprite 时,设为False |
Tags { “CanUseSpriteAtlas” = “False” } |
PreviewType | 指明默认情况下如何预览材质 | Tags { “PreviewType” = “Plane”} |
Pass
1 |
|
Pass可用的标签有:
标签 | 说明 | 例子 |
---|---|---|
LightMode | 定义该Pass在渲染管线中的角色。包括Always (始终渲染,不应用照明)、ForwardBase (前向渲染,应用环境光、平行光、顶点光照、光照贴图)、ForwardAdd (前向渲染,应用逐像素光照)、Deferred (延迟渲染)、ShadowCaster (渲染到深度贴图/纹理)等。 |
Tags { “LightMode” = “ForwardBase”} |
RequireOptions | 指定满足某些条件时才渲染此Pass。仅支持SoftVegetation |
Tags { “RequireOptions” = “SoftVegetation” } |
此外,UsePass
命令可以直接复用其他Unity Shader中的Pass,无需再次定义;GrabPass
命令可以抓取屏幕并将结果存储在纹理中供Pass使用。
Fallback
如果所有的SubShader均无法在此设备上运行,则使用Fallback语义块定义的Unity Shader。
Fallback “name”
实际上,Fallback语义块会影响阴影投射。在投射阴影时,Unity会查找用于阴影投射的Pass。一般我们不需要自己写一个Pass,因为Fallback使用的内置Shader包含了这么一个Pass。所以Fallback不能乱设置。
形式
Unity Shader的核心是着色器代码。着色器代码可以写在SubShader
中(表面着色器),也可以写在Pass
中(顶点/片段着色器)。
表面着色器
1 |
|
上面展示了最基本的表面着色器。着色器代码被定义在CGPROGRAM
和ENDCG
关键字之间。
表面着色器不关心要使用多少Pass,每个Pass如何渲染,只关心如何填充颜色、法线,使用何种光照模型。剩余的部分都由Unity为我们解决。
顶点/片段着色器
1 |
|
上面展示了最基本的顶点/片段着色器。
Unity Shader内置变量
数学相关
位于UnityShaderVariables.cginc
中。
变换矩阵
变量名 | 说明 |
---|---|
UNITY_MATRIX_MVP |
MVP矩阵,将局部坐标变换到裁剪坐标,供片段着色器使用 |
UNITY_MATRIX_MV |
|
UNITY_MATRIX_V |
|
UNITY_MATRIX_P |
|
UNITY_MATRIX_VP |
|
UNITY_MATRIX_T_MV |
UNITY_MATRIX_MV 的转置。通过截取该矩阵的前三行和前三列(摒弃平移影响),可以将方向向量从观察空间变换到模型空间。 |
UNITY_MATRIX_IT_MV |
UNITY_MATRIX_MV 的逆转置矩阵,用于将法线从模型空间变换到观察空间。转置可得到UNITY_MATRIX_MV 的逆矩阵 |
_Object2World |
当前的M矩阵,将局部坐标变换到世界坐标 |
_World2Object |
_Object2World 的逆矩阵 |
将观察空间的顶点/矢量变换到模型空间的操作如下:
1
float4 modelPos = mul(transpose(UNITY_MATRIX_IT_MV), viewPos);
相机/屏幕参数
变量 | 类型 | 说明 |
---|---|---|
_WorldSpaceCameraPos | float3 | 相机的世界空间坐标 |
_ProjectionParams | float | x = 1.0, y = Near, z = Far, w = 1.0 + 1.0/Far |
_ScreenParams | float4 | x = width, y = height, z = 1.0+1.0/width, w = 1.0+1.0/height。其中width和height为相机RT对应的像素宽和像素高 |
_ZBufferParams | float4 | x = 1-Far/Near, y = Far/Near, z = x/Far, w = y/Far。用于线性化Z-Buffer中的深度值 |
unity_OrthoParams | float4 | x = width, y = height, z无定义, w = 1.0或0.0(正交1.0, 透视0.0)。 |
unity_CameraProjection | float4x4 | 相机投影矩阵 |
unity_CameraInvProjection | float4x4 | 相机投影矩阵逆矩阵 |
unity_CameraWorldClipPlanes | float4 | 相机的6个裁剪平面在世界空间下的等式。按左、右、下、上、近、远裁剪平面排列。 |
备注
mul
默认使用右乘的方式,按列矩阵进行乘法。即,要将向量v
通过矩阵M
进行变换,代码为:
float4 result = mul(M,v)
Unity内置矩阵变量按列存储。
- 使用一串float填充矩阵时,使用的是行优先填充。
注意,C#中的Matrix4x4为列优先填充。
使用VPOS
或WPOS
语义可以获取片段对应的屏幕坐标。将其xy分量除以屏幕分辨率,可以得到视口空间坐标。
也可以通过
ComputeScreenPos
函数,从裁剪坐标(经过UNITY_MATRIX_MVP
矩阵变换的vert pos)计算得到未经齐次除法的视口坐标。后续在片段着色器中除以w
分量得到真正的视口坐标。这是因为在顶点着色器中直接进行齐次除法时(变换到投影空间),由于投影空间的非线性特性,会导致插值不准确。
Unity Shader
1 |
|
类型对应
要想让Properties语义块中的属性能在CG/HLSL中使用,就需要在CG/HLSL块中定义对应类型,同变量名的变量。ShaderLab类型和CG/HLSL变量类型对应如下:
ShaderLab类型 | CG变量 | HLSL变量 |
---|---|---|
Color, Vector | float4, half4, fixed4 | float4 |
Range, Float | float, half, fixed | float |
2D | sampler2D | Texture2D |
Cube | samplerCube | TextureCube |
3D | sampler3D | Texture3D |
内置文件与变量
在CG块的开头,可以使用#include
将.cginc
,即CG头文件引入。
常用的UnityCG库有:
UnityCG.cginc
,包含了最常使用的辅助函数、宏和结构体。UnityShaderVariables.cginc
,包含了许多内置全局变量,如UNITY_MATRIX。编译时自动引入。Lighting.cginc
,包含了各种内置光照模型HLSLSupport.cginc
,包含了跨平台编译的宏和定义。编译时自动引入。UnityStandardBRDF.cginc
,用于PBR渲染。UnityStandardCore.cginc
,用于PBR渲染。
UnityCG.cginc
有以下常用结构体与函数:
结构体名称 | 描述 | 包含变量 |
---|---|---|
appdata_base | VS输入 | 位置、法线、一组纹理坐标 |
appdata_tan | VS输入 | 位置、切线、法线、一组纹理坐标 |
appdata_full | VS输入 | 位置、切线、法线、多组纹理坐标 |
appdata_img | VS输入 | 位置、一组纹理坐标 |
v2f_img | VS输出 | ClipPos位置、纹理坐标 |
帮助函数名称 | 描述 |
---|---|
float3 WorldSpaceViewDir(float4 v) | 输入模型空间顶点位置,返回世界空间中从该点到相机的View Dir。 |
float3 ObjSpaceViewDir(float4 v) | 输入模型空间顶点位置,返回模型空间该点到相机的View Dir。 |
float3 WorldSpaceLightDir(float4 v) | 用于Forward 渲染。输入模型空间顶点位置,返回世界空间中该点到光源的Light Dir,未归一化。 |
float3 ObjSpaceLightDir(float4 v) | |
float3 UnityObjectToWorldNormal(float3 norm) | 将模型空间法线转换到世界空间 |
float3 UnityObjectToWorldDir(float3 dir) | 方向矢量从模型空间到世界空间 |
float3 UnityWorldToObjectDir(float3 dir) |
语义
语义(Semantics)可以让Shader知道从哪里读取数据,并把数据输出到哪里。
以SV_
开头的语义是系统数值(System Value)语义。使用该语义描述的变量不可随意赋值,因为流水线需要使用它们完成特定的目的。例如,SV_POSITION
必须存储裁剪空间的顶点坐标。
Unity支持以下语义:
语义 | 描述 |
---|---|
POSITION | 模型空间顶点位置输入,float4类型 |
NORMAL | 顶点法线,float3类型 |
TANGENT | 顶点切线,float4类型 |
TEXCOORDn | 顶点纹理坐标,float2或float4类型 |
COLOR | 顶点颜色,fixed4或float4类型 |
从VS传递数据给FS所用的结构体中,一般包含SV_POSITION
、COLOR0
(第一组顶点颜色,可选)、COLOR1
(第二组顶点颜色,可选)、TEXCOORD0~TEXCOORD7
(纹理坐标,可选)。
尽管我们定义这些变量用于传输颜色、纹理坐标等,但实际上我们可以把任何数据都传入其中(不包括SV语义)。
SV_Target
语义用于标记FS输出。