可编程图形硬件的迅猛发展,让高质量实时体渲染图形成为可能,火焰,烟雾,大气效果,云的渲染变得越来越快,这篇文章既包含科学领域应用,如医疗成像,也包含一些游戏中的特效应用。
1。体数据
离散的体数据可以想象成一堆3维的立方体,也可以称其为体素,每个体素代表一个体空间,其实将体素想象成连续3维信号的采样更加恰当,这个采样在空间表现为一个点。采样理论告诉我们,如果我们以一个原始信号最高频率两倍或以上的采样频率对原始信号进行采样的话,那么我们就能使用采样数据完美重建原始信号。但现实中进行完美重建原始信号存在两个难点:
a.理想重建要求使用一个sinc函数对原始信号在空间域上进行进行卷积(因为sinc函数是一个理想低通滤波器),注意sinc函数是定义在整个空间域上的,意味着重建时所有采样点必须参与到每次计算,计算量上难以承受。
b.现实中的信号并不是带限的,就是说并没有一个最高频率,比如从黑色变化到白色,属于阶跃信号,变化到频域,其最高频率无穷大,SO不存在一个最高采样频率。(信号处理的一些基本概念在“球谐光照”有提到)现实一点,一般用箱式滤波器和蓬式滤波器代替sinc滤波器(其实在通信领域这两个滤波器效果非常差),蓬式滤波器说的草根点其实就是线形插值,三维空间中的线形插值当然就是三线形插值Trilinear,箱式的表现形式就是就近点采样,效果差得很在这里不做讨论。
2。体渲染
直接体渲染:直接体渲染的最基本算法就是RAY-CASTING(光线投射),以前游戏中经常用这个来渲染地形,有兴趣的请参考相关文章《VOXEL 技术在即时渲染地形中的应用》,通俗易懂。体渲染比较麻烦的就是一个亮度积分,沿投射光线对体素发射亮度的衰减函数进行的积分,实时图形学目前不可能做完美的符号积分,SO转换成简单的黎曼和(就是类似矩形近似)。直接体渲染我们现在一般不会用,所以我不会详细注释,在此只是稍微解释下RAY-CASTING的原理。
Ray-Casting:光线投射是一个图像空间的直接体渲染算法,其体渲染积分的数学计算非常的简单,对需要成像的图像空间,视点投射一根光线穿过图像的像素,沿这这根光线,离散的体数据会等间距的被再次采样(为什么是再次采样,因为得到这些数据我们就已经采了一次原始信号了,忘了?)当然是用线行采样(学名蓬式滤波),任何一个采样点周围必定会有8个体数据,然后利用采样点到他们的距离在他们之间进行线形插值,插完得到一个标量,利用这个标量值,找到这个采样点所对应的 EMISSION 和 在这个空间位置上对应的衰减值,其实就是RGBA。体渲染积分也就用前序后者后序的ALPHA混合在近似处理了。
Shear-Warp:错切-变形算法。该算法由G.G.Cameron和P.Lacroute等人 提出,原理是将三维数据场的投影变换分解为三维数据场的错切(Shear)变换和二维图象的变形(Warp)两步 来实现,从而将三维空间的重采样过程转换为二维平面的重采样过程,大大减少了计算量。这是目前被认为速度 最快的一种体绘制算法。其原理就是对体素进行错切变换,然后体素采样方式得以以正交投影形式完成,而后基础平面上得到的像素结果会变换到视平面,而透视投影则在错且基础上再进行缩放就OK了。
Footpring / Splatting:足迹表法(泼墨)。另一种经典的体绘制算法,原理我也不清楚,呵呵。
3。基于GPU的体渲染
体渲染中最基本的操作就是体素的采样,另外一个重要的操作就使体素的合成,这两个操作目前都可以利用现代图形硬件进行加速,纹理映射本质上来讲就是对采样点的颜色进行查值,而将采样点进行混合则很容易在像素片断阶段完成,现在的问题就是我们怎样获得与光线投射算法相同的结果。现代图形硬件只能渲染多边形,所以我们要利用硬件加速直接体渲染,必须建立“代理几何体”,体素采样坐标通过代理几何体上纹理坐标插值获得,在通过后续ALPHA混合这些代理几何体片断来完成体素的混合。体数据本来是存储在3D或者2D图片中。
代理几何体最常用的形式就是一组视平面平行的平面。3D纹理坐标通过内部插值产生,但这只适用于3D纹理。如果体素以一坨2D纹理呈现,该怎么办呢?我们必须用一组 物体对齐 的几何体来作为“代理几何体”,问题就来了,2D纹理只能在本纹理空间内进行采样,一般会沿体素数据的3个主轴存储3个片组,这样的目的主要是为了防止光栅化0个像素。
4。光线投射算法实现
基于几何体片的体渲染有很多优点,比如:纹理缓存与光栅化单元之间带宽很高;内建高效采样器;高性能的光栅化单元。但也有几个缺点:几何体片的方位和数量直接由体素数据所决定,几何体复杂度受原始数据影响很大;输出敏感度差,一个好的图形算法应该是高度输出敏感的,而在图像空间处理更能达到这个目标,实际上光栅化做了极大的无用功,应用中大概只有0.2%到4%的像素片断影响最终的图像,因为大部分的体渲染程序关注的是按边界部分区域可视,或者是只显然制定材质的部分,因此其实大部分像素片断其实都是设置成了全透明的。而传统的光线投射能完美解决这些问题,光线投射的每条光线能独立选择步长,能决定光线投射的距离等等。这章的主要目标就是让这些传统的CPU实现算法在GPU上实现。首先光线投射算法表现出最本质的并行性(因为光线与光线之间基本没啥关系),这种并行性能很自然的被我们映射到现代GPU体系中,比如将每根光线的操作映射到像素上,这样就能用GPU的像素着色器部分来加速光线投射算法了,另外体数据也可以存储在纹理中,这样就可以利用GPU的高带宽来访问他们。但在此之前,我们必须解决两个基本问题,第一就是GPU没有循环指令(现在好像有了),而沿光线遍历是在CPU上是个循环,第二个是条件BREAK,就是在特定条件下终止遍历。
5。均匀栅格的光线投射
均匀栅格的几何造型和拓扑结构都很简单明了,现在直入主题,我们将讨论GPU-BASED RAY-CASTING的最重要的两个要素:体数据的存储于像素片断的处理。GPU可以高效的访问纹理数据,因此我们将体数据以3D纹理的形式存储与显存中,另外,光线投射还需要一些额外的辅助数据,因为光线和像素是以一一对应关系,所以我们可以将这些数据存储在一个2D纹理中,通常只有一个1维的光线参数存储在中间纹理中,他就是入射点把体数据的距离,在遍历光线的过程中,这个值是需要不断更新的,但目前DX和GL都不支持同时读写纹理,这里就需要一个变通的方法,我们会使用两张纹理,第一张存储的这次需要的采样距离,这张纹理是允许读取操作的,另一张则允许写入操作,在每次迭代后,这两张纹理互换角色,纹理写入可以通过渲染到纹理来完成,非常高效。渲染过程主要有三个步骤:a.光线参数的初始化;b.光线遍历亮度衰减积分;c.测试是否终止遍历。均匀栅格的体渲染一般使用的是体包围盒作为代理几何体,首先我们必须确定光线进入体数据的位置,然后这个位置到相机位置的距离写入纹理,作为中间数据送入下一个PASS,如何终止遍历呢?首先如果遍历到一个完全不透明的地方,这根光线遍历就可以终止了,或者超出了体数据的范围,但GPU不提供一个条件退出指令,实际上我们使用深度测试来完成这个功能,以一种方式设置Z值,让GPU在进入丢弃掉Z TEST失败的像素,当然当所有光线都终止时,我们需要完成本帧渲染,也就是结束应用程序循环PASS渲染。其中就有使用Early Z-Test做法,在光线遍历进入前就丢弃像素的。