基于OSG的巨量点云渲染

2018-08-12

  点云渲染是非常简单的事情。因为我们只需要处理顶点,光照都不需要考虑。但是,当点云的数量很大,达到千万、亿级别时,事情就变得困难了。FPS低于10,就根本没有办法做什么交互了,甚至显存根本装不下需要渲染的对象,或者顶点数量超过了显卡的能力上限。所以,我们需要优化这个过程。点云显示最大的性能瓶颈就是光栅化,顶点的数量是最大的问题所在。针对此问题,我们一般由两种优化措施来解决问题:LOD,延迟渲染。
  LOD能够直接减少顶点,我们可以提前制作一份化简的模型(点云),即使顶点数量下降一个甚至两个数量级,对于我们观察它的外形来讲,都不会造成太大的失真,我们可以接受。这直接让”不能渲染“变成了”可能渲染“,这一步尤其重要。我们也可以通过代码随时化简原始稠密点云。OSG直接支持LOD,有一个LOD类,它是一个特殊的Group节点,对于一个对象,假设我们要使用4级LOD,那么就给它添加四个子节点,分别设置生效的距离。
 

osg::ref_ptr<osg::Node> lod1 = loadOBJ("bunny.ply");
osg::ref_ptr<osg::Node> lod2 = loadOBJ("bunny2.ply");
osg::ref_ptr<osg::Node> lod3 = loadOBJ("bunny3.ply");
osg::ref_ptr<osg::Node> lod4 = loadOBJ("bunny4.ply");
osg::ref_ptr<osg::LOD> lod = new osg::LOD();
lod->addChild(lod1, 0,0.1);
lod->addChild(lod2, 0.1, 0.2);
lod->addChild(lod3, 0.2, 0.3);
lod->addChild(lod4, 0.3, FLT_MAX);

  对于纯浏览型的程序,不需要什么交互,却可能需要很大的精细程度跨度。所以,可以设置很多级LOD。LOD本质上是以时间换空间。但是,对于CAD程序,设置多级LOD是不行的,因为CAD会对呈现的数据做很多交互并作算法处理,我们需要呈现并检验算法的结果。但是,问题在于结果数据过于巨大,网格或者点云数据高达几十G,这都不是显卡能够承受的级别了。设置多级LOD也是不行的,因为我们不能信赖优化后的数据能够体现原始数据的性状。这里是问题的核心。
  如果仅仅这样做,还是有问题的:所有的对象都直接存储到GPU了,必然会造成很大的显存浪费。我们需要控制这点。但是,除了当前显示的层级对象,其他的层级都不加载到GPU,也不一定可行,所以,这里需要稍微复杂一些的控制逻辑。
  延迟渲染(Defer Rendering)主要解决的是光源过多,最后形成的fragment shading多次计算,却仍可能被遮蔽这样的浪费问题。如参考1中指出亿级别的点云渲染时,z-test达几百几千次。如果能把不会出现的fragment不进行光照计算,就能极大的减少总体的计算量。延迟渲染实现的过程也是相当简单的,点云对象pointClouds并不需要添加到最终场景中,而是需要添加到一个虚拟的camera的场景中,把渲染的结果的buffer作为贴图输出,这个过程作为一个pass。这个贴图和3D场景的尺寸是相同的。再下一个pass中,把贴图做处理,直接贴到窗口上。这里只需要两个pass,总共需要4个shader文件。这里的shader是非常简单的。一般的延迟渲染需要四个以上的buffer 以形成最终的渲染结果,这里只需要一个。pass1只进行点云的光栅化,把veretex的world pos当作 color保存在color buffer中,映射到绑定的贴图对象。在pass2中,把此贴图对象给入fragment shader,这个贴图和最终的窗口是同样大小的。按照像素位置对贴图采样,就能获取到成像落到像素p位置的顶点的world pos,如果是游戏,我们可以结合灯光等其他信息计算该顶点的各种光照属性,在这里就不需要了,对于有效的顶点,只需要最终输出黑色即可,alpha值为1.0;如果从贴图中取出的点不包含有效的深度信息,那么就没有丁点落到该像素上,则在该点输出alpha为0.0 的白色即可。
  延迟渲染能进一步优化,叫做Detail Accumulation。既然点云的光栅化占GPU工作量的绝大部分,比如80%以上,当camera在场景静止时,就没有必要重新光栅化,直接使用上次光栅化的结果就行了。如此,我们就能够提升程序 3~4倍的性能了。当camera静止时,把其他增添细节的物体也加入到场景中(这些物体的计算量占比不会大)。当然,优化肯定是有代价的。当点云不同,其他的物体却可以在场景内移动的时候,我们怎么判断物体的深度顺序呢?是没有办法的,因为缓存的结果(贴图)已经丢失了深度信息。我们只能选择把点云放置在其他物体(如地面)的上层。
  参考资料1 主要详细讲解了本文所涉及到的技术。参考2则以OpenCL实现了专门为点云设计的软光栅化的算法,由于自己实现的算法中能够访问 光栅化的中间结果,便能够直接discard 很多vertex,大大的减轻的光栅化的压力。至于 能在2013年左右能渲染 138 million 顶点,也是非常惊人的。我还需要再研究研究。
  OSG 的官方 example 中有一个 osgdefer 的示例程序。里面对于延迟渲染的使用基本上讲解很详细了。在了解这个算法之前,我有几点小疑问的:1,多种计算都把结果投影到一张图像上,最后在几张图像上完成最终渲染,这不会造成很大的误差么?比如说像素变大、贴图采样模糊。2,本来只是渲染一张图像(frame buffer),现在变成了好几张,这样的计算不会很浪费时间么?3,最后只在屏幕空间完成shading、antialias,会不会效果很差?上述问题都不是什么大问题,也有补救的方法。用这些损失换取性能也非常划算。这里就不贴什么代码了。我一直认为,把工作原理讲清楚,才是首要任务。
  最终的测试结果表明,Intel HD 530能直接处理500w的点云,不移动camera时,FPS能非常高,完成各种交互都不是问题。当旋转或者缩放时,基本上也有30 FPS。之前的测试表明AMD Radeon R5 430 比之有三倍的性能,所以,应该可以处理1500w的顶点。

P.S. Intel的核心显卡真差。CPU内部bandwidth真低!

ref:

  1. Smooth Visualization of Large Point Clouds.pdf
  2. A GPGPU-based Pipeline for Accelerated Rendering of Point Clous.pdf

 

 

 

如果有任何意见,欢迎留言讨论。


[ 主页 ]
COMMENTS
POST A COMMENT

(optional)



(optional)