3D图形学(10):游戏中的加速渲染算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq826364410/article/details/88937586

内容引自《Real Time Rendering 3rd》

一、空间数据结构(Spatial Data Structures)

空间数据结构(Spatial Data Structures)是将几何体组织在N维空间中的一系列数据结构。这些空间数据结构可以用于很多实时渲染相关操作的加速查询中,如场景管理,裁减算法、相交测试、光线追踪、以及碰撞检测等。

空间数据结构的组织通常是层次结构的。宽泛地说,即最顶层包含它之下的层次,后者又包含更下层的层次,以此类推。因此,这种结构具有嵌套和递归的特点。

用层次结构的实现方式对访问速度的提升很有帮助,复杂度可以从O(n)提升到O(log n)。但同时,使用了层次结构的大多数空间数据结构的构造开销都比较大,虽然也可以在实时过程中进行渐进更新,但是通常需要作为一个预处理的过程来完成。

一些常见的空间数据结构包括:

  • 层次包围盒(Bounding Volume Hierachy,BVH)
  • 二元空间分割树(Binary Space Partitioning,BSP),
  • 四叉树
  • kd树
  • 八叉树(Octree)
  • 场景图 (Scene Graphs)

其中,BSP树和八叉树都是基于空间细分(Space Subdivision)的数据结构,这说明它们是对整个场景空间进行细分并编码到数据结构中的。例如,所有叶子节点的空间集合等同于整个场景空间,而且叶子节点不相互重叠。

BSP树的大多数变种形式都是不规则的,而松散地意味着空间可以被任意细分。

八叉树是规则的,意味着空间是以一种均匀的形式进行分割,虽然这种均匀性限制比较大,但这种均匀性常常是效率的源泉。另外值得注意的是,八叉树是四叉树的三维空间推广。

另一方面,层次包围盒不是空间细分结构,它仅将几何物体周围的空间包围起来,所以包围层次不需要包围所有的空间。

 

1、层次包围盒BVH | Bounding Volume Hierarchies

层次包围盒(Bounding Volume Hierarchies, BVH)方法的核心思想是用体积略大而几何特征简单的包围盒来近似地描述复杂的几何对象,从而只需对包围盒重叠的对象进行进一步的相交测试。此外,通过构造树状层次结构,可以越来越逼近对象的几何模型,直到几乎完全获得对象的几何特征。

对于三维场景的实时渲染来说,层次包围体(Bounding Volume Hierarchy,BVH)是最常使用的一种空间数据结构。例如,层次包围体经常用于层次视锥裁减。场景以层次树结构进行组织,包含一个根节点(root)、一些内部节点(internal nodes),以及一些叶子节点(leaves)。顶部的节点是根,其无父节点。叶子节点(leaf node)包含需渲染的实际几何体,且其没有子节点。

扫描二维码关注公众号,回复: 5780523 查看本文章

BVH的延伸阅读材料

[1] https://hal.inria.fr/inria-00537446/file/bounding_volume_hierarchies.pdf

[2] https://www.codeproject.com/Articles/832957/Dynamic-Bounding-Volume-Hiearchy-in-Csharp

2、BSP树 | BSP Trees


BSP树(二叉空间分割树,全称Binary Space Partitioning Tree)是一种常用于判别对象可见性的空间数据结构。类似于画家算法,BSP树可以方便地将表面由后往前地在屏幕上渲染出来,特别适用于场景中对象固定不变,仅视点移动的情况。

BSP 树是一棵二叉树,每个节点表示一个有向超平面,其将当前空间划分为前向(front)和背向(back)两个子空间,分别对应当前节点的左子树和右子树。

BSP树的遍历

从根节点开始,判断输入位置与当前分割平面的“前”、“后”关系,

“前”则遍历左子树,“后”则遍历右子树,递归到叶子节点终止。

3、八叉树 | Octrees


八叉树(octree),或称八元树,是一种用于描述三维空间的树状数据结构。八叉树的每个节点表示一个正方体的体积元素,每个节点有八个子节点,这八个子节点所表示的体积元素加在一起就等于父节点的体积。一般中心点作为节点的分叉中心。

简单来说,八叉树的空间划分方式很简单,即递归地进行规整地1分为8的操作。如下图,把一个立方体分割为八个同样大小的小立方体,然后递归地分割出更的小立方体。这个就是八叉树的命名来源。这种分割方式可以得到比较规则的结构,从而使得查询变得高效。

 

松散八叉树

松散四叉树边长的公式修改为:

L(depth)=k∗W/(2depth)

节点的间距依旧与传统八叉树保持一致。这意味着同层节点的包围盒会相互重叠,如图所示:

