Unity Shader - Ray Marching - T6 - SoftShadow/PenumbraShadow


自学Raymarching汇总: Unity Shader - Ray Marching Study Summary - 学习汇总

搜索了一下资料:软阴影

也可以参考我之后翻译的一篇:penumbra shadows in raymarched SDFs - 光线步进中使用有向距离场实现软阴影

总结为下图

在这里插入图片描述

图中的:

  • umbra 英 [ˈʌmbrə] 美 [ˈʌmbrə] ,本影 ==> 影子中光源完全照射不到的部分
  • penumbra 英 [pəˈnʌmbrə] 美 [pəˈnʌmbrə] 半影 ==> 黑暗与光明之间的

这是比较理想、真实的软阴影的方式。


但是我们这里Raymarching的是另一种模拟方式:
如下图:
在这里插入图片描述

核心代码:

float getShadow(float3 p, float3 p2l, float distWithL) { // 获取当前碰撞点是否在阴影中,返回光衰减值
    float3 ori = p;                     // 射线起点
    float3 dir = p2l;                   // 射线方向
    float3 pos;                         // 当前步进到的位置
    float dist;                         // 当前步进到的最近距离
    float d;                            // 当前最近距离
    pos = p + dir * EPSILON;            // 先将起点偏移一点点,防止本身在几何体表面距离太小而认为是在阴影
    
    UNITY_LOOP
    float minD = distWithL;
    for (int it = 0; it < MAX_STEP_TIMES; it++) {
        d = sceneDF(pos);       // 获取场景中所有几何体当中最近距离的
        dist += d;              // 调整当前步进到的最近距离
        pos = ori + dir * dist; // 调整当前的步进位置
        minD = min(dist, minD);
        if (d < EPSILON_SHADOW) { // 有碰撞,阴影的碰撞检测距离小一些,精度高一些
            return dist < distWithL - EPSILON ? 0 : 1; // 碰撞点距离小于起点与灯管的距离
        }
        if (dist > distWithL) {
            return 1;
        }
    }
    return 1;
}

float getSoftShadow(float3 p, float3 p2l, float distWithL) { // 获取软阴影
    float ret = 1.0;
    float dist = EPSILON;       // 累计步进距离

    #if _SOFT_SHADOW_IMPROVE
        float last_d = 1e10;    // 初始的上次距离设置大一些,这样y的第一次的值就可以接近于0
    #endif

    for (int it = 0; it < MAX_STEP_TIMES; it++) {
        float d = sceneDF(p + p2l * dist);
        
        #if _SOFT_SHADOW_1
            // ret = min(ret, 10.0 * d / dist); // 10.0 就是我们的灯光大小
            ret = min(ret, LIGHT_SIZE(_LightSize) * d / dist);
        #else // _SOFT_SHADOW_IMPROVE
            float y = d * d / (2.0 * last_d);
            float l = sqrt(d * d - y * y);
            // ret = min(ret, 10.0 * l / max(0.0, dist - y));
            ret = min(ret, LIGHT_SIZE(_LightSize) * l / max(0.0, dist - y));
            last_d = d;
        #endif
        if (ret < EPSILON_SHADOW || dist > distWithL) {
            break;
        }
        dist += d;
    }
    return saturate(ret);
}

fixed4 getColor(v2f i) {
    float3 ori = i.ray;                 // 射线起点
    float3 dir = normalize(i.ray);      // 射线方向
    float3 pos;                         // 当前步进到的位置
    float dist;                         // 当前步进到的最近距离
    float d;                            // 当前最近距离
    float far = _ProjectionParams.z;    // far

    ori += _WorldSpaceCameraPos.xyz;    // 偏移,加上相机位置
    pos = ori;                          // 从起点出发

    float3 lightPos = _LightInfo.xyz;   // 灯光位置
    float lightRange = _LightInfo.w;    // 灯光范围
    float rangeEnable = step(EPSILON, lightRange);

    UNITY_LOOP
    for (int it = 0; it < MAX_STEP_TIMES; it++) {
        d = sceneDF(pos);       // 获取当前几何体集合中最近的距离
        dist += d;              // 调整当前步进到的最近距离
        pos = ori + dir * dist; // 调整当前的步进位置
        if (d < EPSILON) {      // 有碰撞
            float3 p2l = lightPos - pos;            // 碰撞点指向光源向量
            float distWithL = length(p2l);          // 与灯光距离
            float atten = rangeEnable * (1 - clamp(distWithL * lightRange, 0, 1));   // 灯光衰减
            float3 l = normalize(p2l);              // 灯光方向
            float3 n = getNormal(pos);              // 法线方向
            float NdotL = max(0, dot(n, l));        // 漫反射,diffuse
        #if _HP2L
            #if _HARD_SHADOW
            float shadow = getShadow(pos, l, distWithL); // 获取从碰撞点到光源点的阴影光衰减值
            #else
            float shadow = getSoftShadow(pos, l, distWithL); // 软阴影
            #endif
        #else // _L2HP
            // _L2HP ,Light to HitPos 只支持Hard,因为软阴影算法是从HitPos 到 Light的步进计算
            // #if _HARD_SHADOW
            float shadow = getShadow(lightPos, -l, distWithL); // 获取从光源点到碰撞点的阴影光衰减值
            // #else
            // float shadow = getSoftShadow(lightPos, -l, distWithL); // 软阴影
            // #endif
        #endif
            return NdotL * atten * shadow;
        }
        if (dist > far) {
            return 0;
        }
    }
    return 0;
}

