炼数成金 门户 CUDA 查看内容

辐射度算法的CUDA实现

2015-9-9 12:12| 发布者: 炼数成金_小数| 查看: 1054| 评论: 0|原作者: BugRunner|来自: BugRunner的博客

摘要: 1. 简介辐射度算法是一种经典的全局光照算法,它可以解决光线跟踪等直接照明方法中所不能表现的真实世界中的照明现象问题。虽然渲染的结果表现力强,但是问题之一就是算法的耗费较大,由于其原理就是对各个多边形面 ...

tm 算法 计算机 GPU CUDA

1. 简介
辐射度算法是一种经典的全局光照算法,它可以解决光线跟踪等直接照明方法中所不能表现的真实世界中的照明现象问题。虽然渲染的结果表现力强,但是问题之一就是算法的耗费较大,由于其原理就是对各个多边形面片进行着色,而且常常需要较多的迭代次数才能达到比较理想的效果,因此算法原理虽然比较简单,但效率就成为一个重要的瓶颈。
 
2. 辐射度算法原理
辐射度算法的原理就是模拟真实世界中的光照原理,在场景中的每个位置收集所有在该位置所能看到的颜色信息,然后对这些颜色信息进行汇总处理,作为最终该位置的颜色并对当前的位置进行着色。在现实世界中,这个位置就代表了无穷多个无限小的像素点,但在计算机中却不可能做到这样一点,因此只能进行近似的模拟操作,这就是将几何空间场景进行分割处理,得到一些相对于整个场景来说很小的多边形面片,然后以这些面片为单位来代替现实世界中的无限小的像素点进行渲染着色。当然,如果我们对整个场景分割得越发细小,得到的最终光照效果也就是越好,但随之而来的代价就是渲染的时间越长。
 
关于详细的辐射度算法原理,这里就不再详述了,可以参见:http://freespace.virgin.net/hugo.elias/radiosity/radiosity.htm
进行面片分割预处理操作之后的场景基本上如下图所示:
OCTree
我使用的是八叉树来对场景进行均匀分割,当然,为了统一化,在操作前首先要对场景进行单位化,不过也可以根据场景的大小与场景的表现范围进行有选择的统一化方法,只要保证能在分割过程中控制最终面片的大小与整个场景的比例关系即可。对于每个面片的渲染着色,直接使用OpenGL的渲染管线来实现,这个操作代价不大,毕竟只是对多边形面片列表作无特效的渲染,一般显卡上效率即可达到每秒八九百帧。
 
当完成上述预处理操作之后即可以对每个面片进行渲染着色,流程如下:
For each radiosity shading time
For each polygon in polygon list
    For each camera position
      Render the scene
      Get the color buffer:CB
    End for
    Compute the final Color for this polygon
  End for
Use theColor list to update the scene
End for
从流程图中可以看出,整个过程是串行执行的,在每次循环迭代内均是以单个多边形面片为单位进行着色。但正是这个串行的操作降低了算法的执行效率,而且各个多边形面片之间的着色操作是完全独立的,因此就可以将这个过程进行并行化改造,然后利用GPU的并行化来加快整个辐射度算法的执行效率。
 
3. CUDA与OpenGL的交互
由于在算法的实现过程中利用了OpenGL的渲染管线在每个多边形面片所对应的视角处进行渲染,然后从渲染缓存中统计出当前面片的颜色值,因此,若要将算法向GPU上移植就需要将OpenGL渲染得到的颜色缓存区中的数据传到CUDA中使用,可有三种方法实现这个操作。

第一种方法:经过host端完成传输。即以内存作为中间平台,先将OpenGL渲染得到的颜色缓冲数据传输到内存中,然后再从内存中传输到CUDA中,从而完成待使用的数据在CUDA中的映射。

第二种方法:用PBO完成传输。首先用OpenGL完成管线的渲染,此时,颜色缓冲区数据存在于显存之中,用OpenGL的一种实际数据空间在显存上的Buffer Object(Pixel Buffer Object)来做为中间平台,将颜色数据先传输到此PBO中,然后在CUDA中绑定并访问这个PBO,这样就可以将数据映射到CUDA之中。

