应用程序阶段概述
应用程序阶段是负责驱动GPU的管道阶段。
应用程序阶段有三个角色:
- 可见性判别
应该仅把可见或者潜在可见的物体提交GPU,以免浪费资源去渲染总是看不见的物体。
- 提交图元
使用DX的DrawIndexedPrimitive()
或者OpenGL的glDrawArray()
之类的调用把子网格材质对传送至GPU。
另一个提交方法是建立GPU命令表。几何图元要适当地排序以优化渲染性能。若场景需要用多个步骤渲染,几何图元便需要被提交多次。
- 控制着色器参数以及渲染状态
应用程序阶段需要配置uniform变量。
应用程序阶段必须设置所有不可编程但可以配置的管道阶段参数,以确保每个图元能正确渲染。
可见性判断
在把场景中的物体提交至GPU之前,剔除(cull)不会对最终影像有任何贡献的物体是极其重要的事。建立可见网格实例表的过程称为可见性判断(visibility determination)。
CRE:Unity可在OcclusionCull窗口烘焙场景的遮挡剔除。Unity的遮挡剔除机制好像结合了PVS和Portal?
平截头体剔除:
在平截头体剔除(frustum culling)中,完全位于平头截体之外的物体便会被排除在渲染表之外。
测试判别一个给定网格实例是否在平截头体之内,会使用到物体的包围体积(bounding volum)和6个平截头体平面。
包围体积通常是球体因为球体特别容易进行剔除计算。对于每个平面我们把它向内移动球体半径的距离,然后根据球的中心点是位于移动后平面里面还是外面来判别是否应该剔除。
场景图数据结构可以优化平截头体剔除,把包围盒不接近平截头体的物体完全忽略。
遮挡(Occlusion)以及潜在可见集(PVS):
就算物体通过了平截头体剔除,它仍然可能被其他物体遮挡。把可见表中完全被遮挡的物体移除,称为遮挡剔除(occlusion culling)。在拥挤的场景中(例如很多城市高楼),遮挡剔除就非常重要并且有用。而在不拥挤的环境或者俯瞰场景时遮挡就比较少,那么遮挡剔除的成本可能高于其收益。
大型环境的总体遮挡剔除可以通过预计算(烘焙)潜在可见集(potentially visible set, PVS)实现。PVS应该容许包含一些实际不可见物体,但是不应该排除掉对渲染有用的物体。
实现PVS的方法之一就是把场景切割成区域,每个区域提供摄影机在该区域内能看到的其他区域列表。可以用手工设置或者使用脱机的编辑器工具。(这些工具的运作原理一般是扫描颜色编码渲染的帧缓冲结果)(自动化PVS并非完美-应该允许手工调整)
入口(portal):
另一个判别场景中哪些部分可见的方法就是使用入口(portal)。游戏世界会划分为半封闭区域,这些区域以孔洞互相连接,这些孔洞称为入口,例如窗口或者门。
要渲染一个含入口的场景时,首先渲染包含摄像机的区域。然后对于每一个连接该区域的入口,我们建立对应的锥形的体积,相邻区域的物体就用该体积来剔除。
反入口(anti-portal):
把入口的概念反转,锥形的体积也可以用来描述某物体遮挡的区域。这种体积称为遮挡体积(occlusion volume)或者反入口(anti-portal)。
找出遮挡物的每个轮廓边(silhouette edge),然后再用摄像机焦点和物体轮廓边构建遮挡体积。当测试更远的物体是否被遮挡时,如果他们完全在这些遮挡体积之内,就可以剔除。
提交图元
当产生了可见的几何图元列表后,必须提交至GPU管道进行渲染。
渲染状态:
CRE:OpenGL是状态机机制。
渲染状态是全局的-它们在整个GPU中有效。
许多GPU管道是固定但可以配置的,可编程部分也有些部分可配置,管道内的所有可配置参数称为硬件状态(hardware state)或者渲染状态(render state)。应用程序阶段必须确保提交图元时,正确完整地设置硬件状态。
理想情况下,这些状态设置完全由子网格对于的材质描述的。所以应用程序阶段需要遍历可见网格实例列表。遍历每个子网格-材质对,根据材质规格设置硬件状态,然后再提交图元渲染。
- 可配置参数包括但不限于以下:
- 世界观察矩阵。
- 光源方向。
- 纹理绑定。(即某材质或着色器用到的纹理)
- 纹理过滤模式。
- 基于时间的动画效果。
- 是否开启深度测试。
- Alpha混合模式。
状态泄露:
如果在提交图元的过程中,忘记设置某方面的渲染状态,那么上一图元的设置就会“泄露”至下一图元。渲染状态泄露(render state leak)的结果,可能会使物体出现错误的纹理或者不正确的光照效果。
GPU命令表:
应用程序阶段实际上使用命令表(command list)和GPU沟通。这些命令包括设置渲染状态和提交图元。
实例:
设置材质1的渲染状态(含多个命令,每个命令设置一个状态)
提交图元A
提交图元B
设置材质2的渲染状态(含多个命令,每个命令设置一个状态)
提交材质C
提交材质D
提交材质E
几何排序
渲染状态设置是全局的–它们在整个GPU中有效。因此,改变渲染状态时,整个GPU管道必须完成当前工作,才能换上新的设置。若管理不善会令效能严重下降。
显然我们希望渲染状态的改变次数越少越好。最好的解决办法是按材质来排序几何体。(按此方法,我们先设置材质A的渲染状态,然后渲染所有采用材质A的几何物体,再轮到材质B)
但是几何排序会增加像素覆绘(overdraw),对渲染性能产生不利影响。(透明像素覆绘是需要的,而不透明像素的覆绘是浪费GPU时间的)
覆绘
覆绘(overdraw)是指一个像素在一帧图像中被多次绘制的情况。这通常发生在不同的图形对象重叠的地方,尤其是在一个对象被绘制在另一个对象之上。
CRE:最常见的解决办法是优化渲染顺序,从前往后渲染来减少覆绘,很多游戏引擎也是这样做的。
深度预渲染步骤解决覆绘问题
GPU的深度预渲染步骤(z-pass)能解决几何排序增加的覆绘。
深度预渲染的基本概念是渲染场景两次。第一次快速产生深度缓冲。第二次才用完整的颜色填进帧缓冲(无覆绘)。
阶段1:当仅渲染深度缓冲时GPU会使用双倍的渲染模式。不透明物体按从前到后的顺序渲染,使得深度写入次数最少。
阶段2:几何物体按材质重新排序,用最少的状态切换渲染颜色缓冲。
半透明物体的排序
半透明几何物体的材质排序问题没有通用解决方案,必须从后往前的顺序渲染才能得到正确的alpha混合结果。因此渲染半透明物体时我们必须接受频繁切换状态的成本。
场景图
CRE:场景图除了渲染之外还有其他用途,比如碰撞检测。
大部分的几何物体会出现在平截头体范围外,如果用平截头体剔除每个物体,通常难以置信地耗费时间资源。我们希望设计一些数据结构管理场景中所有几何物体,并能迅速丢弃大量完全不接近摄像机平截头体的世界部分,这样才能更仔细地平截头体剔除。此数据结构更可以帮助对场景中的几何物体排序。
这样的数据结构称为场景图(scene graph)。然而游戏场景图不必是图,其数据结构通常会选择某种树。这类数据结构的理念一般是把三维空间以某形式划分为区域,使得不如平截头体相交的区域能直接丢弃,无需逐一物体剔除。
这些数据结构有四叉树、八叉树、BSP树、kd树、空间散列技术等。
八叉树:
八叉树以递归方式把空间分割为象限(quadrant/octant)。每层递归以四叉树的节点表示,每个节点有8个/4个子节点,每个节点代表一个象限。这些象限通常是由轴对齐的平面切割而成的方形。(然而有些四叉树用任意形状区域来细分空间)
八叉树可用于存储和组织几乎任何在空间中分布的数据。在渲染引擎中八叉树通常用来存储可渲染图元例如网格示例、地形的子区域、大型静态网格的个别部分等。
要判断哪些区域在摄像机平截头体内可见,我们从根节点开始遍历,检查每个中间区域是否位于平截头体内。如果某个区域不与平截头体相交,那么其子区域也不会相交,所以可以停止遍历该分支,就可以大幅提升搜寻潜在可见图元的速度。
通常尽量令每个叶节点有均匀的图元数目。要实现此目的,可以基于区域内的图元数目来决定继续或终止细分区域。
八叉树的二维版本是四叉树。
包围球树:
如同八叉树把空间分为方形区域一样。包围球树(bounding sphere tree)把空间层次结构分割为球状区域。
我们首先把图元分成小组,计算每组的包围球。然后这些小组再结合成较大的组,直至得到一个包围整个场景的包围球。
求潜在可见图元时,也是从根节点往子节点遍历,测试每个包围球是否和平截头体相交,仅对相交分支继续递归遍历。
BSP树:
二元空间分割树(binary space partitioning, BSP)把空间递归分割为一半,直接每个版空间(half space)里的物体符合某些预定条件。
- BSP树的用途:
BSP树有多种用途,包括碰撞检测和构造实体几何(constructive solid geometry,CSG),以及它最知名的优化三维图形用途-平截头体剔除和几何排序。
- BSP树的生成:
BSP树再每层递归中用单个平面把空间二分。这些分割平面可以是轴对齐的,但更常用的方法是每次细分时都用场景中某三角形的平面分隔空间。
任何与平面相交的三角形,都会被切割为三个三角形。这样每个三角形都只会在分割平面之前或之后或者共面。
- BSP树用于剔除:
BSP树用于平截头体剔除,实现方法和八叉树四叉树、包围球树无大区别。
- 严格几何排序:
按个别三角形生成的BSP树,可以使三角形按从后至前的严格次序排序。(这种排序功能对于早期的没有深度缓冲只能用画家算法的游戏特别重要)
选择场景图
选择什么场景图视乎游戏要渲染的场景特质。如果是格斗游戏,游戏中两个角色在擂台战斗,擂台外主要是静态场景,那么这游戏可能根本不需要场景图。如果是室内环境,BSP树和入口系统可能是不错的选择。如果是平坦的室外地形(例如俯瞰的RTS),四叉树足以达到高效的渲染性能。
应该以引擎实际性能统计数据选择场景图。统计数据来自渲染引擎实际的性能测量。
(END)