注意Soft只能用:HP2L(HitPos to LightPos)的方式,因为从表面碰撞点为起点,到光源位置,如果过程中里

Differences in Hard and Soft - 硬软阴影区别

在这里插入图片描述

Soft Shadow - 软阴影

根据 Sebastian Aaltonen 在2018 GDC 上发布过未改进的初版实现: 第 38
Aaltonen_Sebastian_GPU_Based_Clay.pdf 或是
百度网盘Aaltonen_Sebastian_GPU_Based_Clay.pdf 提取码: d1w8

ret = min(ret, 10.0 * d / dist); // 10.0 就是我们的灯光大小

可参考:http://www.iquilezles.org/www/articles/rmshadows/rmshadows.htm
在这里插入图片描述

软阴影的球体追踪

  • 软半影的宽度阴影
  • 沿着光射线步进的SDF,模拟圆锥体最大覆盖值
  • 演示场景中,圆锥体覆盖值大概为1:c = min(c, light_size * SDF(P) / time),其中c就是coverage简写,P是当前步进到的位置点,再次SDF(P)意思就是再次对当前点求场景最近距离场,time是累计步进的距离,

从上面的公式可以看出来,如果light_size如果也大,那么light_size * SDF(P) / time就越容易到达1值,那么c的值也就越容易为1,但这个效果往往与我们现实生活相反,不信你自己在房间开启你的光管,光管灯光大小是比较大的,所以阴影越是软,越是平滑,如果你把光管关了,用自己手机开启灯光,你会发现阴影的边界一本是比较锐利的,至少比光管的锐利多了。所以你可以看到本博文最后的代码中,用了一个宏:#define LIGHT_SIZE(v) (303-v) // 获取灯光大小,就是反过来的值。

但这些都是模拟的实现,真实的光照中软阴影是影响参数有:

  • Surface碰撞点指向Light Occluder碰撞点的最近距离
  • Light Occluder碰撞点指向Light的距离
  • Light 的大小

这些参数是我自己用手机灯光拉远拉近时观察的结果

下面我用Excel来采样一条射线步进结果:
在这里插入图片描述

上面LightSize=10shadow0.091324201,可以看到h没有一次是碰撞到表面的,但也有阴影值(不为1),这就是软阴影。


下面我们将LightSize=100
在这里插入图片描述
可以看到h没有小于0.01的,都是1shadow值,意思就是不在阴影中,所以结果就是很锐利的。

Soft Shadow Improvements - 软阴影改进

根据 Sebastian Aaltonen 在2018 GDC 上发布的改进软阴影带条, 第 39
Aaltonen_Sebastian_GPU_Based_Clay.pdf 或是
百度网盘Aaltonen_Sebastian_GPU_Based_Clay.pdf 提取码: d1w8

            float y = d * d / (2.0 * lastDist);
            float l = sqrt(d * d - y * y);
            // ret = min(ret, 10.0 * l / max(0.0, dist - y));
            ret = min(ret, LIGHT_SIZE(_LightSize) * l / max(0.0, dist - y));

相比之前的:

ret = min(ret, LIGHT_SIZE(_LightSize) * d / dist); // 10.0 就是我们的灯光大小

多了,d改成l了,dist改成 max(0.0, dist - y) 其中,l与y,可以查看这里的说明

在这里插入图片描述

运行效果 - 改进后的带条没那么严重了
在这里插入图片描述
下面的GIF因为上传后压缩后导致图像质量太低,出现了带条。
在这里插入图片描述

Add controlling Light Size - 添加用于控制的灯光大小

在这里插入图片描述

Shader

float _LightSize;               // 点光源半径大小:lightSize
...
#define LIGHT_SIZE(v) (303-v)   // 获取灯光大小
...
float getSoftShadow(float3 p, float3 p2l, float distWithL) { // 获取软阴影
    float ret = 1.0;
    float dist = EPSILON;

    #if _SOFT_SHADOW_IMPROVE
        float lastDist = 1e10; // 初始的上次距离设置大一些,这样y的第一次的值就可以接近于0
    #endif

    for (int it = 0; it < MAX_STEP_TIMES; it++) {
        float d = sceneDF(p + p2l * dist);
        
        #if _SOFT_SHADOW_1
            // ret = min(ret, 10.0 * d / dist); // 10.0 就是我们的灯光大小
            ret = min(ret, LIGHT_SIZE(_LightSize) * d / dist);
        #else // _SOFT_SHADOW_IMPROVE
            float y = d * d / (2.0 * lastDist);
            float l = sqrt(d * d - y * y);
            // ret = min(ret, 10.0 * l / max(0.0, dist - y));
            ret = min(ret, LIGHT_SIZE(_LightSize) * l / max(0.0, dist - y));
            lastDist = d;
        #endif
        if (ret < EPSILON_SHADOW || dist > distWithL) {
            break;
        }
        dist += d;
    }
    return saturate(ret);
}
...

Project

GGB

Excel

总结

由于时间关系,先占坑,后面补充说明

References

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值