Unity着色器圣经(Unity Shader Bible)笔记(1)

【翻译】Unity Shader Bible/Unity着色器圣经 全书目录第一章的笔记。

1.0.1 多边形物体的属性

多边形是指平面的封闭几何图形,由大于2条线段组成,首尾相连。

基本体(primitive) 是由多边形构成的三维几何对象,有顶点 (vertices)、切线 (tangents)、法线 (normals)、UV 坐标 (UV coordinates) 和颜色等属性,这些属性被存储在一种名为 “网格(mesh)“的数据类型中。最常见的基本体有球体、正方体、四边形、圆柱体和胶囊体等。

1.0.2 顶点

物体的顶点(vertices),相当于在二维或三维空间中定义曲面区域[^1]的点集。在 Maya 和 Blender 中,顶点表示为网格与物体的交点。这些点有两个主要特征:

  1. 它们是变换组件的子节点。
  2. 根据物体总体积的中心位置,它们有一个确定的位置。

1.0.3 法线

法线(normal) 是垂直于某个多边形表面的向量,用于确定面(Face)或顶点(Vertex)的朝向。

1.0.4 切线

A tangent is a vector of a unit of length that follows the mesh surface along the direction of the horizontal texture.
——Unity

切线与每个几何面 UV 的 U 坐标轴(概念见 1.0.5)方向一致。

Fig. 1.0.4a. 通常情况下我们无法通过shader访问副切线,而是通过切线与法线来计算副切线

1.0.5 UV 坐标

UV 坐标用于在三维模型表面定位二维纹理。以 UV 坐标作为参考,可以控制网格中的每个顶点与贴图中的哪些像素相对应。应用就是在着色器中,可以用 UV 坐标在 3D 模型上定位纹理或保存纹理信息。

将顶点定位到 UV 坐标的过程称为“UV 映射”。在此过程中,UV 会以扁平的二维形式呈现对象网格,并进行创建、编辑和组织。

Fig. 1.0.5a. 顶点可以在 UV 贴图中以不同方式排列

UV 坐标的面积表示为 0.0f 到 1.0f 之间的范围,其中 “0 “表示起点,”1 “表示终点。

Fig. 1.0.5b. 平面直角坐标系上的UV 坐标

UV 坐标(偶尔会有 W)中的 U 和 V 无实际意义(与 XYZ 坐标轴的 XYZ 一样)。用 UVW 纯因为 XYZ 被用掉了然后这帮人又想把 texture space 跟 XYZ 代表的 geometric space 做区分。

1.0.6 顶点颜色

顶点颜色(vertex color) 指的是软件导出模型时为模型指定的颜色,以便光照或别的颜色作用于该模型。

1.0.7 渲染管线架构

渲染管线指的是一个模型(例如扩展名为 .fbx 的模型)要渲染到电脑屏幕上所必须经历的一个完整流程。

Unity 将渲染管线的基本架构分为四个阶段:应用阶段(application stage)、几何处理阶段(geometry processing phase)、光栅化阶段和像素处理阶段。

Fig. 1.0.7a. 渲染管线的逻辑流程

1.0.8 应用阶段

应用阶段从 CPU 开始,负责场景中的一系列操作,如碰撞检测、纹理动画、键盘输入、鼠标输入……

应用阶段的功能是读取内存中的存储的模型数据,随后生成图元(primitives)(如三角形、直线、顶点)。在应用阶段结束时,这些信息都会被发送到几何处理阶段,通过矩阵乘法进行顶点变换。

1.0.9 几何处理阶段

几何阶段发生在 GPU 上,主要负责处理模型的顶点。它分为四个子阶段,分别是:顶点着色→投影→裁剪→屏幕映射。

在应用阶段完成图元装配后,顶点着色阶段(顶点着色器阶段)将处理两项主要任务:

  1. 计算模型顶点的位置。
  2. 将顶点位置转换到不同的坐标空间下,以便投影到计算机屏幕上。

此外,在顶点着色阶段中,我们还可以选择要传递给后续阶段的属性。这意味着在顶点着色阶段中,我们可以加入法线、切线、UV 坐标等属性。

投影和裁剪是应用阶段的一部分。注意,整个渲染过程只针对位于摄像机视锥体(也被称为观察空间)内的模型。投影和裁剪的结果取决于使用的摄像机是透视相机还是正交相机,而且只有位于相机视锥体内的部分才会被投影、裁剪,最终映射到屏幕上。

Fig. 1.0.9 b 无裁剪(左)与裁剪后(右)

将裁剪好的模型写入内存后,就进入到了屏幕映射阶段。在屏幕映射阶段,场景中的三维物体会被转换成屏幕坐标(screen coordinates)(也被称为窗口坐标(window coordinates))。

1.10 光栅化阶段

模型进入到这个阶段时已经有了屏幕坐标(二维坐标),现在我们必须在投影区域内找到屏幕上物体所占据的所有像素,这个过程被称为光栅化。光栅化可以看作是场景中的物体与屏幕上的像素之间同步的过程。

