GAMES104课程笔记04-Rendering on Game Engine
这个系列是GAMES104-现代游戏引擎:从入门到实践(GAMES 104: Modern Game Engine-Theory and Practice)的同步课程笔记。本课程会介绍现代游戏引擎所涉及的系统架构、技术点以及引擎系统相关的知识。本节课主要介绍现代游戏引擎的渲染系统。
Introduction
在接下来的几节课中我们会开始介绍游戏引擎中的渲染系统。从早期的像素风格渲染到如今3A大作逼真的渲染效果,可以说渲染技术伴随了整个游戏行业的发展。
游戏渲染的理论基础和图形学中的渲染是一样的。不过渲染研究更关注于算法理论的正确性而对于实时性没有太多的要求,而在游戏渲染中实时性则至关重要。对实时渲染的关注构成了游戏渲染和渲染理论之间的主要差别。
Challenges on Game Rendering
游戏中渲染的难点有以下几个方面:首先游戏场景往往包含各种不同类型的渲染对象,同时需要在场景中实现光照、大气、天空、水面等不同的渲染效果。
其次,在图形学中我们不太关注渲染在硬件中的实现过程。而在游戏引擎中,为了充分利用计算资源则需要考虑渲染过程的硬件实现。
再者,人们对于游戏画质和帧率的要求逐渐提高。现代3A大作往往需要适配4K的画幅并且保证不同场景下有着足够高的帧率(60-120FPS)。
最后,游戏引擎除了渲染系统外还要为其它物理、逻辑系统提供支持,因此我们不能让渲染系统占掉全部的CPU计算资源。一般来说渲染系统只能占掉10-20%的计算资源,把省下来的资源让给其它玩法系统。
Rendering on Game Engine
总体来看,游戏引擎中的渲染系统是一个庞大的软件工程系统。在整个游戏行业几十年的不断实践中总结出了大量有效的渲染技术。
本课程中我们会逐步介绍游戏引擎的渲染系统。当然游戏引擎涉及到的渲染技术非常丰富,这里只能介绍一些基础的概念。本节课会介绍渲染的基础概念,在后面的课程中会分别介绍游戏行业中标准的材质光照系统、场景渲染和后处理方法以及游戏引擎中的渲染管线设计。
本课程不会涉及卡通渲染、2D渲染引擎、次表面以及毛发渲染等内容。
Building Blocks of Rendering
Rendering Pipeline and Data
目前游戏引擎渲染的主流方法仍是基于光栅化(rasterization)的渲染管线。
首先我们需要把场景中的物体投影到NDC上,然后分别计算平面上每一个像素对应的渲染对象。
接下来对于每一个像素需要调用相应的shader计算像素的颜色。
在调用shader时往往还需要通过纹理采样的方法进行反走样等处理。
Understand the Hardware
渲染计算的特点是有大量的像素需要进行计算,而像素之间的计算则往往是相互独立的。因此人们设计出了GPU来执行图形渲染计算,这样还解放了CPU的计算资源。可以说现代GPU的发展也推动了整个游戏行业渲染技术的进步。
要了解现代GPU的架构我们首先来复习一下SIMD和SIMT的概念。SIMD是指在运行程序时可以把一条指令同时执行在不同的数据上,目前现代CPU对于SIMD有着很好的支持,这种技术在高性能计算等领域中有着广泛的应用;而SIMT则是把同一条指令分配到大量的计算核心上同时执行,现代GPU的计算过程更类似于SIMT。
在现代GPU架构中有着大量重复的内核,每一组内核称为一个GPC。在每个GPC内部存放着大量的SM,而每个SM中还有着大量的CUDA核心用来执行数学运算,当SM接收到指令进行计算时会把运算分配给CUDA核心进行并行计算。同时GPU上还有share memory用来实现GPU上不同核心以及GPU和CPU之间的通信。
GPU和CPU之间通信的代价是非常大的,因此在渲染系统中会尽量把数据通信设计为单向的。这样GPU只需要读取CPU发送的数据而无需反向传输渲染的结果。
为了进一步提高数据读取的效率还需要合理地运用GPU缓存。
在长期的工程实践中人们总结了GPU渲染的性能瓶颈如下:
当然GPU的架构也是一个不断发展的领域,目前现代GPU架构已经可以支持更加灵活的渲染管线。
同时在不同的主机和设备上也往往有着不同于常见GPU的架构设计。
Renderable
在进行渲染时我们只需要考虑那些需要进行渲染的GO,它们称为可渲染对象(renderable)。
一般来说我们可以把整个可渲染对象拆分成若干个block,每个block有着自身的网格、材质等渲染信息。
对于网格数据,我们需要存储网格上所有的顶点坐标以及每个面包含节点的编号。同时我们往往还需要为每个顶点单独存储一个法向来处理曲面发生突变的情况。
对于材质数据,我们需要定义常见材质的渲染模型。在现代游戏引擎中往往还会集成大量的PBR材质以渲染出更加逼真的图像。
除此之外,我们还需要考虑材质的纹理。纹理对于材质的定义以及最终渲染呈现的效果起着至关重要的作用。
当然我们还需要考虑shader,在进行渲染时需要把编译好的shader连同数据一起提交的GPU上进行计算。
Render Objects in Engine
接下来我们就可以对GO进行渲染了。根据光栅化的渲染管线,我们首先利用MVP变换把模型转换到屏幕空间上,然后把渲染数据提交给GPU就可以实现渲染的过程。
然而这样的渲染过程往往不会得到令人满意的渲染结果。实际工程中我们往往需要把一个完整的网格拆分成不同的submesh,每个submesh有着自己的材质和纹理而整个网格共享一套顶点和面片信息。这样利用submesh的概念就可以绘制出更加逼真的图像。
当我们需要绘制大量GO时,如果每个GO都使用单独的网格信息则会造成存储和计算资源上的浪费,实际上很多GO和submesh都共享了相同的材质、纹理甚至是shader。因此为了更高效地利用计算资源人们还提出了资源池(resource pool)的概念。在资源池中我们把所有的网格、材质、shader等资源分别集中到一起,在进行实际渲染时对每个对象分别去寻找对应的数据和资源即可。
为了更高效地利用GPU,我们还可以把场景中的submesh按照材质进行排序。这样可以保证渲染时具有相同材质的submesh会放在一起进行绘制,从而降低GPU切换资源的开销。
在很多游戏场景中还存在着大量相似甚至是完全相同的GO。对于这种情况可以通过GPU batch rendering的方法把这些GO组织在一起,然后把同一batch中的对象一次性绘制出来,进一步提升场景渲染的效率。
Visibility Culling
在游戏场景中一种常见的情况是整个场景内有大量的可渲染对象,但在玩家视野内则只有有限数量的单位。在这种情况下如果直接把场景中所有的可渲染对象送入渲染管线无疑会造成计算资源的浪费。
因此可见性剔除(visibility culling)是渲染系统中非常实用的技术,它的思想是在送入渲染管线前首先判断场景中的每个可渲染对象是否在相机视野中,然后只对视野范围内的对象进行渲染。
visibility culling的核心是把可渲染对象使用bounding box进行表示,然后通过bounding box来迅速判断物体是否在视锥范围内。我们通过bounding box将场景中的物体组织起来,这样在渲染时只需要通过对它们进行遍历就可以快速地实现visibility culling。
在现代游戏引擎中,BVH是应用最为广泛的bounding box。BVH的一大特点是它可以在场景中物体发生运动时通过对节点的操作来动态地修改树的结构,这样无需每次都重新建树从而大大提高了计算效率。
在游戏设计中,PVS(potential visibility set)是一种非常实用的技术。它的思想是把整个场景划分为若干个相对独立的区域,不同区域之间通过portal进行连接。当玩家在场景中进行游戏时只会在某个区域中,而这个区域内的可见性是可以事先确定的,这样就可以利用PVS来进一步剔除无需渲染的对象。
当然随着设备计算能力的进步,PVS的应用在现代游戏中已经没有那么多了。但是PVS的思想仍然是值得我们去学习的,实际上除了渲染之外PVS的思想在场景管理和资源调度中都有着丰富的应用。
利用现代GPU的强大计算性能我们可以通过查询的方式直接获取每个对象的可见性并以此剔除掉不可见的物体,这样的技术称为GPU based culling。
Texture Compression
我们在前面的章节介绍过纹理对于渲染出逼真的物体起着重要的作用。通常情况下纹理会通过一张二维贴图进行表示,并且在计算机中使用JPG或是PNG这样的压缩格式进行存储。而在游戏引擎中则无法使用这些常用的图像压缩格式,这主要是因为JPG这样的压缩算法不支持快速的随机图像坐标访问,而且它们往往具有过大的计算复杂度无法进行实时的压缩与解压。
在渲染系统中最常用的纹理压缩算法是block compression,它的思想是统计每个4×4区域内纹理图像最大和最小值然后通过插值的方法进行查询。
Authoring Tools of Modeling
游戏中的模型是怎么获得的呢?最经典的建模方法是使用3ds Max、Maya、blender等建模软件来绘制3D模型。
近几年基于雕刻的建模软件也获得了非常多的应用。
随着人工智能和三维重建技术的发展,我们甚至可以从实物通过扫描的方法来重建出非常精细的网格。
除此之外,还有一些自动化建模工具来自动生成地形等场景的网格。
Cluster-Based Mesh Pipeline
本节课最后讨论了cluster-based mesh shader这一前沿技术的基本思想。随着现代GPU计算能力的提高以及人们对于画质需求的不断增长,在3A大作中的模型往往都具有百万级甚至千万级的网格。
为了渲染出具有如此高精度的网格就需要使用mesh shader相关的技术。mesh shader的核心思想是把网格上的一小块区域视为一个meshlet,每个meshlet都具有固定数量的三角形。
在进行渲染时可以通过实时生成的方法即时生成meshlet中的网格。
mesh shader可以生成几乎无限的细节,而且可以根据相机和物体的相对位置关系动态地调整网格的精度。
虚幻5中的Nanite技术可以认为是更加成熟的mesh shader。