第1章 欢迎来到Shader的世界
第2章 渲染流水线
第3章 Unity Shader基础
通过前面的章节我们知道,Shader其实就是渲染流水线中的某些特定阶段。
例如顶点着色器阶段、 片元着色器阶段等。
在编写着色器代码时,如果每次都需要手动设置渲染状态,代码将会变得复杂而冗长。
Unity这类编辑器能够让开发者更轻松地管理着色器代码以及渲染设置(如开启/关闭混合、深度测试、设置渲染顺序等)
材质指的是物体的质地,指的是色彩、纹理、光滑度、透明度、反射率、折射率、发光度等等。
Unity通过材质与网格(Mesh)或粒子渲染器(Particle Renderers)结合,呈现出不同的游戏对象。
材质可以看作某一个Shader 的实例。
Standard Surface Shader:一个包含了标准光照模型的表面着色器模板。
Unlit Shader:一个不包含光照(但包含雾效)的基本的顶点/片元着色器模板。
Image Effect Shader :各种屏幕后处理效果(详见第12章)的基本模板。
Compute Shader:一种特殊的Shader文件,这类Shader旨在利用GPU的并行性来进行一些与常规渲染流水线无关的计算,不在本书的讨论范围内。
Standard Surface Shader:包含了标准光照模型的表面着色器模板;
Unlit Shader:不包含光照(但包含雾效)的基本的顶点/片元着色器;
Image Effect Shader:屏幕后处理效果的模板;
Compute Shader:利用GPU的并行性来进行一些与常规渲染流水线无关的计算;
Ray tracing shader:光追着色器;
Unity为用户封装了一套说明性语言ShaderLab,在背后Unity会根据使用的平台将其翻译成真正的代码和Shader文件,开发者只需要和Unity Shader打交道。
shader命名
通过Shader + 字符串的形式。
当为材质选择使用的Unity Shader时,这些名称就会出现在材质面板的下拉列表里。
Shader "Custom/MyShader"
属性Properties
属性是材质和Shader沟通的桥梁,Properties定义的属性会出现在材质面板中。
Properties
{
// Name ("display name", PropertyType) = DefaultValue;
// Numbers and Sliders
_Int ("IntValue", Int) = 2
_Float ("FloatValue", Float) = 1.5
_Range ("RangeValue", Range(0.0, 5.0)) = 3.0
// Colors and Vectors
_Color ("Color", Color) = (1,1,1,1)
_Vector ("Vector", Vector) = (2,3,6,1)
// Textures
_2D ("2DTex", 2D) = "" {}
_Cube ("Cube", Cube) = "white" {}
_3D ("3DTex", 3D) = "black"{}
}
每一个Unity Shader可以包含多个SubShader语义块,但最少要有一个。 当Unity需要加载这个Unity Shader时,Unity会扫描所有的SubShader语义块, 然后选择第一个能够在目标平台上运行的SubShader。 如果都不支持的话, Unity就会使用Fallback语义指定的Unity Shader。
SubShader
{
// 可选的
[Tags]
// 可选的
[RenderSetup]
Pass
{
}
// Other Passes
}
SubShader中定义了 一系列Pass以及可选的状态([RenderSetup])和标签([Tags])设置。 每个Pass定义了一次完整的渲染流程, 但如果Pass的数目过多, 往往会造成渲染性能的下降。 因此, 我们应尽量使用最小数目的Pass。状态和标签同样可以在Pass声明。 不同的是,SubShader中的一些标签设置是特定的。 也就是说, 这些标签设置和Pass中使用的标签是不一样的。 而对于状态设置来说,其使用的语法是相同的。 但是, 如果我们在SubShader进行了这些设置, 那么将会用于所有的Pass。
常见的RenderSetup选项
状态名称 | 设置指令 | 解释 |
---|---|---|
Cull | Cull Back / Front/Off | 设置剔除模式;剔除背面/正面/关闭剔除 |
ZTest | ZTest Less Greater I LEqual I GEqual I Equal I NotEqual I Always | 设置深度测试时使用的函数 |
ZWrite | ZWrite On /Off | 开启/关闭深度写入 |
Blend | Blend Srcfactor Dstfactor | 开启并设置混合模式 |
以字符串键值对的形式:
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "DisableBatching"="True"}
SubShader 的标签类型
标签类型 | 说明 | 例子 |
---|---|---|
Queue | 控制渲染顺序,指定该物体属于哪 个渲染队列,通Queue 过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染(详见第8章) ,我们也可以自定义使用的渲染队列来控制物体的渲染顺序 | Tags { “Queue”= “Transparent” } |
Render Type | 对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色器等。可以被用于着色器替换(Shader Replacement)功能 | Tags { “RenderType” = “Opaque” } |
DisableBatching | 一些SubShader在使用 Unity 的批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画(详见11.3 节)。这时可以通过该标签来直接指明是否对该SubShader使用批处理 | Tags { “DisableBatching” = “True” } |
ForceNoShadowCasting | 控制使用该SubShader的物体是否会投射阴影(详见8.4节) | Tags { “ForceNoSdowCasting” = “True”} |
lgnoreProjector | 如果该标签值为 “True” 那么使用该 SubShader 的物体将不会受 Projector 的影响。通常用于半透明物体 | Tags { “lgnoreProjector” = “True” } |
CanUseSpriteAtlas | 当该 SubShader 是用于精灵 (sprites) 时,将该标签设为“False” | Tags { “CanUseSpriteAtlas” = “False”) |
PreviewType | 指明材质面板将如何预览该材质。默认情况下,材质将显示为一个球形,我们可以通过把该标签的值设为 "Plane " “SkyBox” 来改变预览类型 | Tags { “PreviewType” = “Plane” } |
上述标签只能在SubShader块中声明,不能在Pass块中声明。
Pass的标签类型
标签类型 | 说明 | 例子 |
---|---|---|
LightMode | 定义该Pass在Unity的渲染流水线中的角色 | Tags { “LightMode” = “ForwardBase” } |
RequireOptions | 用于指定当满足某些条件时才渲染该 Pass, 它的值是一个由空格分隔的字符串。目前, Unity 支待的选项有:SoftVegetation 。在后面的版本中,可能会增加更多的选项 | Tags { “RequireOptions” = “SoftVegetation” } |
除了上面普通的 Pass 定义外, Unity Shader 支持一些特殊的 Pass, 以便进行代码复用或实现更复杂的效果。
UsePass: 可以使用该命令来复用其他 Unity Shader 中的 Pass。
GrabPass: 该Pass负责抓取屏幕并将结果存储在 张纹理中,以用于后续的 Pass 处理。(详见10.2节)
如果一块显卡跑不了上面所有的 SubShader,就跑这个最低级Shader
FallBack "Shader Name" // 或者 FallBack Off
使用 CustomEditor 语义来扩展材质面板的编辑界面。
使用 Category 语义来对 Unity Shader 中的命令进行分组。
表面着色器在SubShader语义块中编写。其本质上是顶点/片元着色器,它是Unity 在顶点/片元着色器上层为开发者提供的一层抽象封装,但在背后, Unity 还是会把它转化一个包含多 Pass 的顶点/片元着色器。可以在 Unity Shader 的 Import Setting 面板中点击 Show generated code 来查看其生成的真正代码。
Shader "Custom/SimpleSurfaceShader"
{
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"
}
在Pass语义中编写。
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Custom/SimpleVertexFragmentShader"
{
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
}
}
}
另外还有固定函数着色器,我们需要完全使用 ShaderLab 的语法(即使用 ShaderLab 的渲染设置命令)来编写。目前已经逐渐抛弃。
Shader "Tutorial/Basic"
{
Properties {
_Color ("Main Color", Color) = (1, 1, 1, 1)
}
SubShader
{
Pass
{
Material {
Diffuse [_Color]
}
Lighting On
}
}
}
使用建议:
如果你想和各种光源打交道,你可能更喜欢使用表面着色器 ,但需要小心它在移动平台的性能表现。
如果你需要使用的光照数目非常少,例如只有单个平行光,那么使用顶点/片元着色器是个更好的选择。
最重要的是,如果你有很多自定义的渲染效果,那么请选择顶点/片元着色器。