【游戏引擎开发必问】 渲染管线的剖析

1,426 阅读8分钟

“你对渲染管线了解多少?”

当我听到这个面试题的时候,我是懵逼的。很长时间以前学的图形学知识,虽然看了红蓝宝书,并且熟悉OpenGL,但是关于渲染管线的细节,仍然有诸多模糊之处,非常惭愧。在19年的时候,我不知死活的去面试业内大厂的的引擎工程师,在这道面试题当中被DISS了。

知耻而后勇,在接下来几个月的学习总结当中,我拿到了公仔厂TIMI的实习OFFER,但是遗憾的是由于实验室老师不放,只能等着秋招再战。通过本文,我将将我理解的渲染管线流程整理如下。本篇文章将分为三个部分进行讲解:

  • GPU渲染流程
  • 图形渲染管线
  • 如何使用纯粹的C++去实现出一个渲染管线,并且支持PBR效果。

全文不涉及任何数学知识,全部是理解的部分,我尽可能用白话来对整个渲染流程进行叙述。如果你想了解透视投影矩阵是怎么推倒的?如何确定View矩阵? 向大家安利《3D游戏与计算机图形学中的数学方法》。这本书里面的很多都是游戏引擎必知的干货。

1.GPU渲染流程

所有渲染,都是将数据从CPU传输到GPU的过程。

从GPU的视角来看渲染的话,非常简洁明了。对于程序整体框架来说大致分为如下的步骤:

  • 应用程序调用图形API(opengl/dx12)。
  • API调用GPU 驱动程序。
  • GPU驱动程序负责将图形API函数转换为GPU可识别编码。
  • CPU将内存当中的Data传递至GPU。
  • 此时GPU拥有数据与程序代码,就可以执行,并且将图像渲染至屏幕上。

image.png 本图在全局角度去看渲染流程的进行。渲染的本质就是将CPU数据转移到GPU进行计算。

2.图形渲染管线

在上一节从全局角度去看渲染,本节我们深入渲染管线去理解渲染究竟是怎么发生的。如何渲染出炫酷的图像。

image.png 再漂亮的图像,也是我们讨论的渲染管线所创建的

如图2所示,我们可以看到非常绚丽明亮的色彩,我们分析一下这张图里面的渲染原理。对于道路的石头质感,小屋的木质质感,则是贴图的功劳。对于火焰的光源以及自然的天光,则是光照的功劳。漂亮的模型是建模师通过3dmax努力的结果……当然,为了更加完美的图像,目前3A游戏的制作大多采用PBR光照、各种贴图的叠加、泛光、粒子系统等等的复杂叠加而成的。但是这些高级特点都离不开渲染管线作为基础。我们将渲染所需要的事物抽象如下:

image.png 渲染所需要的事物。看起来很简单,天下大事,必作于细。

接下来我们要加速了,开始要进入渲染管线的思维过山车当中,跟紧了。

2.1 渲染管线概览

image.png 渲染管线概览。不要害怕,这已经是全部东西了。

接下来我们会针对每一个阶段进行剖析,但是首先,我们来介绍一些基础概念:

  • 应用程序阶段:运行在CPU上的阶段,一般用于输入操作处理、动画处理、事件处理等等。
  • 几何阶段:负责逐个顶点以及逐个图元的操作。
  • 光栅化:以变换化过经过投影的顶点与着色信息为基础,逐个像素进行绘制的操作。将屏幕当中的2D Point转换到屏幕上的像素。
  • 缓冲区:每一个缓冲区都存储着不同的渲染信息,比如Z-Buffer存储的是每个像素的深度信息,模板缓冲区可能存储的是阴影像素。绘制出绚丽的图像,本质上说是诸多缓冲区之间的叠加与计算。
  • shader:着色器,一般在Opengl当中可以编码的是Vertex Shader 与 Fragment Shader。一般在shader当中进行光照方程的数学计算。比如Phong模型、PBR光照模型。

