剔除算法汇总:
(1)距离剔除:物体与相机的最大或最小距离,可能最大距离还要加上fadeDistance,来判断物体是否被剔除。距离剔除原理简单,效率高,cpu端运行。ue4的实现在FScene的ComputeViewVisibility函数调用的FrustumCUll函数。ue4可以用culling volume,根据物体的体积设置剔除距离,这个剔除volume可以嵌套。
UWorld::UpdateCullDistanceVolumes里面,更新volumme或者actor或者primitive时被调用,主要是更新primitive对应的距离信息。primitive信息渲染线程用的FPrimitiveSceneInfo,里面会设置PrimitiveBOund的最大渲染距离,最后在上面的cull函数里,用的就是这个bound的最大渲染距离。
(2)视锥体剔除:应用程序阶段,判断是否在视锥体内,将视锥体外的物体剔除。用物体的包围盒和fov、近裁剪面、远裁剪面的距离等计算。ue4默认开启的,在FrustumCUll函数中实现,分为包围球和包围盒两种方式。
可以在场景中添加一个cinecameraActor,在perspective处将画面切到这个cinecameraActor,即看到相机的视角。命令行输入:freezerendering回车。然后点击左上角的弹出按钮(向上的三角形),移动摄像机,看到视锥体外的东西没有了。再输入freezerendering解除冻结,看到东西又回来了。
stat initviews
toggledebugcamera这个要在运行时,输入 有一些快捷键 比如f能冻结场景 ,和句号 能变化fov。
(3) occluder剔除(软件剔除):应用程序阶段,首先用cpu绘制一个低分辨率的z-buffer,在z-buffer上绘制较大的遮挡体,然后绘制小物体的包围盒,执行遮挡查询操作。纯cpu,集成简单,需要美术设置遮挡体(ue4的遮挡体还可以设置lod),消耗基于正在构建场景的lod网格和遮挡物的数量。virtual void AddElements(const FOccluderVertexArraySP& Vertices, const FOccluderIndexArraySP& Indices, const FMatrix& LocalToWorld)函数搜集遮挡体图元的几何信息。
SceneSoftwareOcclusion.cpp RasterizeOccluderTri光栅化遮挡物三角形
class FSWOccluderElementsCollector : public FOccluderElementsCollector { public: FSWOccluderElementsCollector(FOcclusionSceneData& InData) : SceneData(InData) { SceneData.NumOccluderTriangles = 0; } void SetPrimitiveID(FPrimitiveComponentId PrimitiveId) { CurrentPrimitiveId = PrimitiveId; } virtual void AddElements(const FOccluderVertexArraySP& Vertices, const FOccluderIndexArraySP& Indices, const FMatrix& LocalToWorld) override { SceneData.OccluderData.AddDefaulted(); FOcclusionMeshData& MeshData = SceneData.OccluderData.Last(); MeshData.PrimId = CurrentPrimitiveId; MeshData.LocalToWorld = LocalToWorld; MeshData.VerticesSP = Vertices; MeshData.IndicesSP = Indices; SceneData.NumOccluderTriangles+= Indices->Num()/3; } public: FOcclusionSceneData& SceneData; FPrimitiveComponentId CurrentPrimitiveId; };
collector搜集几何信息(点、索引、primId),导入SceneData中。SceneData也包含被遮挡物的几何信息。static void ProcessOcclusionFrame(const FOcclusionSceneData& InSceneData, FOcclusionFrameResults& OutResults)这个函数可能处理遮挡,输出结果。
static void ProcessOccluderGeom(const FOcclusionSceneData& SceneData, FOcclusionFrameData& OutData) //光栅化遮挡物的三角形,转换到clip空间,然后跟近裁剪面、左、右、上下进行检测。
FOcclusionSceneData结构存储Occludee的信息(boundBOx的min和max,primId)
(4)视口剔除:投影变换之后,屏幕映射之前。几何阶段后期,是渲染管线的必要一环。图元完全或部分在ndc中时,才将其送到光栅化阶段。完全在ndc内的直接送下一阶段,完全在ndc外的完全舍弃,部分在ndc外的需要裁剪,可能会产生新的顶点。
(5)背面剔除:光栅化阶段,先判断多边形朝向,并和观察方向比较,通过设置顺/逆时针方向为正面。
(6)动态遮挡查询:在深度测试时,得到待剔除物体,在应用程序阶段执行。遮挡查询允许你在绘制前,向gpu插入一条查询,绘制结束后,从gpu将查询结果读到系统内存。一般都用上一帧的结果。ue4的OcclusionCull函数里包含了用预计算可见性数据判断可见性和遮挡查询相关代码。硬件的动态遮挡查询在ue4里几种遮挡算法里最费的一种。由于是将上一帧的回读,就会出现突然冒出来的情况,可以将遮挡物的Bound Scale设为大点儿的值,比如1.25,就会有所改善。另外,不要做小的物体,做成整块的。或者,用r.HZBOcclusion 1开启hzb再通知gpu进行遮挡测试,测的物体更少,也能改善。Show->Advanced->Bounds可以看到物体的包围盒。有的手机也支持动态遮挡查询了。用哪些遮挡算法也要灵活,开启预计算可见性剔除后就可以关闭动态遮挡查询。
(7)Early-z culling,硬件自动完成。顶点着色器后就知道物体的深度了,在光栅化阶段就可以将被遮挡的片元剔除不进入片元着色器阶段。但有深度写入,alpha test的情况就无法进行。但据测试,即使当前dc由于深度写入或者alpha test无法剔除,下个dc还是可以的。ue4引入了pre-z,解决深度写入就无法early-z的问题。
(8)hiz culling:几何shader得到待剔除物体后,在顶点shader执行。基于gpu,用几何着色器先生成物体的包围盒,再根据包围盒选定对应层级就的深度图等等,具体看参考文档。
https://github.com/nvpro-samples/gl_occlusion_culling
(9)预计算可见性体积:先烘焙,后用。ue4一般是可玩区域放置可见性体积,进行烘焙。
const uint8* FSceneViewState::GetPrecomputedVisibilityData(FViewInfo& View, const FScene* Scene).
查找当前摄像机在哪个bucket,哪个cell,从而获取该cell的数据:uint8* PrecomputedVisibilityData(FViewInfo& View, const FScene* Scene) 的预计算可见性数据
ue4遮挡的几个问题:剔除后,只是不渲染了,蓝图和碰撞还是在的。instance物体按照簇进行剔除的。r.visualizeOccludedObject看被遮挡的物体。vr:轮询遮挡查询。
参考文档:
1. Understanding Culling Methods: https://www.youtube.com/watch?v=6WtE3CoFMXU&t=1584s
2.
Hi-Z GPU Occlusion Culling(遮挡剔除): https://zhuanlan.zhihu.com/p/47615677
剔除算法综述
https://zhuanlan.zhihu.com/p/74936111
4.
Hierarchicl Z-buffer可见性算法
https://zhuanlan.zhihu.com/p/80789138