而k值的选择就是一个比较重要的问题,k值过小则无法体现松散八叉树减少粘性区域的优势,k值过大则会导致包围体过于松散。一般k值等于2。这样可以有个重要性质:如果一个对象的大小小于等于一个节点的大小(内边界),那这个对象的中心点在这个节点内边界范围内,就能保证这个对象是在这个节点的松散包围体内。

八叉树延伸阅读材料

[1] http://web.cs.wpi.edu/~matt/courses/cs563/talks/color_quant/CQoctree.html

[2] https://en.wikipedia.org/wiki/Octree

4、场景图 | Scene Graphs


BVH、BSP树和八叉树都是使用某种形式的树来作为基本的数据结构,它们的具体区别在于各自是如何进行空间分割和几何体的存储,且他们均是以层次的形式来保存几何物体。然而三维场景的绘制不仅仅是几何体。

然而,渲染三维场景不仅仅只是渲染出几何图形,对动画,可见性,以及其他元素的控制,往往需要通过场景图(Scene Graphs)来完成。

场景图被誉为“当今最优秀且最为可重用的数据结构之一。”Wiki中的对场景图的定义是“场景图(Scene Graph)是组织和管理三维虚拟场景的一种数据结构,是一个有向无环图(DirectedAcyclic Graph, DAG)。”

场景图是一个面向用户的树结构,可以通过纹理、变换、细节层次、渲染状态(例如材质属性)、光源以及其他任何合适的内容进行扩充。它由一棵以深度优先遍历来渲染整个场景的树来表示。

另外提一句,开源的场景图有Open Scene Graph和OpenSG等,有兴趣的朋友们可以进行进一步了解。

场景图的延伸阅读材料


[1] http://www.openscenegraph.org/index.php/documentation/knowledge-base/36-what-is-a-scene-graph

[2] https://en.wikipedia.org/wiki/Scene_graph

[3] http://archive.gamedev.net/archive/reference/programming/features/scenegraph/index.html

二、裁剪技术 | Culling Techniques


裁剪(Culling)的字面意思是“从大量事物中进行删除”。在计算机图形学中,相对应的就是裁剪技术(Culling Techniques)所要做的工作——“从大量游戏事物中进行删除”。所谓的“大量事物”就是需要绘制的整个场景,删除的是对最终图像没有贡献的场景部分,然后将剩余场景发送到渲染管线。因此,在渲染方面通常使用“可见性裁剪(Visibility Culling)”这个术语。但其实,裁剪也可以用于程序的其他部分,如碰撞检测(对不可见物体进行不十分精确的计算)、物理学计算,以及人工智能(AI)领域。

与渲染相关的裁剪技术,常见的有背面裁剪(Backface Culling),视锥裁剪(View  Frustum Culling),以及遮挡裁剪(Occlusion Culling,也常常称作遮挡剔除)。

  • 背面裁剪即是将背向视点的物体删除,是一种非常简单直观的操作,只能一次一对个单一多边形进行操作。
  • 视锥裁剪是将视锥之外的多边形删除,相对而言,这种操作比背面裁剪稍微复杂。
  • 遮挡裁剪,是将被其他物体遮挡的物体进行删除,这种操作在三者中最为复杂,因为其需要聚集一个或者多个物体,同时还需使用其他物体的位置信息。

下图是三种裁剪技术的对比

1. 背面裁剪 | Backface Culling
 

假设你正在观察一个场景中不透明的球体。大约有一半的球体是不可见的。那么,可以从中这个例子里得到一个众所周知的结论,那就是,对不可见的内容不需要进行渲染,因为它们对最终的渲染图像没有贡献。

不需要对球体的背面进行处理,这就是背面裁剪的基本思想。对于一组物体来说,还可以一次性地进行背面裁剪,这也称为聚集背面裁剪(Clustered Backface Culling)。

2. 层次视锥裁剪| Hierarchical View Frustum Culling

如上文所示,只需对完全或者部分在视锥中的图元进行渲染。一种加快渲染速度的方法便是将每个物体的包围体与视锥进行比较,如果包围体位于视锥之外,那么便不需要渲染包围体中的几何体。由于这些计算在CPU上进行,因此包围体中的几何体不需要通过管线中的几何和光栅阶段。相反,如果包围体在视锥内或者与视锥相交,那么包围体中的内容就是可见的,所以必须发送到渲染管线中去。

利用空间数据结构,可以分层地来应用这种裁剪。例如,对于层次包围体BVH来说,从根节点进行先序遍历(Preorder Transversal),就可以完成这一任务。

视锥裁剪操作位于应用程序阶段(CPU),这意味着几何阶段和光栅阶段都可以从中受益,对于大场景或者一定的相机视线来说,场景只有一小部分是可见的,只需要将这部分发送到渲染管线。可期望获得一定的加速效果,视锥裁剪技术利用了场景中的空间相关性,因为可以将彼此靠近的物体包围在一个包围体中,而且几乎所有包围体都是以层次形式聚集在一起。

