第3章 Unity Shader 基础
3.1 Unity Shader 概述
材质与 Unity Shader
在 Unity 中我们通常需要将材质(Material)
和 Unity Shader 配合使用,常见流程如下:
- 创建一个材质
- 创建一个 Unity Shader, 并将它赋给材质
- 将材质赋给要渲染的对象
- 在材质面板调整 Unity Shader 的属性,以得到满意的效果。
Unity 中的材质需要结合 GameObject 的 Mesh 或者 Particle Systems 组件来工作,它决定了我们的游戏对象看起来是什么样子的。默认情况下,一个新建的材质将使用 Unity 内置的 Standard Shader, 这是一种基于物理渲染的着色器。
我们把 Unity 中的 Shader 文件统称为 Unity Shader,其与我们之前提到的渲染管线中的 Shader 有所不同。
3.2 Unity Shader 的基础: ShaderLab
什么是 ShaderLab?
Unity Shader 是 Unity 为开发者提供的高层级的渲染抽象层,Unity 希望通过这种方式来让开发者更加轻松地控制渲染。
所有的 Unity Shader 都是使用 ShaderLab 来编写的,ShaderLab 是 Unity 提供的编写 Unity Shader 的一种说明性语言。它使用一种嵌套在花括号内部的语义(syntax)
来描述一个 Unity Shader 文件的结构。从设计上,ShaderLab 类似于 CgFX 和 Direct#d Effects 语言,它们都定义要显示一个材质的所有东西,而不仅仅是着色器代码。
一个 Unity Shader 的基础结构如下所示:
Shader "ShaderName"{
Properties{
// 定义属性
}
SubShader {
// 显卡 A 使用的子着色器
}
SubShader {
// 显卡 B 使用的子着色器
}
Fallback "VertexLit"
}
Unity 会根据平台将 Unity Shader 编译成真正的代码和 shader 文件,而开发者只需要和 Unity Shader 打交道即可。
3.3 Unity Shader 的结构
名字
每个 Unity Shader 文件的第一行都需要通过 Shader 语义来制定该 Unity Shader 的名字:
Shader "Custom/MyShader" {}
Properties
Properties 语义块中包含了一系列的属性,这些**属性会出现在材质面板中。**Properties 语义块的定义通常如下:
Properties {
Name ("display name", PropertyType) = DefaultValue
Name ("display name", PropertyType) = DefaultValue
...
}
名字(Name)
用于我们在 Shader 代码中访问使用显示的名字(display name)
则是显示在材质面板上的名字- 另外我们需要为每个属性指定他的
类型(PropertyType)
,我们还需要为每个属性设置一个默认值
Unity 允许我们重载默认的材质编辑面板,以提供更多自定义的数据类型,具体可参考Customer Shader GUI一文。
重量级成员 SubShader
每一个Unity Shader文件可以包含多个 SubShader 语义块,但最少要有一个。当Unity需要加载这个 Unity Shader 时,Unity 会扫描所有的 SubShader 语义块,然后选择第一个能够在目标平台上运行的 SubShader。如果都不支持的话,Unity 就会使用Fallback 语义指定的 Unity Shader。
SubShader语义块包含的定义通常如下:
SubShader {
//可选的
[Tags]
//可选的
[RenderSetup]
Pass {}
// Other Passes
}
标签(Tag)和渲染状态(RenderSetup)可以在 SubShader 中设置,也可以在 Pass 中设置,如果在 SubShader 中设置,则会对所有 Pass 生效,在 Pass 中设置则只对该 Pass 生效。
每个 Pass 定义了一次完整的渲染流程,如果 Pass 数目过多,往往会造成渲染性能下降,因此我们应尽量使用最小数目的 Pass。
RenderSetup
ShaderLab 提供了一系列渲染状态的设置指令,这些指令可以设置显卡的各种状态。
Tags
SubShader 的标签(Tags)是一个键值对,它的键和值都是字符串,它们是 SubShader 和渲染引擎之间的沟通桥梁,用于控制 Unity 怎样以及何时去渲染一个对象。
Pass 语义块
Pass 语义块包含的语义如下:
Pass {
[Name]
[Tags]
[RenderSetup]
// Other Code
}
首先我们可以在 Pass 中定义名称:
Name "MyPassName"
通过这个名称,我们可以在其他 Shader 中直接引用该 Pass:
UsePass "MyShader/MYPASSNAME"
需要注意的是,由于Unity内部会把所有 Pass 的名称转换成大写字母的表示,因此在使用 UsePass 命令时必须使用大写形式的名字。
3.4 Unity Shader 的形式
Unity Shader 支持三种方式:表面着色器(Surface Shader)
、顶点/片元着色器(Vertex/Fragment Shader)
、固定函数着色器(Fixed Function Shader)
。
表面着色器
表面着色器(Surface Shader)是 Unity 自己创建的一种着色器类型,其是 Unity 对顶点/片元着色器的更高一层的抽象,它存在的价值在于,Unity 为我们处理了很多光照细节,使得我们不再需要操心这些“烦人的事情”。Unity 最后会将表面着色器转换为对应的顶点/片元着色器。
一个简单的表面着色器代码示例如下:
Shader "MyShader/Surface"
{
SubShader
{
Tags { "RenderType"="Opaque" }
CGPROGRAM
#pragma surface surf Lambert
struct Input
{
float4 color : COLOR;
};
void surf (Input IN, inout SurfaceOutputStandard o)
{
o.Albedo = 1;
}
ENDCG
}
FallBack "Diffuse"
}
表面着色器被定义在 SubShader 语义块(而非 Pass 语义块)中的 CGPROGRAM
和 ENDCG
之间。原因是,表面着色器不需要开发者关心使用多少个 Pass、每个 Pass 如何渲染等问题,Unity 会在背后为我们做好这些事情。
顶点/片元着色器
在Unity中我们可以使用 Cg/HLSL语言来编写顶点/片元着色器(Vertex/Fragment Shader)
。它们更加复杂,但灵活性也更高。
Shader "MyShader/VertexFragment Shader"
{
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(0.0, 0.0, 0.0, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
和表面着色器类似,顶点/片元着色器的代码也需要定义在 CGPROGRAM 和 ENDCG
之间,但不同的是,顶点/片元着色器是写在 Pass 语义块内,而非 SubShader 内的。原因是我们需要自己定义每个 Pass 需要使用的 Shader 代码,虽然我们可能需要编写更多的代码,但带来的好处是灵活性很高。
固定函数着色器
对于一些不支持可编程渲染管线的较旧的设备,我们需要使用固定函数着色器(Fixed Function Shader)
来完成渲染,这些着色器往往只完成一些简单的效果。
Shader "Tutorial/Basic" {
Properties {
_Color ("Main Color", Color) = (1,0.5,0.5,1)
}
SubShader {
Pass {
Material {
Diffuse [_Color]
}
Lighting On
}
}
}
固定函数着色器的代码被定义在 Pass 语义块中,这些代码完全使用 ShaderLab 的语法来编写,而非使用 Cg/HLSL。
由于现在绝大多数 GPU 都支持可编程的渲染管线,这种固定管线的编程方式已经逐渐被抛弃。实际上,在 Unity 5.2 中,所有固定函数着色器都会在背后被 Unity 编译成对应的顶点/片元着色器,因此真正意义上的固定函数着色器已经不存在了。
选择何种 Unity Shader
- 如果光源数量较多,可以选择表面着色器,但需要小心它在移动平台的性能表现
- 如果光源数量较少,则使用顶点/片元着色器是更好的选择
- 最重要的是,如果你有很多自定义的渲染效果,那么请选择顶点/片元着色器
- 除非你有明确的要求必须使用固定函数着色器,否则不要使用它
答疑
我可以用 GLSL 来写吗
如果你坚持使用 GLSL 来写而不是 Cg/HLSL,那么你发布的平台就只有 Mac OS X、OpenGL ES 2.0 或者Linux,而对于 PC、Xbox 360 这样的仅支持 DirectX 的平台来说,你就放弃它们了。
扩展阅读
- Unity Shader 官方文档:https://docs.unity3d.com/Manual/SL-Reference.html
- NVIDIA Cg 文档:https://developer.download.nvidia.com/cg/index_stdlib.html
- NVIDIA Cg 教程:https://developer.download.nvidia.com/CgTutorial/cg_tutorial_chapter01.html