写在前面
开始学习《入门精要》第八章透明效果后,接触到了渲染顺序、透明度测试这些概念,才发现之前的渲染管线总结忘掉了光栅化和像素处理这两个阶段的学习记录,怪不得没什么印象。。。赶紧写一篇补上!
参考
《Unity Shader 入门精要》——冯乐乐
《Real-Time Redering 3rd》——毛星云
图形渲染管线1.0
【技术美术知识储备】图形渲染管线1.0-基本概念&CPU负责的应用阶段_flashinggg的博客
图形渲染管线2.0
【技术美术知识储备】图形渲染管线2.0-GPU管线概述&几何阶段_flashinggg的博客
2.0最后一部分讲到了几何阶段的图元装配环节——裁剪和屏幕映射,接下来就进入光栅化阶段和像素处理阶段:
1 光栅化阶段 Rasterization
光栅化阶段是光栅化渲染的核心部分,同时也是任何一个图形管线的核心。几何处理阶段输出的是屏幕空间下的顶点信息,以及与着色相关的深度值、法线、视角方向等信息,光栅化阶段获得了这些信息后负责完成三角形设置及三角形遍历的工作。
需要注意的是,为什么光栅化会被叫做“三角形”的设置和遍历?其实整个渲染的过程的渲染对象是图元(primitive), 这个图元可以是点、线、面,而由于三角形更加常见,因此光栅化操作通常被认为是基于三角形的。
(三角形设置及遍历是由GPU完成,我们没有任何控制权,这可以体现在写shader时我们并没有写入如何进行三角形遍历等设置,而是直接进入到了下一阶段——片元着色器)
1.1 三角形设置 Triangle Setup
三角形设置是光栅化的第一个小阶段,主要工作是为后续进行的三角形遍历准备数据,准备的数据可以分为以下两种用途:
- 为找寻三角形覆盖片元(即扫描变换,scan conversion)做准备——确定边界像素坐标信息(边缘函数,edge equations)、三角形表面差异(differentials)等
- 为对着色数据进行插值做准备——准备与着色相关的三角形表面数据
1.2 三角形遍历 Triangle Traversal
前面已经提到了,三角形遍历主要完成:
- 找到三角形网格覆盖的片元
- 使用三角形三个顶点信息对整个覆盖区域的片元进行插值
最终传递个下一阶段(像素处理阶段)的信息是一个片元序列,这些片元并不是严格意义上的像素,而是一个个状态的集合,这些状态包括了屏幕坐标、深度(z值)信息、法线、纹理坐标、三角形的哪一面可见(通过输入标志实现,该值对于单通道中正面和背面渲染不同材质十分重要!!!)等等信息。
2 像素处理阶段 Pixed Processing
像素处理阶段分为片元着色器(Fragment Shader)和逐片元操作(Pre-Fragment Operations)两个小阶段。
2.1 片元着色器 Fragment Shader
在这几天的Shader代码学习中意识到了:顶点着色部分和片元着色部分是一个Shader最最重要的部分。
2.1.1 获取的是什么?
片元着色器获取由顶点着色器输出 --> 裁剪和屏幕映射的图元装配环节 --> 三角形设置和遍历的光栅化这一番操作输出的一个片元序列,信息都是已经经过插值的。
2.1.2 主要目的?怎么做?
主要做了什么?——片元着色器主要目的是进行所有的逐片元着色计算。
怎么做?——片元着色阶段最常见也是最重要的技术之一是纹理贴图,我在【技术美术图形部分】纹理基础1.0-纹理管线一文中就提到了:“渲染管线中,一般会在渲染管线——片元着色阶段使用纹理。建模时会给每个模型顶点分配一个纹理坐标,在进入渲染管线——光栅化阶段时,生成的每个片元会根据每个顶点对应的纹理坐标差值得到新的纹理坐标,根据这个新的纹理坐标对传入的纹理图像数据进行采样获取纹理值。”
2.1.3 输出了什么?输出到了哪儿?
输出了什么?——片元着色阶段通过操作获得每个像素最终的颜色,输出的是一个数据集合,包含每个像素的各个颜色分量和像素透明度的值(RGBA)。例如在写一个Shader时,一个Pass里的片元着色器通常返回的值类型是fixed4。
输出到了哪儿?——这个值通常会输出到屏幕上或颜色缓冲器(一个颜色的矩阵列,每个颜色包含RGB三个分量)中,以进行后续的合并操作,同时深度值也可以被片元着色器修改。
2.1.4 局限性
虽然片元着色阶段可以实现很多重要的效果,且高度可编程,但它有一个局限性:片元着色器仅可以影响单个片元。也就是说,一个片元无法将自己的结果传递给另外的其他片元(导数情况另说)。
2.2 逐片元操作 Pre-Fragment Operations
这是整个渲染管线的最后一步,逐片元操作是OpenGL的说法,DirectX中把这一过程称为合并阶段(Merge),这一阶段和几何阶段的屏幕映射一样,虽然不可编程但是高度可配置。最常用于透明处理(Transparency)和合成操作的颜色混合(Color Blending)操作就是在这一阶段进行的。
这一阶段主要有两项任务:
- 决定每个片元的可见性
- 进行颜色混合
2.2.1 片元的可见性处理
这一阶段负责每个片元的可见性问题的处理,涉及到很多测试的工作。测试,顾名思义,要对每个片元做测试,片元通过了所有测试才能被打上“合格”的标签,只有合格的片元才能进入下一阶段——进入颜色缓冲区进行合并操作。
那么都有什么测试呢?实际上测试工作是个复杂的过程,不同图形API的具体实践流程也不相同,《入门精要》中给出了两个基本的测试——模板测试和深度测试。大概规划一下这一阶段一个片元需要经过:
模板测试 --> 深度测试 --> 混合
之后,才能最终被写入颜色缓冲器中。我们就可以选择开启/关闭模板测试和深度测试。
- 模板测试 Stencil Test
与模板测试相关的是模板缓冲(Stencil Buffer),每个片元上都有一个模板值(通产占用8个位),默认情况下模板值都为0。如果开启了模板测试,GPU会读取模板缓冲区中该片元位置的模板值,然后将这个读取到的模板值与参考值(reference value)进行比较,这个比较的关系是由我们来决定的,可以是大于、等于、小于等等的判断关系。当通过对比时,我们可以决定是舍弃/显示/修稿模板值等操作。
让我们举个更好理解的例子,我们可以在模板缓冲器中绘制一个⚪,至于如何绘制?很简单,一堆1里面只有⚪里面的片元模板值为0就行,接着就可以使用上述提到的判断关系让后续的图元仅在这个⚪中绘制,类似一个mask的操作,例如下图:
(图源:Unity ShaderLab 模板缓存(Stencil Buffer) 基本概念)
经过一番讨论我们可以发现,原来模板测试是在屏幕空间进行的,也就是说整个过程其实是在一个二维空间实现的。
- 深度测试 Depth Test
在Games101中就已经接触了深度测试,因此对我而言相比模板测试更容易理解。类比于模板测试有模板缓冲,深度测试也有深度缓存(Z-buffer),z-buffer和颜色缓冲器的形状大小一样,每一个片元都储存一个深度值(z值),记录着该位置处从相机到最近图元之间的距离。每次将图元绘制成相应像素时,都需要进行一次深度判断:z<z-buffer中的z,则对当前对应的z值和颜色进行更新;反之则无需改变。
2.2.2 进行颜色混合
一个片元经过层层测试工作的筛选,最终来到混合(Blend)阶段,
- 什么物体要进行混合?
当然并不是所有进行渲染的物体都需要有混合这一步操作的——只有透明或者半透明的物体,才会需要混合操作。对于一个不透明物体,前面通过了深度测试的片元可以直接覆盖掉当前的深度值,可以直接关闭掉混合操作。
- 怎么混合?为什么要混合?
开启了混合功能之后,GPU会取出源颜色和目标颜色,将两种颜色进行混合。这里我们需要搞清楚一个点:源颜色和目标颜色到底是什么?
这就涉及到为什么要进行颜色混合操作:我们知道,屏幕中呈现的画面其实是一次一次渲染得到的效果的连续呈现过程,当前帧画面中像素A位置的颜色和下一帧画面中像素A位置的颜色很大可能是不同的,也就是说颜色缓冲区中像素的颜色值(目标颜色)和片元着色器计算后产生的颜色值(源颜色)会不一样!我们该如何处理呢?是继续传递源颜色给屏幕,还是传递目标颜色给屏幕?还是要把二者用某种方式混合一下传递给屏幕?这就是混合(Blend)要做的事情!
- 混合操作手段唯一吗?
当然不是,往往混合操作是会有个混合函数的,这个函数与透明通道息息相关,我们可以做很多种操作:相加、相减、相乘等等,就像PS里有两个图层的混合模式选项。
3 关于缓冲器
3.1 其他缓冲器
上面提及的缓冲器有:颜色缓冲器、深度缓冲器、模板缓冲器,其中颜色缓冲器储存着每个片元的颜色值,深度缓冲器储存每个片元的z值,模板缓冲器储存模板值,还有其他可以用来过滤和捕获片元信息的缓冲器。
3.1.1 alpha 通道
alpha 通道(alpha channel)和颜色缓冲器在一起,可以储存每个片元的不透明值,例如:
fixed _cutOff;
fixed4 frag(v2f i) : SV_Target {
fixed4 texColor = tex2D(_MainTex, i,uv);
//Alpha Test
clip(texColor.a - _CutOff);
}
这里就是做了次获取片元alpha通道值与参考值作比较的操作。
3.1.2 帧缓冲器
帧缓冲器(frame buffer)包含一个系统具有的所有缓冲器,有时可以认为是颜色缓冲器和z缓冲器的组合。
3.2 双缓冲机制
双缓冲(Double buffer)机制是一种图形学里常用的画图技术,使用这种方法可以减少闪烁、撕裂等不良的显示效果,还可以减少等待时间。
当进行完光栅化和像素处理阶段的操作后,屏幕上已经可以看到相机视点呈现的效果了。但一个图元需要进行着色、模板和深度测试、混合的一系列操作才得以呈现在屏幕上,为了避免这个过程也被呈现到屏幕上,图形系统会使用双缓冲。屏幕绘制是在一个后置缓冲器(back buffer)中以离屏的方式进行的,后置缓冲器会不断地将绘制好的内容与传递给前置缓冲器(front buffer),当前已显示的内容和下一帧需要显示的内容就会在不影响显示的情况下完成替换,以保证我们看到的画面是连续的。
至此,整个渲染管线的过程就全部叙述完啦!
至于模板测试和深度测试以及混合阶段的细节操作,会在之后的实践中进一步体会,这里仅介绍在渲染管线中他们都是做什么的。