虚拟世界的管理
Unigine引擎使用了大量优化技术和实用方法来管理虚拟世界。 它们可以在不损失较多图像质量的前提下降低渲染负荷。
细节层次(Levels of Details)#
具有平滑的alpha融合特性的细节层次(LOD)用来降低3D对象的几何复杂度;借助该技术,当3D对象远离摄像机时,渲染器的负荷就能减轻。
含较少细节信息的网格由模型师手工创建,之后它们都被导出到Unigine引擎,充当同一网格的不同个节点表面。 当渲染虚拟世界时,这些个节点表面逐一变得可见并彼此间平滑渐显:开始时以高密度多边形网格呈现,最后在远处看到的则是使用了低密度多边形网格。
如有所需,通过alpha抖动显示方法,不同的LOD可以被骤然切换或是彼此间平滑渐显。
节点表面的LOD可在Node settings(节点设置)窗口中的【Surfaces(节点表面)】标签页调节。 它提供有如下参数:
处于节点表面的能见距离之内
|
处于渐隐/渐显距离之内
|
假设我们有两组LOD:高密度多边形surface_lod_0和低密度多边形surface_lod_1,现在需要安排这两组LOD彼此间完成平滑过渡。
- 我们想在50个单位距离上做切换。 为此,需要让节点表面的能见距离相互“靠拢“:
- 第一个LOD的节点表面surface_lod_0在摄像机靠近对象时应该是一直显现的。 所以它的最小能见距离设置为-inf。 而到摄像机的距离为50个单位就是它的最大能见距离。
- 紧随其后显现的是第二个LOD的节点表面surface_lod_1。 它的可见范围从50个单位距离(最小能见距离)开始,一直到无穷远(最大能见距离 = inf)。
- 现在的LOD切换是急剧地而不是平滑地。 想要实现平滑融合,就要设置对称的渐隐(第一个LOD)和渐显(第二个LOD)距离。 比如说,衰减区域要是5个单位,那么:
- 对于要渐渐消失的第一个LOD来说,它的最大渐隐/渐显距离设置为5。
- 对于要渐渐显现的第二个LOD来说,它的最小渐隐/渐显距离也设置为5。
作为结果,LOD将按如下方式改变:
从对象的边界框到50个单位距离 | 只有第一个LOD的节点表面surface_lod_0是完全可见的 |
50 — 55个单位距离 | 第一个LOD渐渐消失,第二个LOD渐渐显现 |
从55个单位到更远距离 | 只有第二个LOD的节点表面surface_lod_1是完全可见的 |
参照对象#
与LOD相关的参数不止一种:其中参照对象就是用来测量切换LOD时所使用的距离。 它指明了应该测量的是到节点表面自身的距离,还是到所处层级分支上级的任意节点表面或节点的距离。 每个节点表面都拥有两类参照对象:
让我们以房屋模型为例。 当摄像机拉近的时候,我们将会看到高密度多边形的清晰表面,比如拱门,石屋拐角,园窗和屋顶瓦片这些部分的细节。 当摄像机拉远的时候,所有这些表面都应同时切换为一种一致的低密度多边形的LOD表面。
高密度多边形模型
|
用于远处的LOD的低密度多边形模型
|
不过问题出在所有的这些清晰表面都拥有不同的边界框(Bounding Boxes)。 假如它们的距离是通过自身来检测的(最小和最大父级数都为0),又因为清晰表面的边界框会更靠近摄像机,那么房屋不同部分的LOD在被不均匀开启(或关闭)时就很可能会出现状况。 这会造成不自然的深度冲突变化。 此时,远处的拐角还没切换为更加精细的LOD,近处的就已被绘制了两次:作为高密度多边形拐角的LOD被绘制一次,同时作为统一的低密度多边形房屋的LOD又被绘制了一次。
假如我们将整个房屋的边界框设置为参照对象(最小和最大父级数都为1),那么不管我们从哪一侧拉近摄像机,它的所有表面都会同时切换。
在做检测时不止一个选项可用于不同的参照对象。 例如,下限(最小距离)用于检测节点表面自身,上限(最大距离)用于检测父级。 这可能听起来有些复杂,那就让我们看下方的图片。 第一幅图显示的是一个圆环,按照细节层次我们将其划分为了不同的节点表面。
在上面的图片中,最右边一列的节点表面在摄像机非常靠近它们时将被显示。 最左边一列的节点表面在摄像机远远离开它们时将被显示。 将多个节点表面合并成一个可减少要绘制对象的数量,因此,这样做可减少DIP请求数量,提高渲染速度。
注意:此处的所有最小距离都用于节点表面自身的测量,不过几乎所有的最大距离都用于测量其它参照对象,也就是父级。 这里的多张图片会有助于您理解该原理。
五角星代表摄像机;我们现在不考虑透过它能确切地看到什么事物。 在以上两张图片中,必要的节点表面会根据摄像机的位置以及摄像机到相应参照对象的距离来绘制。 例如,左图中,圆环的左上部作为单一的节点表面,右上部被划分为了两个分开的节点表面,下半部也作为单一的节点表面。 右图中,整个上半部被划分为了更小的可能扇区。
在上面的图片中,对于不同参照对象的距离测量,我们要正确“关闭”较小的单一扇区,而不是显示较大的扇区。 最大距离由父级扇区计算,这是因为相邻子扇区的距离可能会有很大不同。 最小距离由当前扇区计算,原因是假如摄像机靠它太近的话我们就需要将其显示。
室内空间#
通常对用户而言,用迷宫一般的一系列房间及它们间的通道这种方式来创建和显示人造世界的一部分就足够了。 对于某些开放空间,如果它们被呈现出来并且不无限延伸的话(前提是它们是有限区域),那么也可以被看作是 ”房间“。 这些限制使这种虚拟世界被理想化成了一系列的闭区(Sectors )和入口(Portals)。
整个空间被划分成叫做闭区(“房间”)的多个凸面区域。 如果在两个相邻的闭区之间存在某些敞开的门或窗,我们能从其中一个闭区看到另一个闭区的一部分,那么这一开口区域就称为入口。 这些闭区和入口帮助渲染器决定在虚拟世界中哪些区域和对象是任意视点都可见的。 此外,如果通过某一入口能看到相邻的闭区,那么该入口就作为它通往的那个区域的视锥体,可以进行视锥体剔除(Viewing Frustum Culling)操作。
室外空间#
适用室内场景的技术在用来管理大量风景地貌的时候是没效率的。 渲染速度直接受制于场景中绘制的实体和多边形的数量,以及为对象所做的物理演算,对于室外场景而言,其运算量通常都是非常高的。 因此,虚拟世界管理的主要目标就是只渲染看的见的区域而剔除所有其它区域。 如果虚拟世界不能被缩小成一系列的封闭区域,那就要使用被称为【space partitioning(空间划分)】的方法了。
在Unigine引擎中,通过自适应轴对齐BSP树来实现空间划分。
为了能提供高效的场景管理,同时也为了实现更好的多树平衡,遂创建了单独的类型树以用于不同的编辑器节点类型:
- World(世界)树负责处理所有闭区(Sectors),入口(Portals),遮挡器(Occluders),触发器(Triggers )和对象簇(Clusters)。
- Objects(对象)树包含所有对象,但不包含带有【collider(碰撞机)】和【clutter(杂物)】标记的对象。
- Collider(碰撞机)对象构成了单独的类型树以方便碰撞检测和避免出现一组对象全部紧靠另一组对象这样的最坏检测情况 。 很显然,对象间只有在处于场景中的同一区域内并发生重叠时才可以相交。 该类型树可彻底减少成对测试的数量,加速计算。
- Clutter(杂物)对象由于自身被大量使用遂也被单独分了出来,它们可以打乱主对象树的平衡。
- Light(灯光)树负责处理所有光源。
- Decal(贴花)树负责处理贴花。
- Player(玩家)树负责处理所有类型的玩家。
- Physical node(物理节点)树负责处理所有物理作用力。
- Sound(音效)树负责处理所有声源。
Mesh Partitioning(网格划分)#
在达到编辑器节点级别之后,仍然需要做进一步的网格划分。 其划分还是基于同样原理:二分和轴对齐。 唯一的不同之处就是这些树是预先计算好的(它们在虚拟世界加载的时候生成),这一做的原因是网格属于烘焙对象,它无需为相关的树做动态更改。 网格被划分为如下类型树:
- Surfaces(节点表面)树
- Polygon(多边形)树
这两种基于网格的类型树为网格的快速相交和碰撞计算提供了基础。
透视投影(Perspective Projection)#
当人眼观看场景时,远处的对象要显得比近处的对象小 - 这被称为透视。 而正交投影会忽略这一影响实现精确测量,透视的定义表明了远处的对象作为缩小体提供了额外的现实信息。
视锥体(Viewing Frustum或View Frustum)是用于透视投影的虚拟摄像机的视角(Field of View);换句话说,它是我们在屏幕上看到的虚拟世界空间的一部分。 它的具体形状要取决于被模拟的摄像机类型,不过它通常就是一种平截头四棱锥体。 视锥体的平面与屏幕平行,它们分别称为近端剪裁平面(Near Plane)和远端剪裁平面(Far Plane)。
作为虚拟摄像机的视角它不是没范围的,有些对象是不进入视角的。 例如,比近端剪裁平面更靠近观看者的对象将不可见。 如果位于远端剪裁平面后面的对象没在无穷远处,或是有些对象被侧面切断了,那它们也同样是不可见的。 因为这类对象无论如何都是不可见的,所以我们就可以跳过它们的绘图。 丢弃看不见的对象的过程就叫做视锥体剔除。
正交投影(Orthographic Projection)#
当人眼观看场景时,远处的对象要显得比近处的对象小。 正交投影会忽略这一影响,它可用于创建满足建筑和工程所需的等比例绘图。
正交投影所用的视景体是一个矩形的平行六面体,或者更通俗的讲它是个盒子。 不同于透视投影,视景体的大小不会从一端到另一端发生改变,因此对象到摄像机的距离不会影响它所呈现出的大小。
遮挡剔除(Occlusion Culling)#
另一种流行的实用方法是移除被其它对象完全隐藏的那些对象,例如,我们无需绘制空白墙面后的房间,或是绘制完全不透明栅栏后的花卉。 这项技术称之为遮挡剔除(Occlusion Culling)。 入口和闭区,以及潜在的可见设置属于遮挡剔除的特殊情况。 前两个已在上面作过描述了。 最后一个按成群区域划分空间,每个区域都包含有一系列的多边形,这些多边形在该区域内的任何位置都可见。 之后,渲染器只需实时查询预先计算好的带有视图位置的设置即可。 该技术经常用来加速二叉空间划分(Binary Space Partitioning,简称BSP)。
异步数据流技术#
数据流是一种优化技术,它所采用的是不一次将所有数据都加载进随机存储器(RAM)。 而是只加载必要数据,所有其它数据则都是按需逐步加载。
在Unigine引擎中,默认是开启异步数据流的。 因使用了数据流技术,以下数据将被异步加载进RAM:
- 所有材质的纹理。
- ObjectMeshStatic, ObjectMeshClutter, ObjectMeshCluster.
像ObjectMeshClutter和ObjectGrass这种程序式生成的对象由独立线程生成,这样一来也就显著降低了性能开销。
请务必记住,异步数据流的使用不会影响网格和纹理向GPU的传输:它们的传输都在主线程中进行。
节点的多线程更新#
节点的多线程更新(前提是您通过控制台命令world_threaded启用了多线程)可以大幅提高性能。 例如,当需要在虚拟世界中渲染大量的粒子系统时这种技术手段就非常奏效。
- 在节点层级中拥有单一根节点的所有节点采用的都是单线程更新。 要想并行执行节点更新,就必须确保要更新的节点没有使用相同的父级。
- 每个节点引用都被按没有任何父级的根节点来处理(不考虑它们在节点层级中的位置)。 例如,对于包含有节点引用的粒子系统而言,引擎会一直使用多线程更新来优化其性能。