渲染管线

渲染管线_第1张图片

一个游戏开发者的点滴积累。

对于笔者这个游戏开发者来说,最先接触到的就是图形API,一开始是DX后来是OpenGL,现在还是OpenGL应用更广泛些。

扯远了,只看应用不懂原理不是笔者的风格,无意中看到了一些关于渲染管线的东西之后觉得很有收获,迫不及待地想将它记录下来,于是便有了这篇文章。

GPU是如何执行绘制的?

先回顾一下基本的东西。在OpenGL库中,定义了十种基本图形,其中,对我们最重要的是以下三种:点、线环以及三角形条带。点非常容易理解,不多说,线环是把连续的点连接起来形成的一个封闭图形,在绘制的时候不填充中间部分。三角形条带则是一连串的点,每三个点连起来形成的多个三角形,三角形的组成方式是0、1、2,1、2、3,2、3、4,依此类推。三种图形的表现如下图所示:


渲染管线_第2张图片

说完了基本图形,就到了处理过程。所有的渲染都是一个将数据从CPU发往GPU处理的过程。应用程序调用图形API(例如OpenGL),图形API调用显卡驱动程序,驱动程序负责将图形API的函数调用转变成GPU可识别的代码。与此同时,CPU会把内存中的图形数据传输到显存中,这些数据包括:顶点数据、纹理数据、着色器参数等。

显存中同样也保存了很多数据,包括:两个(前/后)图像缓冲区,深度/模板缓冲,纹理图等。其中,最重要的就是前后图两个图像缓冲区(其他的一些书中也称为颜色缓冲区)。前缓冲区中的数据被直接显示到显示器上,后缓冲区中的数据是下一帧就要显示的数据,计算完成之后的数据都会放到后缓冲区中以供使用。当后缓中区中的数据完成计算后,前后缓冲区就会切换,将下一帧的内容显示出来。


渲染管线_第3张图片

开发者面对的显示流程

对我们开发者来说,我们能控制的东西分为两个部分:一是场景中的数据;二是如何绘制这些数据。场景中的数据不仅包括可以看到的部分,还有不能看到的部分(场景剔除的部分)。

要绘制一个场景,首先我们就得确定要绘制哪一片区域的东西。原因很简单,我们的计算力还没有多到可以绘制整个场景的程度,就算是全世界的计算力加起来也一样。于是,我们可以只绘制摄像机能“拍"到的那部分,这样就省事多了。

然而,只绘制能拍到的部分也不是那么直接的事。在计算机的世界里,要想能拍到东西,至少得知道物体在什么位置,这是常识吧。这就需要定义一个坐标系,一个能将整个场景中,每个物体在什么位置表示清楚的坐标系,这个坐标系,我们称之为世界空间(世界坐标系)

渲染管线_第4张图片

本地坐标系是物体在制作过程中所采用的坐标系

有了物体,还需要放个摄像机在场景里,调整摄像机的朝向,让它能拍到一些东西。因为对我们来说(不管我们是开发者还是玩家),只有能看到的东西才是有意义的,看不到的东西就可以不管,所以,我们在计算的过程中,会以摄像机为坐标原点,将能看到的物体转换到这个坐标系中,这个坐标系,我们称之为相机空间(相机坐标系)

渲染管线_第5张图片

接下来,我们就要确定哪些东西要被渲染,哪些东西不要渲染。这个过程被称为裁剪。一个真正的游戏裁剪过程十分复杂,包括空间划分也可以被归为裁剪的过程。这里我们只考虑狭义的裁剪,就是规定摄像机前的一块区域内的东西会被显示出来,其他的都不显示。

但是呢,有个问题,就是在相机空间中裁剪太复杂了,前面也说过了,我们的计算力很宝贵,那么有没有别的方法,既可以完成裁剪,又使计算变的简单呢?别说,还真有!就是(齐次)裁剪空间。


渲染管线_第6张图片

对相机空间中的物体进行投影变换后,我们的物体就到了裁剪空间中(至于齐不齐次,就看投影变换矩阵了)。在裁剪空间,我们会利用视锥体裁剪平面的数据来确定物体是在视锥体内,还是在视锥体外,还是部分在视锥体内。在视锥体内的图形保留,在视锥体外的图形剔除,这样便完成了裁剪工作。

最后一步,视口变换。视口变换就是将裁剪空间中已经被裁剪过场景的坐标与视口(可能是整个窗口,也可能是窗口中的一部分)的坐标对应的过程。因为视口有自己坐标系统(比如Windows窗口的坐标原点是左上角,往右是x轴正方向,往下是y轴正方向),可能与裁剪空间的坐标系统(比如OpenGL的坐标系统是左下角为原点,往右是x轴正方向,往上是y轴正方向,从屏幕往外是z轴正方向)不一致,所以,视口变换也就应运而生,它的本质是解决3D场景坐标系与屏幕坐标系对应关系不一致的问题。


渲染管线_第7张图片
视口变换

光栅化

渲染的最后一个阶段是光栅化,这个阶段的工作就是将已经准备好的数据在屏幕上显示出来。实际过程中,模型经过转换到视口空间之后,GPU会计算出每个像素的深度、像素的颜色、插值得出的纹理坐标、像素位置等数据。这些数据会被用来测试可见性,并且最终绘制到图像缓冲区。整个流程大致是这个样子:


渲染管线_第8张图片
光栅化流程

第一步——像素包含测试
这是光栅化操作中必须要执行的一步。这个步骤只做一件事,就是判断某个像素是否在当前的视口内。如果视口被其他的窗口挡住一部分,那么这部分的像素包含测试就会失败。

第二步——裁剪测试
我们可以在视口中定义一个矩形区域,只绘制这个区域之内的东西,这个矩形就被称为裁剪矩形,裁剪测试就是判断像素是否在裁剪矩形之内。

第三步——Alpha测试
当计算像素的颜色是,同时可以计算像素的透明度Alpha数值。我们可以指定一个Alpha数值,只有像素的Alpha值与我们指定的Alpha数值满足某种我们需要的关系才被绘制。

第四步——模板测试
与Alpha测试类似,只是这个阶段会从像素的模板缓冲区中读取模板值,与我们设置的模板数值进行比对,满足某个条件才会绘制。

第五步——深度测试
将像素的深度值和深度缓冲区中的深度值进行比较,如果满足关系,那么就会被绘制,像素中的深度值会写入到深度缓冲区中,作为下一次比较的参数。

第六步——混合
将当前像素的颜色与图像缓冲区中的颜色通过某种算法生成一个新的颜色值,保存到图像缓冲区中。当然,最简单的方法是用当前像素的颜色覆盖图像缓冲区中的颜色。也可以用一些算法来模拟一种透明的效果。

总结

本文中,我们理清了渲染管线的渲染流程,对其中的每一个阶段做什么事情有了一个大致的概念。别小看这些概念,有这些基础的概念垫底,学习更深的内容才能得心应手。

参考资料

3D游戏与计算机图形学中的数学方法(第三版)
3D游戏编程大师技巧

你可能感兴趣的:(渲染管线)