光影 基本概念
何谓光影?
对于一个完整的现代图形应用程序(基于如 OpenGL、Vulkan、DirectX 等图形库)来说,着色器是它渲染场景的手段。
我们知道图形应用程序的目的是读取模型文件或硬编码几何体,并在屏幕上绘制。着色器就描述了我们传入的几何体在屏幕上的何种位置以何种方式绘制。
如果将资源包比作食材,那么着色器就是调料,上好的食材固然重要,但是调料也是激发出食材风味不可或缺的部分。
渲染模组/引擎如何帮助光影和游戏交流
渲染模组/引擎(下简称渲染模组)充当了游戏和光影的桥梁。由于原版所提供的信息极其有限(许多效果必须的变量没有直接提供, 缓冲区 也不足),如果想仅利用原版资源包的着色器来编写光影,无异于自讨苦吃。
渲染模组利用模组加载器提供的 接口 或直接对 Minecraft 源代码进行逆向工程并注入,接管了 Minecraft 的原版渲染管线,并提供了大量信息和更多缓冲区,为光影开发提供便利。
场景在着色器中发生了什么?
着色器类型
光影通常以多个着色器组成,着色器接收渲染模组提供的各种变量,以及先前乃至上一帧计算好的存入缓冲区的信息,按照程序进行计算后输出到指定的缓冲区。
不同渲染模组的工作原理不尽相同,所对应的光影包规格也有所区别。
一个着色器中又可以细分为多个阶段,也就是我们常说的顶点着色器、像素着色器等,这里按照通常管线的顺序,简单列举一下每种着色器计算的对象:
- 顶点着色器
Vertex Shader ,它的主要职责是变换坐标,包括顶点坐标、纹理坐标等,也可以处理顶点的颜色,计算对象为每个顶点(逐顶点操作)。
- 几何着色器
Geometry Shader ,这个阶段是可选的 ,它的主要职责是生成新的顶点,计算对象是每个图元,可以通过特定的索引值确定需要处理的顶点。
- 片段着色器
Fragment Shader ,它的主要职责是处理像素的颜色,也是大多数效果程序所处的位置,计算对象是每个像素。
- 计算着色器
Compute Shader ,这个阶段是可选的 ,它负责进行抽象计算。可以任意存取缓冲区,但是不能传入自定义变量,也没有默认输出。
当仅考虑顶点着色器和像素着色器时,在 上文 中我们所提到的所谓“ 何种位置 ”大多数时候就在顶点着色器中进行处理,而以“ 何种方式 ”则是顶点着色器和像素着色器的共同作用。
光栅化
光栅化通过将场景映射到二维平面上并分划为一个个的小格子,且仅保留每个格子中心点的信息来将几何体离散,然后一一对应到输出对象的最小单元(通常是缓冲区或屏幕的像素)上,也叫栅格化 (与 Photoshop 等图像处理软件中的同名功能一致)。
场景离散之后每个像素上都有确定的几何信息(法线、位置等)和纹理信息,然后根据这些几何信息利用数学方法来处理每个像素内容,因此这种方法也会造成一定的几何走样 (也就是我们通常所说的锯齿)。
“光栅化”这个说法通常是用作与光线追踪的对立:前者在栅格化之后再进行数学上的各种光照拟合和其他后期处理;而后者通过模拟光子运动计算光照之后直到需要输出图像或者进行其他后期处理时才栅格化。
渲染方法的发展
在计算机最早起步的阶段,还没有各种图形库和接口供开发者使用,那时候通常是通过准备特殊的图块字符映射表,然后将场景通过各种特殊的字符打印在屏幕上,那时候的图形也以 2D 为主。
随着计算机和 GPU 发展,3D 图形兴起,各种 3D 图形库也开始发展起来,OpenGL 就是其中之一。最早的图形库使用固定渲染管线 ,整个管线按照图形库的内置次序计算顶点光照、阴影等效果。开发者只可以配置渲染参数,没法精细控制每个几何体的效果,也没法自定义如何处理每个像素。
由于固定管线过于死板,希望能更自由地控制每个图元和像素的呼声越来越高, 可编程管线应运而生。
在可编程管线刚刚发展起来的早期图形程序中,渲染思想是将一类几何体全部准备好并传入特定着色器,然后立即在传入的几何体上利用其几何信息计算诸如阴影和反射等效果,再输出到屏幕上。这就是我们现在所说的向前渲染法 (Forward Shading)。
这在 3D 图形程序刚起步的早期是没什么问题的,那时候场景中的几何体还不多。然而随着场景几何体增多、几何体之间相对于视角的遮挡越来越频繁,这种做法开始产生越来越多不必要的开销。因为每个着色器都会将所有传入的几何体计算一遍,即使在之前或之后的着色器中这个几何体会被更靠前的几何体遮挡。
出于上述原因, 延迟渲染法 (Deferred Shading)应运而生,它的思想是不再在传入几何体的阶段立即计算大多数效果,而是分为两个阶段:
几何缓冲阶段 :通过着色器将纯色场景和诸如法线贴图和反射贴图等全部作为纹理映射到几何体上,再分别写入多个 缓冲区 ,并通过 颜色附件 在着色器之间进行传递;
延迟处理阶段 :之后的着色器读取对应缓冲区的这些信息,在铺屏四边形上统一计算光照、反射等其他效果。
延迟渲染还提供了一个优势,在向前渲染法中,像素着色器仅会对图元覆盖区域进行着色,这就导致了溢出类或者与其他类几何体有交互的特效难以正确绘制(比如镜头特效和 环境光遮蔽 等),而延迟渲染法由于使用了铺屏四边形,所以可以在屏幕上任意位置进行绘制。
但同时由于其使用缓冲区的特性和场景几何信息的不可插值性 1, MSAA 之类提升内部分辨率再进行降采样的抗锯齿方法也就不能使用了 2。
[1] 想象两个一远一近的像素,它们的颜色代表了它们的坐标,那么两个像素的颜色应该是确定且等于坐标的 。如果将它们的颜色取平均后再赋回,那么它们的颜色就不等于坐标了,也就失去了几何意义。
[2] 严格来说,如果在延迟渲染阶段不使用场景几何信息,或者保留降采样之前的几何信息,那么延迟渲染和 MSAA 也是可以共存的。