第三种方法:用RBO完成传输。这种方法与使用PBO的方法差不多,只不过这里直接使用Frame Buffer Object中的Render Buffer Object来完成。改变OpenGL的渲染状态为离屏渲染到自定义的FBO中,这样就可以在一个RBO中得到对应的颜色缓冲数据,然后直接在CUDA中绑定并访问这个RBO就可以完成数据在CUDA中的映射。
 Data Trans
Data Trans 
4. 基于CUDA实现的辐射度
在CUDA上实现辐射度就是将该算法用并行结构实现而已。由上面描述的串行执行结构很容易得到相应的并行化算法结构。
1. 串行将N个多边形面片所需的颜色缓冲数据渲染至显存中,并传输到CUDA中进行映射。这一步的主要耗费是将数据向CUDA中传输并进行映射,并行性较低。
2.将多边形面片所需的数据块绑定到CUDA纹理,以便于各个线程访问。由于此数据块较大,故不宜使用__global__及__constant__来进行映射。
3. 在CUDA内部开N个线程,每个线程针对一个多边形面片进行缓冲数据的处理。这一步是主要的并行部分,可对N个多边形进行并行化处理,而且各个线程所执行的指令相同,且耗时一致,这样就避免了线程间的同步操作。
4. 返回N个多边形面片的最终color并进行着色。这一步也可以并行化操作,但由于不是效率瓶颈,直接在CPU上串行实现。
 Parllel Sample 
Parllel Sample 
4.1 效率对比:
在实现了最终的算法后,在上面场景下所做的效率统计如下表所示:

场景的分割精度是指在单位化后的场景中进行八叉树分割得到的叶子结点的AABB包围盒的较大尺寸,这也就决定着最终的叶子结点的个数和需要进行着色的多边形面片的个数。
其中,经由RBO来实现数据传输的方法没有实现。
N为在CUDA中并行处理的多边形面片的个数,当然,由于所有的操作并不是在均在GPU上并行计算而实现,因此并行后也并不能得到线性的80倍的加速比。而且,在并行化后的第一步中,数据传输也限制了整个算法的效率。
 
4.2 算法效果图:
Radiosity Result
 
5. 一些问题
1. CUDA与OpenGL互操作的问题
CUDA与OpenGL的互操作是通过OpenGL中的Buffer Object来实现的。理论上来说,这种互操作可以实现不经过host而直接在device中进行数据传递,而且速度应该比经过host要快很多,毕竟数据传输不需再经过PCI,但是通过实验得到的结果并不是这样。从上面统计的时间中可以看出,使用PBO反而比经过host来实现数据传输要更慢,关于这个问题,个人理解为当前的CUDA在与OpenGL互操作上的实现并不是很好。

2. FBO,RBO与CUDA的交互问题
使用PBO进行算法的改造,虽说效率不太理想,但毕竟这个操作还是实现了,但使用FBO与RBO来实现CUDA与OpenGL之间的互操作就不太容易实现。注册与绑定PBO到CUDA上可以实现,但对于FBO与RBO则均是调用失败。这个问题目前还正在解决之中。希望这个问题的解决能带来比当前更好的效率。

3.并行线程的数目N与显存大小的关系问题
在CUDA中可以进行速度提升的根本原因就是可以对N个多边进行同时的着色计算,当然这个N是越大越好,较好的情况就是有多少个多边形这个N就是多大,这样只需要一个流程就可以完成对所有多边形面片的操作,但是,这个N当然是受限的。在实际中,N值是由CUDA可支配的显存空间的大小与每个多边形面片在着色时所需的颜色缓冲区的大小的比值来确定,它不能大于这个比值。

鲜花

握手

雷人

路过

鸡蛋

最新评论

热门频道

  • 大数据
  • 商业智能
  • 量化投资
  • 科学探索
  • 创业

即将开课

热门文章

     

    GMT+8, 2020-1-19 19:06 , Processed in 0.167923 second(s), 23 queries .