软阴影与抗锯齿.docx
- 文档编号:30495241
- 上传时间:2023-08-15
- 格式:DOCX
- 页数:13
- 大小:224.07KB
软阴影与抗锯齿.docx
《软阴影与抗锯齿.docx》由会员分享,可在线阅读,更多相关《软阴影与抗锯齿.docx(13页珍藏版)》请在冰豆网上搜索。
软阴影与抗锯齿
软阴影的实现尝试
软阴影(Soft-Shadow),并非一种图形学算法或技术的代名词。
怎么说呢,它是图形学大师们孜孜不倦地追求的更逼近真实的阴影效果。
——ZwqX
在已有的阴影DEMO上作改进,是当时很自然的想法。
而选择了[ShadowMapDemo2],不知道,算不算是一个失策。
CascadedShadowMap技术,从本质上说,只是分区间地调整投影矩阵,根据视距离使用不同分辨率的阴影图,促使效率与效果平衡而已。
这是ShadowMap改进算法的一条路,但并非唯一。
回想[ShadowMap阴影贴图技术之探Ⅲ],里面我简单地用乒乓式的采样,实现了一下3X3的阴影图模糊——PCF。
虽然结果看上去很那个,但这昭示着ShadowMap改进算法的另一条路——图形学中的图像处理,调整阴影图。
在我的上一个阴影DEMO中,主要的shader只有一个,它是在Shadow-Casting阶段完成场景阴影图的分层次贴附。
在CPU上完成的是ShadowMap-Generating阶段,因为那里没有涉及像素层面,完全不需要GPU的并行计算能力。
但如果要对一张贴图的内容进行像素级的处理……恩,这是通常的想法。
但当时脑中另一个突然一闪的想法,让我决定先行对其进行捕获——场景后处理。
一般说的DefferedShading并不等同于这个意义,但就字面来说它们也有那么一点共通。
恩,题外话先免了,先暗自打个小算盘:
场景后处理,我是想在最终画面上动手脚。
在OpenGL流水线的尾部,无论是否双缓冲,必然是把显存上每个像素格的值传向显示器屏幕上的每个“真·像素”上,表示成颜色(譬如LCD是用彩色滤光片等结构对此转换的,可以理解为每个像素上又有红绿蓝三色子像素balabala……嘛,其实我也不怎么理解啊这些硬件的--,呃离题了,回来~)。
如果把这个覆盖屏幕的“结果”当作贴在一个Screen-Aligned-Quad(屏幕大矩形)的一张纹理,我们在显存-屏幕这个过程之前进行拦截,获得并处理这个“纹理”后再让它传向屏幕。
有这方面基础的朋友都知道,FBO又该出来表演了[学一学,FBO]。
把Shadow-Casting的结果写入FBO绑定的一个纹理中,然后自己画一个Screen-Aligned-Quad,贴这个纹理——对这个过程启用shader处理。
最后虽然屏幕上本质只有这么一个矩形,但是众多本该直接映射在屏幕上的场景元素已经完美地“被代表”了。
看上去丝毫没有河蟹爬过的痕迹……完全没有。
依然是模糊处理。
高斯模糊。
01
//
02
uniform int BSceneWidth;
03
uniform int BSceneHeight;
04
uniform float ishoriz;
05
06
uniform int samplecount;
07
uniform float weights;
08
uniform float sigma;
09
10
uniformsampler2D BScenemap;
11
12
void main()
13
{
14
gl_FragData[0]=BlurFilter(BScenemap,gl_TexCoord[0].xy) ;
15
}
其中BlurFilter函数是对当前像素,在水平或垂直方向上采样(samplecount),采样值乘以一个高斯分布值而已(公式也就网上常见的,sigma为参数),跟PCF也就差不多那回事。
很明显,我们需要在水平方向和垂直方向都模糊一次,这里用2个PASS。
我们只需要模糊阴影,因此把阴影部分和场景部分分开写入纹理(MRT),最后再统一。
1
//CastingShader,rendertotwotextures (MRT)
2
gl_FragData[0]=vec4(gl_Color.rgb*diffuse.rgb *texColor.rgb,1.0);
3
4
gl_FragData[1]=shadeFactor;
1
//PostProcessingShader,asatexturetothescreen-alignedquad
2
uniformsampler2D Scenemap;
3
uniformsampler2D Shademap;
4
5
void main()
6
{
7
vec4shadeFact=texture2D(Shademap,gl_TexCoord[1].xy);
8
gl_FragColor=texture2D(Scenemap,gl_TexCoord[0].xy)*shadeFact;
9
}
写到FBO纹理后,屏幕矩形绑定之:
软是软了,就边界效果而言是很好的(这里图片压缩了看上去糟糕而已)但有几个问题:
1.很明显的边界问题,阴影与场景分离了,SampleNum越大分离得越明显;2..随着SampleNum增加帧率狂降。
效果和效率都不行。
首先,这种高采样带宽的东西本来就是帧率杀手,更不用说高斯分布权数的计算实在够呛。
其次因为场景和阴影分离,而模糊的只是对于相机“可见”的那部分阴影,所以图中间隔线是活生生被当作阴影边界了。
后来也考虑过再加入图像处理中的“膨胀”运算[形态学运算小结],但是有点吃力不讨好的感觉,无论膨胀多少次,还是能依稀见到狭缝,加上效率问题,宣告此法的破产。
左-右,上-下依次32p4p4pass1,32p2p2pass1,32p2p2pass2,64p2p2pass1(自己猜,我什么都忘记了哈)
还是好好用别的方法吧~
软阴影与反锯齿有着深刻的关系,PCF在Graphics上原本就是一种反锯齿技术,而它在阴影算法上往往作为简捷的软阴影达成途径。
VSM(VarianceShadowMap)同样旨在解决这一问题,但它并非像以往一样对阴影图的后处理,而是重新定义阴影图。
——ZwqX
1.锯齿与反锯齿
锯齿感是在Casting的时候产生的。
我们确实是相当于把一张阴影图(深度图)贴在场景上了,但具体的操作依然是一条深度比较指令。
譬如,传统的SM中,根据场景物体像素在光源视觉下的深度得到的阴影图,在Casting阶段对应相机视觉中的像素判断是否属于阴影图中的像素——这本来就是一个TRUEORFALSE的判断,若TRUE证明非阴影,像素颜色值不变;若FALSE则涂黑,以表明该处阴影。
NewValue=OldValue*(isShadow?
0:
1)
即使把0替换成shadowMapValue或是运用距离值判断,也改变不了这里的根源:
把视场景分割成阴影区和非阴影区。
双方强烈的撕扯总会造成霹雳的裂痕。
我们一般使用的纹理在贴在奇形怪状的表面上时产生的扭曲同样会导致锯齿,可为什么却不那么难接受呢?
原因是filtering。
初学纹理贴图的时候,一定会接触到TextureFiltering,尤其在发生欠采样和过采样的时候,纹理映射的策略。
在OpenGL1.x中,GL_LINEAR和GL_NEAREST应该是最为熟悉的,尤其是线性插值GL_LINEAR,让我们贴纹理的时候能得到比较好的性效比:
1
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
2
glTexParameteri(GL_TEXTURE_2D_ARRAY_EXT,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
贴纹理过程的采样能通过简单的线性插值完成,再把插出来的值映射到恰当的位置去——想想对于深度图这会是什么情况:
低分辨率的阴影图上一个4X4格子,对应场景中9X9大小区域,恰好区域覆盖阴影边界,深度插值后某像素获得了表示“半影”的灰度数值。
但是要“贴”到场景时,需要经过一个TRUEORFALSE过滤器,这个“半影”像素依然要选择:
自己究竟属于影子,还是非影子。
一旦整个边界的像素选择完成,由于阴影判断式对所有像素等效,它们会发现自己“整齐”地划出了一条边界线,锯齿边界线。
2.VarianceShadowMap
VSM(VarianceShadowMap)的提出正基于此。
其实它本质也是一种基于概率的方法,把以上的TRUEORFALSE过滤器过程改作一个可见性估计过程……嘛,你也知道概率学上的东西有很多微妙的地方……让texturefiltering作用于每个像素的方差(Variance)而非其本身的值,于此建立的切比雪夫不等式作为这个可见性估计式。
ZwqXin本身概率论学得不深,为了避免误人子弟,这许概率数学原理我就PASS了。
每个像素经过这样折腾一下,取切比雪夫不等式的上限(UpperBound,即上式pmax(t))为结果,它大概揭示了当前像素属于“非阴影区”的程度(当这个UpperBound越接近0时代表此像素越可能“属于阴影”)。
UpperBound计算式的三个参数,t是像素的实际深度(根据GPUGEMS3,这里取的是像素距离光源的距离distToLight),μ是当前像素深度的期望E(x)(取的是直接在阴影图上采样得到的[插值后的]深度,好吧,囧),σ2是方差(σ2 =E(x2)-[E(x)]2)。
首先查查什么是“切比雪夫不等式”(Chebyshev'sinequality)。
是的,上过概率课,所以至少听过这个名词(听说,那是因为期末考后潜意识对自身的遗忘魔法):
inanydatasampleorprobabilitydistribution,"nearlyall"thevaluesare"closeto"themeanvalue—theprecisestatementbeingthatnomorethan1/k2 ofthedistribution'svaluescanbemorethankstandarddeviationsawayfromthemean.[WIKI]。
上面式子应该是另一种表达形式吧(吧?
)。
在我看来,切比雪夫不等式的上限是关于t的函数式,描述了一个具体的量t在随机集合X上的位置的概率范围。
Casting过程中,一个像素往深度图ShadowMap上采样,姑且认为这是一个随机选择的过程;像素若是根据其纹理坐标(光源投影矩阵合理所得)进行的采样,纹理上应该只有唯一的一个点与之对应——当然,即使取隔壁的点也不是不可,不过它作为准确值的概率没有那个“唯一的点”大——把可能取的值作为集合X,那X就是一个以“唯一的点”为中心的圆,使该点作为集合的期望值μ。
(期望的计算式子E(X)=x1 *p(x1)+x2 *p(x2)+……,稍微考虑一下就明白)。
问题是t,它作为实际值,在我们的纹理采样过程中是不可能出现的,甚至我觉得它根本无法得出,为什么取“像素距离光源的距离distToLight”呢?
它的计算不存在于纹理采样过程中,但也不可能就说它就是“真实值”啊——哪怕表义如此。
对此,可看看GPUGEMS38.1中作者的解释,不过我是没有太理解而已(尽管没有更好的)。
于是,既然摆脱的TRUEORFALSE的抑制,我们完全可以动用texturefiltering去取那个“唯一的点”——期望值E(x)]了。
这不,为了计算方差,我们还需要一个E(x2)——类似地,我们只需要在ShadowMapGenerating阶段把像素深度值的平方也保存起来就可以了——这就是VarianceShadowMap,需要一张纹理的两个通道,分别存储深度和深度平方。
CASTING阶段把各值传入切比雪夫不等式计算上限值——这不是确切的“属于非阴影区的概率”,但它是唯一明确能得到的值,这个不等式误差是VSM致命弱点的根源。
这个上限值概率作为“阴影因子”乘在场景像素上,造出了肉眼可见的“半影”区,同时大大地增强了阴影的边缘的轮廓感(难看的锯齿减少)。
(CSM一般效果,没加PCF模糊,注意看的是阴影外形)
(对比。
CSM+VSM可见没有了突兀的轮廓,filter的效果显著)
(对比。
没有VSM效果时,远观时(splitNum=4)轮廓已经很糟糕了,但VSM下能显示完好轮廓)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 阴影 锯齿