@author: 白袍小道
查看随意,转载随缘
第一部分:
这里主要关心加速算法,和该阶段相关的UE模块的结构和组件的处理。
What-HOW-Why-HOW-What(嘿嘿,老规矩)
1、渲染模块这里有个主要任务需要完成:将需要在屏幕上(或者某设备)显示出来的Primitives(几何体,或者绘制图元)输入到pipeline的下一阶段。
2、渲染的每帧,计算Camera,Light,Primitives输出到几何阶段(非几何着色)
插一句:Geometry State包含了视点变换,顶点着色,投影、裁剪、映射
3、几个空间数据结构和算法:
层次包围,入口裁剪、QuadTree, 空间分隔树, Kd树,八叉树,场景图、细节裁剪
空间数据结构:
是将几何体组织在N维空间中的一系列层次(上抱下,下抱下下,类推)数据结构
层次包围体BVH
入口裁剪PortalCulling
细节裁剪( 屏幕尺寸裁剪 )
具有包围体的问题,将这个包围体投射到投影平面,然后以像素为单位来估算投影面积,如果像素的数量小于用户定义的阈值,那么不对这个物体进行进一步处理。
遮挡删除
遮挡剔除必要性:
不难理解,可见性问题可以通过Z缓冲器的硬件构造来实现,即使可以使用Z缓冲器正确解决可见性问题,但其中Z缓冲并不是在所有方面都不是一个很"聪明"的机制。例如,假设视点正沿着一条直线观察,其中,在这条直线上有10个球体,虽然这10个球体进行了扫描转换,同时与Z缓冲器进行了比较并写入了颜色缓冲器和Z缓冲器,但是这个从这个视点渲染出的图像只会显示一个球体,即使所有10个球体都将被光栅化并与Z缓冲区进行比较,然后可能写入到颜色缓冲区与Z缓冲区。
上图中间部分显示了在给定视点处场景的深度复杂度,深度复杂度指的是对每个像素重写的次数。对于有10个球体的情形,最中间的位置,深度复杂度为10,因为在这个地方渲染了10个球体(假设背面裁剪是关闭的),而且这意味着其中有9次像素写入是完全没有必要的。
两种主要形式的遮挡裁剪算法,分别是基于点的遮挡裁剪和基于单元的遮挡裁剪
伪代码--------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
下面是常用几种遮挡算法
1、硬件遮挡查询(UE中有,也可以自己先写写,然后测试对照)
硬件遮挡查询的基本思想是,当和Z缓冲器中内容进行比较时,用户可以通过查询硬件来找到一组多边形是否可见的,且这些多边形通常是复杂物体的包围体(如长方体或者k-DOP)。如果其中没有多边形可见,那么便可将这个物体裁剪掉。硬件实现对查询的多边形进行光栅化,并且将其深度和Z缓冲器进行比较
2、HZB(同上)
层次Z-缓冲算法用八叉树来维护场景模型,并将画面的Z缓冲器作为图像金字塔(也称为Z-金字塔(Z-pyramid)),该算法因此在图像空间中进行操作。其中,八叉树能够对场景的遮挡区域进行层次剔除,而Z-金字塔则可以对单个基元和边界体积进行层次Z缓冲。 因此Z-金字塔可以作为此算法的遮挡表示。
通过从前到后遍历八叉树并裁剪遇到的八叉树节点,此算法可以仅访问可见的八叉树节点及其子节点(右上角的节点),
的容器只对可见包围体中的多边形进行渲染。
3、遮挡地平线算法
通过从前到后渲染一个场景,我们可以定位到地平线在哪里进行渲染,而任何在当前地平线之后和之下的物体都可以被裁剪掉。
4、遮挡物收缩与视锥扩张算法(也有类似处理)
可以使用基于点的遮挡算法来生成基于单元的可见性,根据给定的量来缩小场景中所有遮挡物来达到延伸有效可见点的目的,
通常与Occluder Shrinking算法一起配合使用
5、LOD这个放到后面细说。【篇幅不少】
6、裁剪图策略:后面加入
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
第二部分:
下面按照延时渲染来过一下。
涉及主要类
DeferredShadingSceneRenderer
SceneRender
SceneOcclusion
FSceneViewState
TStaticMeshDrawList
FRenderTask
FDrawVisibleAnyThreadTask
DrawingPolicy
一、FDeferredShadingSceneRenderer::InitViews
这里主要通过检测可见性,透明排序等,完成视图初始化。这里我们关注检测可见性
1、 灯光信息的可见分配。
2、预处理可见性
位于文件:SceneVisibility.cpp
FSceneRenderer::ComputeViewVisibility(FRHICommandListImmediate& RHICmdList)
2.1 |
得到当前预处理可见性的数据 |
FSceneViewState::GetPrecomputedVisibilityData
完成任务:
*返回给定视图位置的可见性数据数组(数据),如果不存在,则返回NULL。
*数据位通过场景中每个原语的VisibilityId进行索引。
*此方法在必要时解压缩数据,并基于视图状态中的bucket和chunk(优化时候也需要注意)索引缓存数据
(这里若需要查看可以通过几个调试选项【详细在后面备注】,然后烘培)
如何完成:
a、计算盒子的ViewOrigin是否在格子中,这里利用反过来将盒子散列到bucket去减少了计算量。
这里盒子偏移,Bucket索引计算可以看看。
b、若有必要解压数据
2.1后(构建有时会覆盖截锥体,就是迭代覆盖视口的图元可见性Map【view.PrimitiveVisibilityMap】)
2.2 |
计算使用标准视锥体裁剪的图元的数目---FrustumCull |
接下来是引擎特性(处理view.PrimitiveVisibilityMap)
2.2a |
更新了视图的原始fade状态(略)。 |
2.2b |
(扫描VisibilityMap后面就说VMP)如果几何体标记为Hide,view.PrimitiveVisibilityMap标记 |
2.2c |
视图属性标记了只显示几何体的,那其他同样标记 |
2.2d |
反射捕获(Reflection Captures)的处理:只对接受非移动的. |
2.2e |
剔除线框中的小框对象【就一个投射矩阵判断】【主要是提高编辑器,因为线框模式的禁止了遮挡,我去】 |
2.2f |
(不在线框中的)进行剔除 |
2.3 |
OcclusionCull |
这里才是算法实现的开始,包括使用前面说的预计算数据,根据特征级别(opengl,dx)做不同的OC处理
a、软处理(自己整CPU处理)FSceneSoftWareOCclusion。
b、FetchVisibilityForPrimitives处理(还有FHZBOcclusionTester)
c、标记到非OC(没得整)组
2.3.1 |
FSceneSoftWareOCclusion |
几个重要事情:
SubmitScene
CollectOccludeeGeom
Sort potential occluders by weight
Add sorted occluders to scene up to GSOMaxOccluderNum
reserve space for occludees vis flags
ProcessOcclusionFrame:
上面基本是数据的规整,这里才是执行算法过程
2.3.1.1 |
ProcessOccluderGeom,对着算法来一遍(嘿嘿) |
每个模型
a\ 转换模型到裁剪空间【矩阵操作】
每个三角形
b\ 修正三角形:ClippedVertexToScreen,TestFrontface,AddTriangle
【满足裁顶点加或修正三角形:按深度(获取离屏最远的)】
(详细稍后补齐)
2.3.1.2 |
按深度整理【最接近屏幕的在前】 栅格化遮挡删除 |
2.3.2 |
FetchVisibilityForPrimitives |
这里先略(那啥,放到另外一个地方)
1、若支持并行处理:构建数据FVisForPrimParams,利用上多任务FetchVisibilityForPrimitivesTask处理FHZBBound
2、FOcclusionQueryBatcher::BatchPrimitive (一个算法)
3、排序BasePass,实现HiZ剔除
如果我们不使用深度仅通过排序静态绘制列表桶大致从前到后,以最大化HiZ剔除。不会干扰状态排序,且每个列表都是单独排序的
(客官,待续)
4、提交视野的自定义数据
5、RHI资源(构建)
检验: