第二章 渲染流水线
目录渲染流水线
渲染流水线是一个概念上的流水线,它与GPU流水线不同,渲染流水线并不在硬件上实现。
1. 应用阶段
①准备好所有的场景数据,例如摄像机的位置、视锥体、模型、光源等。
②为了提高渲染的性能,我们还要进行一个culling(剔除)的工作,比如:一个三角形有两个面,通常后面的面是不可见的,这就利用了backface-culling 的思想。
③设置渲染的状态,比如材质、纹理、Shader等
④输出渲染图元
2. 几何阶段
处理图元信息,将其转化为顶点的信息。通过对输入的渲染图元进行多步处理后,这一阶段将会输出屏幕空间的二维顶点坐标、每个顶点对应的深度值、着色等相关信息,并传递给下一个阶段。
3. 光栅化阶段
根据传来的屏幕顶点信息,逐像素处理。
CPU与GPU之间的通信
1. 把数据加载到显存中
渲染所需的数据从硬盘中加载到内存(RAM),再从内存中加载到显存(VRAM),具体如下图所示。
2. 设置渲染状态
渲染状态定义了场景中的网格是怎么被渲染的。比如:
- 使用哪个顶点顶点着色器(Vertex Shader),用于确定顶点位置。
- 使用哪个片元着色器(Fragment Shader),用于指定颜色、光照、阴影等效果。
- 使用什么光源
- 使用什么材质
如下图所示
3. 调用draw call
CPU向GPU发出一个draw call, GPU根据手头的数据和渲染状态,开始进行渲染。
GPU流水线
接下来,我们将对所有的流水线阶段进行详细的解释。
顶点着色器
顶点着色器的主要任务是:坐标变换和逐顶点光照
坐标变换将模型坐标转移到齐次裁剪坐标系
裁剪
由于我们的场景很大,其中有很多图元并不完全在视野内。我们将这些图元分成三类:
- 完全在视野内:直接传递给下一个阶段。
- 完全不在视野内:不需要传递。
- 不完全在视野内:视野内的顶点保留,视野外的顶点去掉,图元与坐标系边界的交点变成新的顶点。这个过程叫做裁剪。
具体如图
屏幕映射
从齐次裁剪坐标系转换到屏幕坐标系,根据屏幕大小进行缩放。这个变换保留了(x,y)的值。
但同时,z的值并不会被抛弃,它决定了顶点的深度,也就决定了谁覆盖谁的问题。z值和屏幕坐标系共同组成了窗口坐标系。
三角形设置
从三角形设置阶段就进入了光栅化阶段。光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。
在上一个阶段我们得到了三角形的顶点坐标,但是只有顶点坐标是无法画出三角形的,我们需要对给定的两个顶点,求出他们连线所经过的所有像素坐标。为下一个阶段做准备。
三角形遍历(扫描变换)
逐像素检查每个像素是否被一个三角形覆盖,如果是的话,就生成一个片元。
片元着色器
前面的光栅化阶段实际上并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用来表述一个三角网格是怎样覆盖每个像素的。而每个片元就负责存储这样一系列数据。真正会对像素产生影响的阶段是下一个流水线阶段——逐片元操作(Per-Fragment Operations) 。
片元着色器的输入是上一个阶段对顶点信息插值得到的结果,更具体来说,是根据那些从顶点着色器中输出的数据插值得到的。而它的输出是一个或者多个颜色值。
逐片元操作
逐片元操作是OpenGL里面的说法,在DX中用输出合并阶段来表示。
这一阶段的主要任务有:
①确定每个片元的可见性,通过深度测试、模板测试等确定
②将所有通过测试的片元存入颜色缓冲区进行混合
模板测试通常用于限制渲染的区域。
对于不透明物体,开发者可以关闭混合(Blend) 操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。但对于半透明物体,我们就需要使用混合操作来让这个物体看起来是透明的。
总结:什么是Shader
- Shader是GPU流水线上一些可高度编程的阶段。
- 一些特定位置的着色器,如顶点着色器、片元着色器。
- 依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换以及传递数据,用片元着色器来进行逐像素的渲染。