原址:一、什么是Unity Shader? - 知乎
作者:changyun
目录
1 什么是Unity Shader?
Unity Shader是由UnityLab语言编写的一段计算机程序,用来定义渲染管线在不同阶段的处理流程,本质上是一段文本文件。编写完成后,需要进行编译,将其转换成GPU可以执行的机器指令。Unity提供了四种不同的Shader模板:Standard Surface Shader、Unlit Shader、Image Effect Shader和Compute Shader。这些模板为开发者提供了一些预设的基础代码和着色算法。具体来说,
(1) Standard Surface Shader是一种基于表面属性的着色算法,而不是基于像素的着色算法。表面属性就是材质属性,如颜色、纹理、透明度、反射率等。Standard Surface Shader可以自动的基于表面属性实现光照计算。Standard Surface Shader支持多种光照模型,如Lambert、Phong、Blinn-Phong、Cook-Torrance、Oren-Nayar、Minnaert、Fresnel等。如果需要改变光照模型,需要打开Surface Standard Shader文件,修改代码
#pragma surface surf Phong
即可将光照模型改成Phong光照模型。
(2) Unlit Shader是一种不考虑场景中光照影响的着色算法。
(3) Image Effect Shader定义了屏幕后处理特效的Unity Shader,它在图像渲染完成后,将渲染结果作为输入,进行一系列的处理,例如模糊、色彩调整、镜头畸变,最终输出加入特效后的图像。
(4) Compute Shader是一种GPU的并行性来进行一些与渲染流水线无关计算的编程模型。为大规模的数据处理提供了强有力的支持。使用Compute Shader需要掌握多线程编程能力。
2 Unity Shader和材质的关系?
在Unity中,材质是一种用于描述物体表面外观的数据结构,其中包含了颜色、纹理、透明度等属性信息。材质在被赋予给游戏对象之后,可以被Mesh Renderer组件所使用,Mesh Renderer会将材质属性数据传递给Unity Shader程序进行处理。因此,材质与Unity Shader共同构成了实现高质量渲染的必要组成部分。通过精心设计和调整材质和Shader,可以使游戏对象在渲染时呈现出更加真实、细腻、吸引人的视觉效果。
3 什么是RenderQueue?
每个材质都具有RenderQueue属性,用于定义材质在渲染管线中的渲染顺序,RenderQueue值越小优先级越高。具有相同RenderQueue值的材质物体会根据其在场景中的顺序进行依次渲染。在Unity中,常见的RenderQueue值设置如下:
1. Geometry(默认值为 1000):该RenderQueue值用于渲染几何体,例如立方体、球体等等。如果您的场景中只包含几何体,则无需对RenderQueue进行修改。
2. AlphaTest(默认值为 2000):该RenderQueue值用于渲染使用Alpha Test的材质,例如一些使用半透明纹理的材质。
3. Transparent(默认值为 3000):该RenderQueue值用于渲染半透明的材质,例如玻璃、水等物体。
4. Overlay(默认值为 4000):该RenderQueue值用于渲染覆盖在其他物体之上的材质,例如UI元素。
5. Background(默认值为 100):该RenderQueue值用于渲染场景的背景,例如天空盒等。
4 什么是ShaderLab语言?
ShaderLab是Unity提供的编写Unity Shader的一种说明性语言,它使用一个个语义块来描述Unity Shader。Unity会在平台背后将Unity Shader的这些结构编译成真正的代码和shader文件,而开发者只需要和Unity Shader打交道即可。一个Unity Shader的基础结构如下所示:
Shader "ShaderName"{
Properties{
// 显示在材质面板中的属性
_Name ("Display Name", PropertyType)=DefaultType
}
SubShader{
// 显卡A使用的子着色器
}
SubShader{
// 显卡B使用的子着色器
}
Fallback "OtherShaderName"
}
4.1 Properties语义块
Properties语义块的作用仅仅是为了让里面定义的属性可以出现在材质面板中。因此,即使没有在Properties语义块中定义的属性也可以在代码块中定义。Properties语义块支持的属性类型见表1所示。
表1 Properties语义块支持的属性类型
属性类型 | 默认值定义语法 | 例子 |
---|---|---|
Int | number | _MyInt("DisplayInt",Int)=2 |
Float | number | _MyFloat("DisplayFloat",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,4,1) |
2D | "defaulttexture"{} | _2D("2D",2D)=""{} |
Cube | "defaulttexture"{} | _Cube("Cube",Cube)="white"{} |
3D | "defaulttexture"{} | _3D("3D",3D)="black"{} |
4.2 SubShader语义块
每一个Unity Shader都至少包含一个SubShader语义块,Unity会默认选择第一个能在当前平台上运行的SubShader语义块执行,如果都不支持运行则会使用Fallback语义块指定的Unity Shader。Unity提供这种语义块的原因在于不同显卡处理能力不同,我们希望在较低性能的显卡上执行简单的渲染任务,而在高级显卡上使用计算复杂度更高的着色器。SubShader语义块中包含的定义通常如下:
SubShader{
// 当前上下文环境中所有pass的标签设置
Tags{"TagName1"="Value1" "TagName2"="Value2"}
// 当前上下文环境中所有pass的状态设置
StateName Value // 比如 Cull Back
pass{
Name "MyPassName1"
[Tags]
[RenderSetup]
// other code
}
// other passes
}
其中,SubShader中支持的Tags标签类型如表2所示。
表2 标签类型
标签类型 | 说明 | 例子 |
---|---|---|
Queue | 控制渲染顺序,指定该物体属于哪一个渲染队列。 | Tags{"Queue"="Transparent"} |
RenderType | 用来指定shader的渲染类型,不同的渲染类型会有不同的处理过程,常见的渲染类型包括Opaque(不透明)、Transparent(透明)、Overlay(覆盖层)、Background(背景)等。 | Tags{"RenderType"="Opaque"} |
DisableBatching | 一些SubShader在进行批处理时会出现问题。 | Tags{"DisableBatching"="True"} |
ForceNoShadowCasting | 如果该SubShader中pass被设置成前向渲染,那么就会在正常的渲染流水线之前增加一个Shadow Caster Pass阶段,该阶段会会生成ShadowMap也就是深度贴图,通过ForceNoShadowCasting标签可以强制取消Shadow Caster Pass阶段,从而不产生阴影。 | Tags{"ForceNoShadowCasting"="True"} |
IgnoreProjector | 设置是否该Shader会忽视投射器的影响。 | Tags{"IgnoreProjector"="True"} |
CanUseSpriteAtlas | 当该SubShader是用于精灵时,该标签设置为True。 | Tags{"CanUseSpriteAtlas"="True"} |
PreviewType | 指定材质面板将如何预览该材质。默认情况下,材质将显示为一个球形,可以通过改变该标签为"Plane"或"SkyBox"来改变预览类型。 | Tags{"PreviewType"="Plane"} |
Pass语义块中支持的标签类型如表3所示。
表3 Pass语义块支持的标签类型
标签类型 | 说明 | 例子 |
---|---|---|
LightMode | 在Unity Shader中有多种渲染路径,如前向渲染、延迟渲染、ShadowMap渲染等。LightMode就是用来指定某个Pass的渲染路径的,通常包括: (1) ForwardBase:前向渲染,只采用最主要的光源(平行光)执行最基础的光照计算,如漫反射、高光反射。 (2) ForwardAdd:前向渲染,将场景中所有光源(平行光、点光源、聚光灯等)都参与光照计算,然后进行融合。 (3) Deferred:延迟渲染,包括两个pass,首先第一个pass用于将通过深度测试的片元存储到G-Buffer中。第二个pass根据G-Buffer进行光照计算。 (4) ShadowCaster:该渲染路径用于生成ShadowMap,将相机放到光源位置投射来产生一个深度贴图(ShadowMap)。 | Tags{"LightMode"="ForwardBase"} |
RequireOptions | 用于指定当满足某些条件时才渲染该Pass。 | Tags{"RequireOptions"="SoftVegetation"} |
5 小结
目前为止,本文详细解释了什么是Unity Shader以及Unity Shader中语义标签格式的粗略介绍。不同类型的Unity Shader其SubShader语义块的内容略有不同,只需要重点关注表面着色器和顶点/片元着色器即可。其中表面着色器是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语义块内,Unity会在内部自动将其转换为对应的Pass语义块,我们只需要定义好光照模型,Unity就会自动在背后执行光照计算。
而顶点/片元着色器想要执行光照计算则只能自己编码实现了,一个简单的顶点/片元着色器的代码如下所示。
Shader "Custom/Simple VertexFragment Shader"{
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert //定义顶点着色器函数名为vert
#pragma fragment frag //定义片元着色器函数名为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
}
}
}
6 本章参考文献
[1] 冯乐乐. Unity Shader 入门精要[M]. 人民邮电出版社, 2016.