除了层次包围体,其他的空间数据结构同样也可以用于视锥裁剪,包括上文提到的八叉树和BSP树。但是当渲染动态场景时,这些方法便会显得不够灵活,不如层次包围体。

3. 入口裁剪 | Portal Culling

对建筑物模型来说,很多裁剪方面的算法可以归结为入口裁剪(Protal Culling)。在这个方向,最早的算法由Airey提出,随后Teller和Sequin,以及Teller和Hanrahan构造出了更高效,更复杂的算法。

入口裁剪算法的基本思想是,在室内场景中,建筑物墙面通常充当大的遮挡物,通过每个入口(如门或者窗户)进行视锥裁剪。当遍历入口的时候,就减小视锥。

使得与入口尽可能紧密贴合。因此,可以将入口裁减算法看作是视锥裁剪算法的一种扩展,且需将位于视锥之外的入口丢弃。

入口裁剪方法以某种方式对场景进行预处理,可以是自动形式,也可以是手动形式,可以将场景分割为一系列单元(Cells),其通常对应于建筑物中的房间或者走廊;链接进阶房间的门和窗口称为入口(Protals)。单元中的每个物体和单元的墙面可以存储在一个与单元关联的数据结构中,还可以将邻接单元和链接这些单元的入口信息保存在一个临接图中。

单元分别从A到H,入口是连接单元的通路,只对穿过入口能看到的几何体进行渲染。

4. 遮挡剔除 | Occlusion Culling

遮挡裁剪(Occlusion Culling),也常被称作遮挡剔除。

聊一聊遮挡剔除必要性。不难理解,可见性问题可以通过Z缓冲器的硬件构造来实现,即使可以使用Z缓冲器正确解决可见性问题,但其中Z缓冲并不是在所有方面都不是一个很“聪明”的机制。例如,假设视点正沿着一条直线观察,其中,在这条直线上有10个球体,虽然这10个球体进行了扫描转换,同时与Z缓冲器进行了比较并写入了颜色缓冲器和Z缓冲器,但是这个从这个视点渲染出的图像只会显示一个球体,即使所有10个球体都将被光栅化并与Z缓冲区进行比较,然后可能写入到颜色缓冲区与Z缓冲区。

这种遮挡在现实生活中却很常见,如热带雨林,发动机,城市,以及摩天大楼的内部。

这种用来避免低效率的算法可以带来速度上的补偿,具体可以将这些方法归类为遮挡裁剪算法(Occlusion Culling Algorithms),因为它们都试图裁剪掉被遮挡的部分,也就是被场景中其他物体遮挡的物体,最优的遮挡裁剪算法只选择其中可见得的部分。

有两种主要形式的遮挡裁剪算法,分别是基于点的遮挡裁剪和基于单元的遮挡裁剪

三、层次细节 | LOD,Level of Detail

细节层次(Level of Detail,LOD)的基本思想是当物体对渲染出图像贡献越少,使用越简单的形式来表达该物体。这是一个已经在各种游戏中广泛使用的基本优化技术。

例如,考虑一个包含1万个三角形的汽车,其中所包含的细节信息比较丰富。当视点靠近物体时,可以使用详细的细节表示,而当视点远离物体时,比如仅需覆盖200个像素,则完全无需渲染出1百万个三角形,相反,我可以使用诸如只有1000个三角形的简化模型。而由于距离的原因,简化后的模型与细节较丰富的模型看上去其实很接近。以这种方式,可以显著地提高渲染的性能开销。

 

通常情况下,雾效会与LOD一起使用。这样我们可以完全跳过对一些物体的渲染,直接用不透明的雾来进行遮挡。另外,雾效的机制可以实现下文所介绍到的Time-Critical LOD Rendering。通过将元平面移近观察者,可以更早地剔除对象,并且可以实现更快速的渲染以保持帧速率。

一般情况下,完整的LOD算法包含3个主要部分:

  • 生成Generation
  • 选择Selection
  • 切换Switching

其中,LOD的生成就是生成不同细节的模型表示。RTR3书中12.5节中讨论的简化方法可用于生成所需数量的LOD。另一种方法是手工制作具有不同数量的三角形模型。选择机制就是基于某种准则选取一个层次细节模型,比如屏幕上的评估面积。最后,我们还需要从一个细节层次转换到另一个细节层次,而这个过程便称为LOD切换。

基于距离的LOD选取 |Range-Based

选取LOD的一种常用方法是将物体的不同LOD于不同距离联系起来。细节最丰富的LOD的距离从0到一个用户定义值r1之间,下次层次的LOD的距离位于r1~r2之间,以此类推,如下图:

猜你喜欢

转载自blog.csdn.net/qq826364410/article/details/88937586