对于每个物体,光栅化程序都会执行两个过程:三角形设置和三角形遍历

三角形设置负责计算屏幕上每个三角形边的像素坐标信息,并将这些数据发送到三角形遍历阶段。三角形遍历会列出每个三角形网格所覆盖的像素,通过这种方式生成一组称为“片元(fragments)”的序列。

片元并不是真正意义上的像素。However, this word is used many times to refer to an individual pixel.

1.1.1 像素处理阶段

使用前序阶段得到的数据进行插值,当所有像素都准备好投射到屏幕上后,最后一个阶段——像素处理阶段就准备开始了。片元着色器阶段(也称为像素着色器阶段)负责决定每个像素是否可见,它的基本工作是计算每个像素的最终颜色,然后将其发送到颜色缓冲区。

Fig. 1.1.1a. 几何图形覆盖的区域(左)转换为屏幕上的像素(右)

1.1.2 渲染管线类型

在当前的 Unity 版本中,一共有三种类型的渲染管线:支持最古老版本的内置渲染管线(Built-in RP);通用渲染管线(URP)和高清渲染管线(HDRP),它们属于更为先进的可编程渲染管线(SRP),经过预先优化可以展现出更好的图形性能。

一种渲染管线可以有不同的处理路径,这些路径被称为渲染路径。

一条渲染路径对应一系列与照明和着色有关的操作。常见的渲染路径包括前向渲染(forward rendering)、延迟渲染(deferred shading)、旧版延迟(legacy deferred)和旧版顶点光照(legacy vertex lit)。

在 Unity 中,默认的渲染路径是前向渲染,这也是 Unity 的三种渲染管线的初始渲染路径。前向渲染具有更强的显卡兼容性和光照计算限制,这使得它成为一个更优的选择。需要注意的是,在 URP 中我们只能使用前向渲染作为渲染路径,而 HDRP 允许使用 前向渲染延迟渲染 进行照明材质渲染。

Fig. 1.1.2b. 要想在内置渲染管道中选择渲染路径,我们需要进入层级,选择主摄像机,然后在"渲染路径"属性中根据项目需要更改配置

假设场景中有一个模型和一束方向光。光线和模型之间的相互作用基于两个要素,它们分别是光照特性和材质特性。这两个要素之间的相互作用称为光照模型

基本光照模型相当于将环境光、漫反射和镜面反射三种不同的“光”叠加在一起。

光照计算在着色器中进行,可以采用逐顶点或逐片元的方式进行计算。当通过一个个顶点计算照明时,称为逐顶点照明,在顶点着色器阶段执行光照计算;当通过一个个片元计算照明时,称为逐片元或逐像素照明,在片元着色器阶段执行光照计算。

1.1.3 前向渲染

前向渲染是默认的渲染路径,支持诸如法线贴图、逐像素光照、阴影等多种材质功能。前向渲染包含了两种可以在着色器内编程的pass,分别是 base pass 和 additional pass。[^2]

一个通道 (Pass) 可以定义一个名称 (Name) 和任意数量的标签 (Tags)。这些名称/值字符串用于将通道的意图传达给渲染引擎。
——Unity
所以 pass 大致可以理解为一个“意图”

在base pass中,我们可以定义 ForwardBase 光照模型;而在additional pass中,我们可以定义 ForwardAdd 光照模型。这两种模型都具有在着色器中计算光照的功能。

Bass pass 可以逐像素地处理平行光,当场景中有多个平行光源时,则会处理最亮的光源。除此之外,bass pass 还可以处理光照探针、全局照明和环境光(天空盒光照)。

顾名思义, Additional pass 可以逐像素地处理额外的光源,也可以处理影响模型的阴影。如果场景中有两束光,我们的模型将只受其中一束光的影响。但如果我们为它配置了additional pass,那么该模型将同时受两束光的影响。

有一点我们必须考虑到:每一个计算照明的pass都会单独生成一个绘制调用(draw call)。绘制调用指的是每次我们想要在电脑屏幕上绘制一个元素时,在 GPU 中进行的图形调用。这些调用需要大量的计算,因此需要尽可能地减少调用次数,尤其是是运行在移动设备上的项目。

具体例子看原书给的四个球和一束平行光

1.1.4 延迟渲染

延迟渲染确保了场景中只有一个用于计算每个光源的pass,而且只计算受光源影响的部分像素,这些计算都是通过分离几何体和光源实现的。

延迟渲染的优点在于我们可以在场景中设置大量影响不同模型的光线、提升最终的渲染效果,代价仅是增加了 GPU 上每个像素的计算量。

虽然在多光源计算方面延迟渲染优于前向渲染,但它也存在一些硬件兼容性方面的限制和问题。