这些概念暂时不懂也没关系,先理解一下, 随着之后深入的探讨,会一一把这些坑填上的。

2.2 应用程序阶段

应用程序阶段是在CPU上运行的,因此开发人员可以完全的控制Application发生的一切。单单理解这个阶段或许难以理解,我们以一款ACT游戏为例:

  • 我们的角色需要绚丽的人物动画,比如:裂天破空斩。
  • 我们的人物斩击到怪物上,被弹刀,产生碰撞。
  • 通过键盘鼠标进行输入,d+d+j+k:旋风斩。
  • 逻辑上我们正前方有个怪物,但是由于没有渲染,我们看不到他。我们需要把怪物信息传递给下一个渲染阶段:几何阶段。确定有哪些模型需要渲染。
  • 流水在流动,通过贴图动画产生优美的环境。

综上所述,Application当中的工作就是:处理动画、碰撞、输入、需要渲染的模型。就这么简单。

2.3 几何阶段

几何阶段用来负责逐个顶点的操作。为什么要强调逐个顶点呢。一个模型无论多复杂,都是由顶点组成的。由索引来确定每一个顶点间关系,并且将顶点组合成一个个三角形。在几何阶段,我们集中针对模型当中的顶点进行处理。

2.3.1 Model&View Transform

Model Transform 对于每一个Object,我们都有一个局部坐标系。这个局部坐标系我们称之为Model坐标系。很容易理解,对于我们每个个体来说,当我们说我的胳膊的位置的时候,一般不会说我的胳膊在世界的经纬度,而是说我的胳膊在我身体的左右两侧。而这个坐标系,参照点就是我们自身,这个就是Model坐标系。在进行物体建模的时候,Model坐标系非常适合美术进行操作。比如美工在我后背上插一个金闪闪的翅膀。

但是当我们进行渲染的时候呢,仅仅知道每个物体的局部坐标系可远远不够。比如我现在在天津大学的实验室写下这篇文章,现在要在世界上定位我的位置,我说:我在实验室中间。这是完全不能定位我的位置的。因此,我们需要将局部坐标系转化为世界坐标系。

那么转化成世界坐标系的公式就是: 我的世界坐标 = 天津大学的世界坐标 x 实验室在天津大学的局部坐标 x 我在实验室的局部坐标

好了,例子说完了,我们说一些严肃的。

  • Model Transform:转换的是Model的顶点和法线。

  • 每个Model对应一个Model Transform。

  • 世界坐标系唯一,所有Model经过Model Transform变化后,每一个model都可以使用一个世界坐标对其位置进行唯一描述。 View Transfrom 对于模型世界坐标,这还远远不够。这个世界只有在我们观察的时候,对于我们才是有意义的。(唯心主义的思路)而作为玩家的我们观察虚拟世界的唯一方法就是通过Camera。而我们的目的是:求出其他模型的世界坐标相对于Camera所在的世界坐标的位置。这就引入了View Transform。

  • View Transform : 将模型的世界坐标系转换到Camera的观察坐标系下。是求Model和Camera之间的关系。

2.3.2 Projection

对于透视投影,只要学过图形学的小伙伴一定会知道正交投影和透视投影。但是可能不太清楚,这些看起来憨憨的视景体在渲染当中处于什么样的地位。Projection图形学当中最关键的一步。Projection的目的是:将3D的虚拟空间中的坐标映射到一个2D平面上。这个2D平面,是我们进行光栅化的基础。我们显示器上每一个像素的显示,都是基于这个2D平面生成的。

在渲染管线中,实现Projection很容易,依据当前屏幕的长宽比以及给定的其他参数,我们可以构造一个Projection矩阵,通过将View Space当中的顶点位置乘以Projection矩阵,就可以很容易得到每一个顶点的Clip坐标。对于Clip坐标,其x,y值就是其对应的2D坐标。