《shader入门精要》笔记-第3章-Unity Shader 基础

UnityShader概述

unity中的shader

Standard Surface Shader

会产生一个包含了标准光照模型(使用了Unity5中新添加的基于物理的渲染方法,详见第18章)的表面着色器模板

Unlit Shader

会产生一个不含光照(单包含雾化效果)的基本顶点/片段着色器

ImageEffect Shader

为我们实现各种屏幕文件.这类Shader旨在利用GPU的并行性来进行一些与常规渲染流水线无关的计算,而不在这本书的讨论范围内(草)

UnityShader的结构

SubShader

Subshader中定义了一系列的Pass一级可选的状态和标签的设置.

每个Pass定义了一次完整的渲染流程,但如果pass的数目过多,往往会造成渲染性能的下降.因此,我们应尽量使用小数目的Pass.

状态和标签同样可以在Pass声明.不同的是,SubShader中的一些标签设定是特定的.也就是说,这些标签设置和Pass中使用的标签是不一样的.而对于状态设置来说,其使用的语法是相同的.但是,如果我们在SubShader中进行了这些设置,那么将会用于所有Pass

状态设置

ShaderLab提供了一系列渲染状态的设置指令,这些指令可以设置显卡的各种状态.例如是否开启混合/深度测试等.

状态名称 设置指令 解释
Cull Cull Back/Front/Off 设置剔除模式.剔除背面/正面/关闭剔除
ZTest ZTest Less Greater/LEqual/GEqual/NotEqual/Always 设置深度测试时使用的函数
Zwrite ZWrite On/Off 开启/关闭深度测试
Blend Blend SrcFactor DstFactor 开启并设置混合模式

当在SubShader块中设置了这些渲染状态时,将会应用到所有Pass,如果我们不想这样,可以在Pass语义块中单独进行上面的设置.

SubShader标签

SubShader的标签是一个键值对,他的键和值都是字符串类型.
标签结构如下:

1
Tags { "TagName1" = "Value1" "TagName2" = "Value2" }
标签类型 说明 例子
Queue 控制渲染顺序,指定该物体属于哪一个渲染队列,通过这种方式可以保证所有的透明物体可以在所有不透明物体后面被渲染(详见第8章).我们也可以自定义使用的渲染队列来控制物体的渲染顺序 Tags { “Queue” = “Transparent” }
RenderType 对着色器进行分类,例如这是一个不透明的着色器,或是一个透明的着色器.这可以用于着色器替换功能(啥玩意) Tags { “RenderType” = “Opaque” }
DisableBatching 一些SubShader在使用Unity批处理功能时会出现问题,例如使用了模型空间下的坐标进行顶点动画(详见第11.3节).这时可以通过该标签来指明是否对该SubShader使用批处理. Tags { “DisableBatching” = “True” }
ForceNoShadowCasting 控制使用该SubShader的物体是否会投射阴影(详见8.4)节 Tags { “ForceNoShadowCasting” = “True” }
IgnoreProjector 如果该标签设置为True,那么使用该SubShader的物体不会受到projector的影响.通常用于半透明物体. Tags { “IgnoreProjector” = “True” }
CanUseSpriteAtlas 当该SubShader是用于精灵时,将该标签设为False Tags { “CanUseSpriteAtlas” = “False” }
PreviewType 指名材质面板将如何预览该材质.默认情况下,材质将显示为一个球形.我们可以通过将该标签的值设为"Plane" "SkyBox"来改变预览类型. Tags { “PreviewType” = “Plane” }

Pass语义块

Pass语义块包含的语义如下

1
2
3
4
5
Pass {
[Name]
[Tags]
// Other Code
}

首先,我们可以在Pass中定义该Pass的名称,例如

1
Name "MyPassName"

通过这个名称,我们可以使用Shader的UsePass命令来直接使用其他UnityShader中的Pass.例如:

1
UsePass "MyShader/MYPASSNAME"

这样可以提高代码的复用性.需要注意的是,由于Unity内部会把所有Pass的名字转换成大写字母的表示,因此在使用UsePass命令时必须使用大写的名字.