1.1.5 我该用哪种渲染管线?

  • 如果我们要开发 PC 游戏,我们可以使用 Unity 三个渲染管线中的任何一个,因为一般来说 PC 的计算能力比移动设备或掌机都要大。如果我们想要高清晰度的游戏画面,我们可以使用高清渲染管线(HDRP)或内置渲染管线进行创建。
  • 如果我们只需要中等清晰度的游戏画面,我们可以使用通用渲染管线(URP)或内置渲染管线。

内置渲染管线更加灵活、技术含量更高,(为了灵活性)没有经过预先优化。HDRP 和 URP 都已经过预先优化,前者可以生成高端图像效果,后者可以生成中端图像效果。

选择渲染管线的另一个重要因素是着色器。一般来说,在 HDRP 和 URP 中,着色器都是在 Shader Graph 中创建的。 Shader Graph 是一种图形化编辑器,允许我们通过节点创建着色器。我们可以通过节点直观地调整着色器效果,而无需使用 HLSL 编写代码。但需要注意的是,如果我们升级项目的 Unity 的版本(例如从 2019 版升级到 2020 版),Shader Graph 很可能无法编译,因为 Shader Graph 的版本和更新与 Unity 的版本有关。

在 Unity 中创建着色器的最佳方法是使用 HLSL 语言编写,因为这样可以确保我们的着色器程序能够在不同的渲染管线中编译,并且无论项目的 Unity 版本是否升级都能继续工作。

1.1.6 矩阵与坐标系统

矩阵(matrix) 是我们在编写着色器时经常遇到的一个概念,是由一组数字组成的阵列。矩阵计算遵循一定的算术规则,经常用于计算机图形学中。

在 Unity 中,矩阵常应用于坐标空间的变换,我们可以找到以下矩阵:

  • UNITY_MATRIX_MVP
  • UNITY_MATRIX_MV
  • UNITY_MATRIX_V
  • UNITY_MATRIX_P
  • UNITY_MATRIX_VP
  • UNITY_MATRIX_T_MV
  • UNITY_MATRIX_IT_MV
  • unity_ObjectToWorld
  • unity_WorldToObject

上面所提到的这些矩阵都是4*4的矩阵,意思是矩阵有四行四列,

$$ \text{UNITY_MATRIX} = \begin{pmatrix} \mathbf{X_x} & Y_x & Z_x & T_x \ X_y & \mathbf{Y_y} & Z_y & T_y \ X_z & Y_z & \mathbf{Z_z} & T_z \ X_t & Y_t & Z_t & \mathbf{T_w} \end{pmatrix} $$

(从这里开始这一节的理解能力直线上升,快速过概念是根本看不懂的。推荐结合原文多看几遍)

在 Maya 中模型默认有两个节点,分别是 “变换”(transform)节点和 “形状”(shape)节点,它们都负责计算顶点在一个称为 “模型空间”(object-space)的空间中的位置,该空间定义了顶点相对于模型中心的位置。

模型空间中的每个顶点都要乘以一个被称为模型矩阵(UNITY_MATRIX_M)的矩阵才能得到最终坐标。通过模型矩阵,我们可以修改模型顶点的平移、旋转和缩放的值。每当我们平移、旋转或缩放我们的模型时,模型矩阵都会更新。这一相乘的过程在着色器的顶点着色阶段进行,作用于模型中的所有顶点。

世界空间指的是模型顶点相对于世界中心所处的位置,也就是场景的 XYZW [0, 0, 0, 1] 这一点与模型顶点位置之间的距离。如果我们想将顶点坐标从模型空间转换到世界空间,可以使用内置着色器变量 unity_ObjectToWorld。

Fig. 1.1.6b

观察空间指的是模型顶点相对于摄像机所处的位置。如果要将顶点坐标从世界空间转换到观察空间,我们可以使用内置的 UNITY_MATRIX_V 矩阵。

Fig. 1.1.6c

裁剪空间(也被称为投影空间)指的是模型顶点在摄像机视锥体中的位置,它会受到摄像机的近裁平面、远裁平面和视场大小的影响。如果我们想将顶点坐标从观察空间转换到裁剪空间,可以使用内置的 UNITY_MATRIX_P 矩阵来实现。

Fig. 1.1.6d

最后补充变换矩阵的定义。上面提到的所有矩阵都是变换矩阵,用来将模型从一个空间转换到另一个空间。

例如,着色器内置的 UNITY_MATRIX_MVP 指的是三个不同矩阵的乘法:M 指模型矩阵,V 指观察矩阵,P 指投影矩阵。该矩阵主要用于将物体的顶点从模型空间转换到裁剪空间。
请记住,模型是在三维(3D)环境中创建的,而最终投影到电脑屏幕上的对象是二维(2D)的,因此我们必须将模型从一个空间转换到另一个空间。

[^1]: surface 这个词,在图形学领域偏向于翻译为“曲面”,但实际工程中往往翻译为“表面”(比如 surface texture 表面纹理)。

[^2]: (译者注:pass 一般不翻译成中文。Base 指基础,additional 指附加)