找回密码
 立即注册
查看: 542|回复: 0

Unreal Engine UE4 静态阴影实现 Static ShadowMap ESM ...

[复制链接]
发表于 2021-11-21 19:02 | 显示全部楼层 |阅读模式
阴影的产生

阴影是光线被阻挡的结果;当一个光源的光线由于其他物体的阻挡不能够达到一个物体的表面的时候,那么这个物体就在阴影中了。


这里的所有蓝线代表光源可以看到的fragment。黑线代表被遮挡的fragment:它们应该渲染为带阴影的。
阴影映射



我们渲染一个点P处的片段,需要决定它是否在阴影中。
第一步:我们先得使用T变换矩阵把P变换到光源的坐标空间里。采样深度信息,既然点P是从光的透视图中看到的,它的z坐标就对应于它的深度,例子中这个值是0.9。
第二步:使用点P在光源的坐标空间的坐标,我们可以重采样深度贴图,来获得从光的视角中最近的可见深度,结果是点C,最近的深度是0.4。(UE4中重采样需要做Gamma矫正)
第三步:重采样深度贴图的结果是一个小于点P的深度,我们可以断定P被挡住了,它在阴影中了。
深度贴图

第一次采样获得深度信息,我们管储存在纹理中的所有这些深度值,叫做深度贴图(depth map)或阴影贴图。
阴影生成

若用d记作物体在Shadowmap中采样获取的深度,z表示物体的光源坐标深度,f表示阴影值,那么基础Shadowmap算法可以表示为:




0表示在阴影中,1表示不在阴影中。
它存在像自阴影和阴影悬浮这样的固有缺陷,此外它只能产生非0即1的阴影值,在阴影边缘处不可避免地会产生锯齿。
软阴影和阴影抗锯齿的解决方案

Exponential Shadow Maps (ESM)
为了使阴影在0~1直接过渡,假设阴影的软硬程度与z和d存在函数关系,准确是与z-d存在函数关系,ESM认为阴影与z-d的函数关系为



e^{-60*x}

可以看出z越接近d的时候,函数值越接近1;z越远离d的时候,函数值越接近0。
具体实现

    在深度贴图中保存 在采样阴影时计算
ESM存在的问题
c值比较小的时候,ESM的漏光问题非常严重,即使c值比较大的时候依然会有一点漏光。但是若C值设得太大,软阴影的效果就不明显了,而且可能会超过浮点数的表示上限。如果用在移动平台上,使用16位浮点数纹理,就更容易溢出了。对32F来说,c到88就已经到极限了。但为了要让那个近似更接近原始值,c应该越大越好,否则在z-d越接近0的时候,误差会越来越大。另一个缺点在于,原始ESM要求depth在非线性的projection space,这就给点光源的阴影造成了麻烦。如果用CSM的话,projection space也会因为在不同的层级而需要分别计算,分界线可能出现跳变。
改进ESM

view space depth
如果depth是在线性的view space,那么点光源和CSM都能用上ESM,也就是各种光源的shadow都可以切换到ESM。这个公式来自于EGSR2013上浙大的文章“Exponential Soft Shadow Mapping”。


这么一来,depth就都可以用view space的,只需要在c上除个far plane – near plane即可。
解决精度问题
前面提到了,如果c太大, 就有可能超过float的范围。但其实c*(d-z)本身远远小于c*d,不容易越界。所以如果不需要blur,那么只要在生成阶段保存d,就像SSM那样;在使用阶段,计算 即可。不过这样的话,已经失去了所有ESM的优点,还要ESM做什么。所以这里还需要改进blur的部分,争取在里面解决问题。实际上早在SIGGRAPH 2009的Advances in Real-Time Rendering in 3D Graphics and Games里,Lighting Research at Bungie就提到了logarithmic space filtering的方法。这里正是利用d-z远小于d或z的原理,把取值范围缩小了,精度也因此提高。filtering本身就是完成这个:


其中 来自于gaussian filter的 。如果进一步推这个公式,就能得到:


这个被称为log space filtering。最终filter的结果是个不会溢出的量


改进ESM实现方法

    在Shadowmap中保存 blur中使用  在采样阴影时计算
改进版ESM滤波具体实现,需要用到Unreal里的custom节点
int kSize = (mSize-1)/2;
float kernel[64];
float3 FinalColor = float3(0.0f,0.0f,0.0f);

float3 d0 = pow(Texture2DSample(Tex, TexSampler, UV), 2.2);
//create the 1-D kernel
float sigma = 7.0;
float Z = 0.0;
int j = 0, i = 0;
for (j = 0; j <= kSize; j++)
{
    kernel[kSize + j] = kernel[kSize - j] = 0.39894 * exp(-0.5 * float(j) * float(j) / (sigma * sigma)) / sigma;
}

//get the normalization factor (as the gaussian has been clamped)
for (j = 0; j < kSize * 2 + 1; j++)
{
    Z += kernel[j];
}

//read out the texels
for (i=-kSize; i <= kSize; i++)
{
    for (j=-kSize; j <= kSize; j++)
    {
        float3 di = pow(Texture2DSample(Tex, TexSampler, UV + float2(1.0 * i / 512 * dist, 1.0 * j / 512 * dist)), 2.2);
        FinalColor += kernel[kSize+j] * kernel[kSize+i] * exp(c * (di - d0));
    }
}

FinalColor = log(FinalColor/(Z * Z)) + c * d0;
return float4(FinalColor, 1.0);



references:

LearnOpenGL CN
杨鼎超:图形学基础 - 阴影 - ShadowMap及其延伸
Clawko:影子传说——三种Shadowmap改进算法的原理与在Unity中的实现
https://jankautz.com/publications/esm_gi08.pdf
切换到ESM - KlayGE游戏引擎

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-23 03:30 , Processed in 0.091498 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表