[这是参照Nvidia Gems中有关软阴影的算法,这里是针对聚光灯的一些个人的经验和心得 -ephtracy-]
算法的基本原理是对阴影的边缘进行PCF,这里涉及三个环节:
1. 采样空间
2. 采样点的分布
3. 动态分支
关于采样空间,存在三种,光源空间,物体空间以及屏幕空间
从真实度上说是降序排列,从效率上说是升序排列;
屏幕空间在上一篇曾提及到,效果很难达到我们期望的结果;
光源空间到物体空间的转换一般被称为“90度的思维转换”,
因为片元着色器擅长从一个片元周围采样平均,而不擅长同时对多个片元做累加操作,
前者只需要一个Pass,后者需要多个Pass,
而且从光源空间采样的话,需要渲染多幅阴影图,代价很高昂,
而如果我们希望只从一张阴影图中获得我们希望的结果,就是物体空间采样了
关于采样点的分布,是最重要的环节,
最native的方式当然是矩形阵列式的平均分布,
但是这样会产生很明显的条格状边缘,尤其是当采样半径加大的时候,就更加明显,
而且还有分离成几个采样阴影的现象,即像是由多个光源投射出来的效果,
造成这个的主要原因并不在于采样的分布形式,
而在于相邻像素的采样点分布是否具有很明显的相关性,
即我们需要邻近点的采样分布有明显的不同,
以使低频噪音转变成高频噪音(而人眼又恰恰对高频噪音不敏感)
Nvidia上的做法是构建一个随机表纹理,为每个阴影图上的点产生64个随机偏移量,
注意并不需要为每个点都独立地产生偏移量,因为只需要一定范围的采样分布不一样就可以,
所以可以使用一张小尺寸的随机表,比如128x128,然后repeat一下,效果其实更好
而这些随机偏移量的构造对于最终阴影的边缘光滑程度具有决定性的影响,
首先这些偏移向量必须均匀分布,而这些你并不能指望rand()函数,
因为它是伪随机函数,容易造成采样点的集中,结果又会出现阴影的聚集或不均匀分布,
对于矩形分布,我们的做法是划分成8x8的均匀区域,然后在每个区域中随机采样,这样就能达到我们的要求
如果希望模拟一个光源点通过一个点投影在一个平面上的结果是圆形的状况,
就不能使用矩形分布而应该是圆形分布,
这也很容易实现,只需要将矩形分布从直角坐标系转换到极坐标系就可以了
但实际问题是,影响整个算法的主要因素是对阴影图的采样次数,
因为这不像高斯模糊一样是可分离的,
所以只好老老实实地对每个像素做64次采样,
那么我们就只有在随机表查询上下功夫,
Nvidia采用的随机表纹理是RGBA四通道的,可以包含两个随机向量,
既64次采样需要额外的32次偏移量采样,这样显然开销很大,
因此我们希望只保存少量的随机坐标,然后通过某种关系(比如镜像)生成其他的偏移坐标,
这样就可以大幅减少随机纹理的访问次数
最后是动态分支,它的作用很明显,就是让采样操作仅仅集中在阴影边缘,
对于一幅通常的场景而言,阴影的分布一般是比较低频的,即大片的阴影或明亮的区域,
所以实际需要做PCF的像素很少,
所以我们可以先对64个采样的中的8个(比如最外围的8个)做采样,
由结果评估该像素是否是处于半影区,这样可以提高的效能是很大的
尽管动态分支对显卡的开销也还是很大的,而且会有个别点出现误差
阴影图的主要局限在于精度和分辨率,
对于聚光灯而言,16bits的深度纹理就足够了,
而这个算法主要解决的是由于分辨率不足而造成的边缘锯齿的情形,
而且由于随机查询有类似于蒙特卡洛实验的效果,所以采样半径也可以比均匀分布来得更广
-----------------------------------------------
这些截图是工作在 GeForce 9600MGT 上,幀频在50~70左右,
单光源,场景三角形30,000+, 使用一张分辨率为1024x1024的shadowMap
后两张是软阴影开与不开的一个对比
转载于:https://www.cnblogs.com/ephtracy/archive/2009/08/10/1542764.html