Pass同样可以设置标签,但它的标签不同于SubShader标签.这些标签也是告诉渲染引擎我们希望怎么来渲染该物体.

标签类型 说明 例子
LightMode 定义该Pass在Unity的渲染管线中的角色 Tags { “LightMode” = “ForwardBase” }
RequiresOptions 用于指定当满足某些条件时才渲染该Pass,它的值是一个由空格分隔的字符串.目前,Unity支持的选项有: SoftVegetation. 在后面的版本中,可能会增加更多的选项. Tags{ “RequireOption” = “SoftVegetation” }

除了上面的普通Pass定义外,Unity Shader还支持一些特殊的Pass, 以便进行代码复用或实现更复杂的效果.

  • UsePass
    如之前所说,可以引入其他Unity Shader中的Pass.
  • GrabPass
    负责抓取屏幕并将结果存储在一张纹理中,以用于后续的Pass处理(详见10.2.2节)

FallBack

1
2
3
4
FallBack "name"
FallBack Off

FallBack "VertexLit"

事实上,FallBack还会影响阴影的投射.在渲染阴影纹理时,Unity会在每个UnityShader中寻找一个阴影投射的Pass.通常情况下,我们不用自己专门实现一个阴影投射的Pass,这是因为FallBack使用内置Shader中包含了这样一个通用Pass.

因此,为每个UnityShader设置正确的FallBack是非常重要的.

UnityShader的形式

尽管UnityShader可以做的事情非常多(如设置渲染状态等),但其最重要的任务还是指定各种着色器所需的代码.这些着色器代码可以写在Shader语义块中(表面着色器的做法),也可以写在Pass语义块中(顶点/片元着色器和固定函数着色器的做法).

表面着色器

Surface Shader
是Unity自己创造的一种着色器代码类型.它需要的代码量很少,Unity在背后做了很多工作,单渲染代价比较大.当给Unity提供一个表面着色器的时候,它在背后仍旧会将它转换成对应飞顶点/片元着色器.

它存在的价值在于,Unity为我们处理了很多光照细节,使得我们不需要再操心这些烦人的事情.
一个非常简单的表面着色器示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Shader "Custom/sur"
{
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会在背后去帮我们做好这些事情.我们要做的只是告诉它,“用这些纹理去填充颜色,用这个法线纹理去填充法线,使用Lambert光照模型”

CGPROGRAM和ENDCG之间的代码是使用Cg/HLSL编写的,也就是说,我们需要把Cg/HLSL语言嵌套在ShaderLab中.值得注意的是,这里的Cg/HLSL是Unity经过封装后提供的,它的标准语法和标准的Cg/HLSL几乎一样,但还是有细微不同.例如有些原生的函数和用法Unity并没有提供支持.

顶点/片元着色器

Vertex/Fragment Shader
更加复杂,但灵活性更高.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Shader "Custom/vertexfragment"
{
SubShader{
Pass {
CGPROGRAM

#pragma vertex vert
#pragma fragment frag

float4 vert(float4 v : position) : SV_POSITION{
return mul(UNITY_MATRIX_MVP, v);
}

float4 frag() : SV_Target{
return fixed4(1.0,0.0,0.0,1.0);
}

ENDCG
}
}
}

和表面着色器类似,顶点/片元着色器的代码也需要定义在CGPROGRAM和ENDCG中间.但不同的是,顶点/片元着色器是写在Pass语义块中的,而非SubShader语义块内.原因是我们需要自己定义每个Pass需要使用的Shader代码.虽然我们可能需要编写更多的代码,但带来的好处是灵活性很高.更重要的是,我们可以控制渲染的实现细节.

固定函数着色器

Fixed Function Shader
弃子,现在不怎么用的着色器
上面两种着色器都是用了可编程管线.而对于一些较旧的设备,它们不支持可编程管线着色器,因此,我们就需要使用固定函数着色器.
这些着色器往往只能完成一些非常简单的效果