“你对渲染管线了解多少?”
当我听到这个面试题的时候,我是懵逼的。很长时间以前学的图形学知识,虽然看了红蓝宝书,并且熟悉OpenGL,但是关于渲染管线的细节,仍然有诸多模糊之处,非常惭愧。在19年的时候,我不知死活的去面试业内大厂的的引擎工程师,在这道面试题当中被DISS了。
知耻而后勇,在接下来几个月的学习总结当中,我拿到了公仔厂TIMI的实习OFFER,但是遗憾的是由于实验室老师不放,只能等着秋招再战。通过本文,我将将我理解的渲染管线流程整理如下。
本篇文章将分为三个部分进行讲解:
- GPU渲染流程
- 图形渲染管线
- 如何使用纯粹的C++去实现出一个渲染管线,并且支持PBR效果。
全文不涉及任何数学知识,全部是理解的部分,我尽可能用白话来对整个渲染流程进行叙述。如果你想了解透视投影矩阵是怎么推倒的?如何确定View矩阵? 向大家安利《3D游戏与计算机图形学中的数学方法》。这本书里面的很多都是游戏引擎必知的干货。
如果你想打破渲染黑盒,想知道OpenGL背后发生了什么,请关注一下我用1个月疫情时间写的纯C++的PBR渲染管线。具体实现思路在本文第三部分讲解。
纯C++实现PBR软渲染管线Github源码
https://github.com/OneSilverBullet/SilverGL-Soft_Render_Pipline
1. GPU渲染流程
所有渲染,都是将数据从CPU传输到GPU的过程。
从GPU的视角来看渲染的话,非常简洁明了。对于程序整体框架来说大致分为如下的步骤:
- 应用程序调用图形API(opengl/dx12)。
- API调用GPU 驱动程序。
- GPU驱动程序负责将图形API函数转换为GPU可识别编码。
- CPU将内存当中的Data传递至GPU。
- 此时GPU拥有数据与程序代码,就可以执行,并且将图像渲染至屏幕上。
2. 图形渲染管线
在上一节从全局角度去看渲染,本节我们深入渲染管线去理解渲染究竟是怎么发生的。如何渲染出炫酷的图像。
如图2所示,我们可以看到非常绚丽明亮的色彩,我们分析一下这张图里面的渲染原理。对于道路的石头质感,小屋的木质质感,则是贴图的功劳。对于火焰的光源以及自然的天光,则是光照的功劳。漂亮的模型是建模师通过3dmax努力的结果……当然,为了更加完美的图像,目前3A游戏的制作大多采用PBR光照、各种贴图的叠加、泛光、粒子系统等等的复杂叠加而成的。但是这些高级特点都离不开渲染管线作为基础。我们将渲染所需要的事物抽象如下:
接下来我们要加速了,开始要进入渲染管线的思维过山车当中,跟紧了。
2.1 渲染管线概览
接下来我们会针对每一个阶段进行剖析,但是首先,我们来介绍一些基础概念:
应用程序阶段:运行在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坐标。
(未完待续)
版权声明:本文为CSDN博主「Silver Gamer」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/memories_sunset/article/details/96382752