测试了Light Propagation Volumes,全实时没有任何预处理的GI,而且可以适用任意场景。
文档很长,不过基本原理还是比较直白的:
生成reflect shadow map(rsm)。
将rsm信息用SH系数方式注入一个volumetexture中。
在volumetexture中进行propagation(大概叫传递或者传播的意思)。
最后将propagation完的volumetexture应用到场景上(可以用于延迟或者前向,这个比较强)。
总的来说,这个方法并不非常新鲜,如果对辐射度相关的东西有研究,特别是Radiosity和SH比较熟悉的话,看到这种方法
应该并不是很意外,而是一种很自然的发展,像ATI的irradiance volume,已经很接近了。
文档后面那长长的参考资料,如果都看过和了解,能想出这种方法也应该不难(不过那个量是非常恐怖的,像树一样,资料后面
还有资料。。。,需要日积月累)。
更细的东西可以看原始文档,这里说些文档上没提的细节和需要稍微注意的地方:
* 首先为什么能够这样做,这是因为SH系数有个强悍的特性,可以在球面所有方向进行叠加,适应3维空间上的不同方向,早先
通常用cubemap之类来保存radiance,SH避免了这个麻烦,当然如此少的系数只是近似。使用的时候通过与某个方向上的系数的
点乘计算出该方向上的强度。所以可以用一个点来表示各个方向系数的和。
* 说下文档中sh系数的来源,某个向量投影到SH基函数的时候,前两个band的系数是1/(2*sqrt(PI), -sqrt(3)/(2*sqrt(PI)*y,
sqrt(3)/(2*sqrt(PI)*z, -sqrt(3)/(2*sqrt(PI)*x。根据[Sloan08],如果投影到zonal sh基函数,则lobe系数是band0:
sqrt(PI)(cos(a)-1), band1: 0.5*sqrt(3)*sqrt(PI)*sin(a)^sin(a); 分别乘上去就是0.5*(cos(a)-1),0.75*sin(a)^sin(a),
-0.75*sin(a)^sin(a),0.75*sin(a)^sin(a),也就是文档中的那个ZH系数。
* 文档中的SHRotate函数有个副作用是对向量做normalize,如果向量已经是normalized的,则不需要再搞一次了,band1的
系数直接等于(vZHCoeffs.y * vcDir.y, -vZHCoeffs.y * vcDir.z, vZHCoeffs.y * vcDir.x)即可。
* 跟上面差不多,shader用到SHProject的地方多数都是用90度,所以可以直接先计算ZH系数,不用傻傻去做cos,sin。
* 注入和propagation两个阶段的texture,都应该用point方式读取,应用到场景上的时候用trilinear读取。
* 在dx9下由于没有render to volumetexture,可以用2d texture来模拟作为radiance volume,比如用1024x32来模拟
32x32x32,不过这样在pack到这个图的时候就比较痛苦一些,有不少小问题,要正确计算像素和uv的位置,而且在dx9下,像
素被认为是无大小,所以要小心的做偏移,另外就是trilinear也要自己做了,一个是完全用point方式sample,然后自己一堆lerp,
另一个是相邻两层用硬件bilinear,然后做一次lerp,后面这个要容易些,不过在边缘要做额外处理,比如在边缘塞进一个空白的像素,
坏处是图变大了,另一个方式是裁剪掉在边缘半个像素之内位置,这样不用改什么东西,只是调整了一下裁剪的大小。
* 关于像素snap,如果不snap则完全不稳定,闪烁厉害,基本没法使用。rsm snap的时候类似shadowmap,基数是一个rsm
texel的投影后的尺寸。而radiance volume snap的基数是volume的texel在world空间上的大小,两者不一样。
* 原始文档没有具体说用多大的rsm和volume的size,我自己试的话rsm在512x512比较好些,太多的话性能就下降厉害,
太少的话光源动的时候闪烁比较明显。
* 如何注入(injection)。使用一堆point primitive,画到radiance volume texture里(或pack后的2D radiance texture),
点的坐标是rsm空间的屏幕坐标,在vs里转到radiance volume的clip空间,在ps中投影到sh。
* 关于性能,没有具体测过,与场景有一定关系,不过我用cry的那个sponza场景,用上面说的设置,原来30fps,开启一个
cascade之后大概只有17,没有任何优化,并且为了方便,我用16bit浮点图存系数,很大的影响了速度,另一个是由于用dx9,
mrt消耗比较大(显卡是很垃圾的9400,有其它HDR等效果)。总的来说,实时是可以保证的,不过要稍好一些的显卡。
* propagation过程能量在cube的每个face间传递,但是最终的结果是前面所有步骤的叠加,不过可以对propagation
过程的中间结果加一个参数,用来控制衰减的快慢,较大的衰减可以减少propagation次数。
* 文档中并没有说一个volume实际覆盖多大的区域,这个跟场景有关系,我自己用32X32X32覆盖到32X32X32,觉得还
可以。但看cry的图似乎还要再大些,可能是64X64X64。这个数值如果更改那么注入的偏移也要跟着改。
* 如何应用到场景上。本质上很容易,就是场景的点的normal投影到sh,用trilinear采样radiance volume的点,做dot得到
亮度即可(所谓的cos lobe),场景的position可以根据需要向normal方向做一定偏移,这可以大大改善漏光和背面自照明的问题,
原文不推荐这种方法,不过我觉得在场景没有太多靠在一起的东西的时候效果还不错,反正一切还是以场景表现为主。
* RGB三个分量要分别用一个radiance volume表示,就是说injection和propagation阶段都是mrt,这在dx9上比较慢。
* 对于实际使用来说,虽然文档说适应任意场景,但不同场景有些参数可能需要不同,比如injection的偏移,衰减系数,
propagation次数之类,甚至grid cell的大小等等,可能都需要根据不同场景来调节达到最好效果。
贴些图(上边开,下边关):
============================================================================
============================================================================
============================================================================
============================================================================
============================================================================
============================================================================
============================================================================