在做opengl的时候做到了shadowmapping,效果做出来了但其实并不能算吃的很透
刚巧想起之前在202已结搭建好了框架环境,可以成功把202娘跑起来
所以会跟着202好好的把课听一遍
这次开坑的任务:
总结202课上精要内容,加入自己理解作为输出
在作业1的框架上完成shadowmapping的硬阴影
开始吧 Let ‘s go!
Recap Shadow Mapping
做两次光栅化(pass)
第一次从光的视角(light space)做一次pass,得到光看到的更近的更浅的深度值
将其渲染到一个texture上记录下来,作为ShadowMap
第二次从观察视角(camera)做一次pass,得到物体更远的深度值
取一阶段的ShadowMap深度进行比较
若第二次的深度值小于第一次的深度值,则生成硬阴影
优势:不需要场景的几何信息,一旦shadowmap已经生成,shadowmap就能作为场景的几何表示
缺陷:会发生自遮挡和比较严重的走样(锯齿)
备注:生成z值时用的矩阵如果是用正交的,第二个阶段也要拿正交的去比
同理用透视生成的z也一样,两者只要对应上都能生成正确结果
但大佬提醒,平行光用正交投影,点光源和聚光灯用透视。
self-occlusion
如果没有在做深度对比的时候进行bias(偏移)出来的图大概率是像这样的:
原因是这样的:
我们在第一阶段用一个像素来记录深度值,但此时如果光对于平面来说是斜照的
那一块区域就都只能取小红线这个深度值(即小红线就是像素记录的深度值)
当从上往下垂直照射(光线和平面法线重合)的时候,自遮挡的现象最不明显
为了尽量缩小影响bias带来的计算偏差
因此我们会根据光照和法线的夹角来计算一个bias在比较深度的时候使用:
float bias = max(0.05 * (1.0 - dot(fs_in.Normal, lightDir)), 0.005);
这样做也会带来一些问题,例如我们看上去物体就像浮在表面上一样
阴影并不是贴合着物体的轮廓的,我们称这种现象为悬浮(Peter Panning)
(这个概念在Learnopengl Shadowmapping有不错的解释:[阴影映射 - LearnOpenGL CN (learnopengl-cn.github.io)](https://learnopengl-cn.github.io/05 Advanced Lighting/03 Shadows/01 Shadow Mapping/#_6))
RTR doesn’t trust in complexity(实时渲染不相信复杂度)就像电子竞技不相信眼泪哈哈哈
晚上玩了下原神观察了下阴影,听大佬说人物的阴影用的CSM(Cascaded Shadow Mapping 级联阴影映射)
即给不同位置的shadowmap不同的分辨率,降低开销同时也能提升细节
动态阴影基本上都是用各种改进的Shadowmap做的,还有就是光追阴影
因此Shadowmap需要解决改进的就是锯齿问题
The Math Behind Shadow Map
将两个函数乘积再积分近似等于积分再乘积(分母相当于做归一化)
当g(x)的support(支撑集)很小,或者g(x)的函数曲线足够光滑(smooth)
左边是visibility,右边是纯Shader的结果
对于点光源和方向光源来说,相当于是Small Support满足了第一条件
所以是可以近似认为,可以用约等式将shader和visibility的部分拆开分别计算再叠加
对于面光源来说,面光源内的radiance认为是相等各处不变的,即L是smooth的,也可以满足第一条件
而对于shading point来说diffuse的表面我们认为变化是比较小的
而glossy的表面(被打磨的铜镜之类的材质效果,是较为模糊的镜面)是较为剧变的,结果就比较不准确
Percentage Closer Soft Shadow (PCSS)
对于硬阴影来说,阴影在边界上没有从无到有的过渡,看上去很不自然
在生活中,光源基本上都是理想的面光源,看到的阴影都更倾向于软阴影,有一个良好的过渡。
PCSS是用来生成软阴影的技术,建立在PCF(percentage Closer Filter)之上
PCF是起初用于解决阴影边界的抗锯齿而非计算软阴影。
PCF不是对最后生成的结果的锯齿做模糊,而是在做阴影的判断时做的Filter
同样是因为不能先得到一个走样的结果,然后再做一个模糊(和做反走样的概念是一样的)
PCF同样不是对生成的ShadowMap做了模糊,因为对Shadowmap和z值比较的结果也就是我们最后的
shadow值是一个非零即一的结果,即使对shadowmap做比较最后的shadow值依然是非零即一的,
这没有意义,无法达到抗锯齿的效果
PCF的做法是在投影到light space找对应像素时,不单单只找那一个像素,而是找周围一圈的像素做一个平均(Filter)
![image-20220312005309957]image-20220312005309957.png)
借用learnOpenGL的对此的解释:(个人感觉很到位而且给出了具体的代码演示)
核心思想是从深度贴图中多次采样,每一次采样的纹理坐标都稍有不同。
每个独立的样本可能在也可能不再阴影中。所有的次生结果接着结合在一起,进行平均化,我们就得到了柔和阴影。
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
for(int x = -1; x <= 1; ++x)
{
for(int y = -1; y <= 1; ++y)
{
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}
shadow /= 9.0;
这个textureSize返回一个给定采样器纹理的0级mipmap的vec2类型的宽和高。
用1除以它返回一个单独纹理像素的大小,我们用以对纹理坐标进行偏移,确保每个新样本,来自不同的深度值。
这里我们采样得到9个值,它们在投影坐标的x和y值的周围,为阴影阻挡进行测试,并最终通过样本的总数目将结果平均化。
使用更多的样本,更改texelSize变量,就可以增加阴影的柔和程度。
效果可行,但对于一个shading point要做更多次的计算,所以会变得很慢
课程中的对比图
这是自己手搓的3x3的卷积,Filter越大则阴影越软,因此软阴影就是因此计算而来的
而一般来说,离物体越近的地方阴影就越硬越明显,因此就有了Percentage Closer Soft Shadow的概念
即根据阴影与遮挡物的距离给定不同大小的filter size
这张图很好的说明了Light面越大则软阴影的区域就越大(图中相似三角形的关系)
我们可以想象,若遮挡物Blocker的离接收物Receiver越近,相似三角形就会更小
这就很好的解释了为什么有“离物体越近的地方阴影就越硬”的这个现象
恰巧笔者最近也在玩消光,有机会的话去游戏里好好观察一番
打异度之刃2去咯 2022/3/12 2:20