Shader 3d RayMarching2 阴影

IQ的这篇文章中iquilezles.org/articles/rm... 讲述了他给予raymarching与sdf技术探索一些阴影方法。 使用有符号距离场(Signed Distance Fields,简称SDFs)的众多优势之一是,查询全局信息非常容易, 在经典的rasterizer场景中,每个物体独立渲染,并不知道其他物体的信息,再做全局光照的时候就会非常困难。

硬阴影

在 raymarching 中渲染阴影通常涉及对场景进行额外的采样,以确定光源和表面点之间是否存在遮挡物。最简单直接的方式是硬阴影

如上图所示为了确定一个点是否被阴影遮挡,你可以从该点沿着指向光源的方向进行 raymarching。如果在达到光源之前找到一个遮挡物(即场景中的另一个表面),则该点处于阴影中。转化为代码非常简单

glsl 复制代码
float distanceRayMarch = RayMarch(p, -incidentLight);
if (distanceRayMarch < length(lightPos - p)) diffuseRatio *= .1;

但是运行之后,发现世界都在阴影中 原因是我们的P点离世界的表面距离小于SURFACE_DIST,这个时候只要沿着发现方向稍微挪动一点距离,便可以得到正确的结果

glsl 复制代码
    float distanceRayMarch = RayMarch(p+surfaceNormal*SURFACE_DIST*1.5, -incidentLight);
    if (distanceRayMarch < length(lightPos - p)) diffuseRatio *= .1;

当然我们可以抽象出一个 GetShadow的函数,基本结构就是Raymaching的结构

glsl 复制代码
float GetShadow(vec3 ro, vec3 rd, float mint, float maxt) {
    float t = mint;
    for( int i=0; i<MAX_STEPS && t<maxt; i++ )
    {
        float h = GetDist(ro + rd*t);
        if( h<SURFACE_DIST )
            return 0.0;
        t += h;
    }
    return 1.0;
}

    float shadow = GetShadow(p + SURFACE_DIST * 2.0 * surfaceNormal, -incidentLight, 0.0, distance(p, lightPos));

然而,这种方法产生的阴影边缘是硬的,没有半阴影或软阴影效果。

软阴影 (Shadow Penumbra)

在现实生活中,人看到的阴影通常具有软边缘,其中一个原因是散射与漫反射:当光线照射到一个物体上时,它会在不同方向上散射,这种现象被称为漫反射。在阴影边缘的地方,部分光线仍然能够到达并被物体散射,造成了柔和的渐变效果,而不是锐利的界限。

根据这个物理事实可以假设如果阴影点距离物体表面越近,他应该阴影程度更剧烈,如果距离物体表面越远,阴影程度更加模糊。 也就是在每一次求distance的时候,记录一下dinstance, 得到最小的dinstance之后,如果这个distance越小 阴影因子越大. 同时引入系数K,可以进行软阴影程度的调整

glsl 复制代码
float GetShadow(vec3 ro, vec3 rd, float mint, float maxt, float k) {
	float res = 1.0;
    float t = mint;
    for( int i=0; i<MAX_STEPS && t<maxt; i++ )
    {
        float h = GetDist(ro + rd*t);
        if( h<SURFACE_DIST )
            return 0.0;
		res = min( res, k*h/t );
        t += h;
    }
    return res;
}

当k=8便可以获得上面的半影效果,感觉非常不错啊。

IQ的两次优化

IQ文章后面有提到了两个优化的点

  1. 一个是解决尖角漏光的问题,基本思路是不仅 raymarching距离与表面距离作为阴影参数,还通过三角测量法将 表面到ray的距离作为考虑。代码如下
glsl 复制代码
float softshadow( in vec3 ro, in vec3 rd, float mint, float maxt, float w )
{
    float res = 1.0;
    float t = mint;
    for( int i=0; i<256 && t<maxt; i++ )
    {
        float h = map(ro + t*rd);
        res = min( res, h/(w*t) );
        t += clamp(h, 0.005, 0.50);
        if( res<-1.0 || t>maxt ) break;
    }
    res = max(res,-1.0);
    return 0.25*(1.0+res)*(1.0+res)*(2.0-res);
}
  1. 另外一个是通过将raymacher穿过物体以找到最深的影子。raymarch的中值条件变成了是否阴影够深,另外clamp函数用的也非常有意思, 避免过小导致无限循环,避免负数导致ray回退,避免过大错过细节,同时最后返回值也很讲究,用了一个非线性的函数.
ini 复制代码
float softshadow( in vec3 ro, in vec3 rd, float mint, float maxt, float w )
{
    float res = 1.0;
    float t = mint;
    for( int i=0; i<256 && t<maxt; i++ )
    {
        float h = map(ro + t*rd);
        res = min( res, h/(w*t) );
        t += clamp(h, 0.005, 0.50);
        if( res<-1.0 || t>maxt ) break;
    }
    res = max(res,-1.0);
    return 0.25*(1.0+res)*(1.0+res)*(2.0-res);
}
相关推荐
Jmyd012316 小时前
WebGL 三维党史馆怎么扛住 720 度全景与移动端弱网?
webgl·三维数字化
limit00753 天前
三维 WebGIS 工具选型:加载、样式、分析、交付,一套界面能做完什么
android·前端·web3·安卓·webgl
总要学点什么4 天前
第79篇:数据可视化-模拟迁徙效果——把三十条航线画成会飞的弧
3d·信息可视化·数据挖掘·数据分析·gis·webgl·cesium
limit00754 天前
三维模型悬空与切片偏移的排查顺序
低代码·3d·arcgis·web3·安卓·webgl
Sakura_j_h_5 天前
Three.js 单文件离线机械臂工作台:爆炸图、透明外壳与剖面
前端·机械臂·webgl·three.js·1024程序员节·esbuild
总要学点什么11 天前
第65篇:地图卷帘控件——点「关闭卷帘」,消失的却是右边那半张地图
信息可视化·gis·webgl·cesium·空间分析
总要学点什么12 天前
第63篇:3DTiles-大雁塔模型——190 块瓦片不是八叉树,抬高的却是球心那条法线
3d·数据挖掘·数据分析·gis·webgl
Jmyd012316 天前
3D数字协同设计实训空间技术架构解析:从三维采集到WebGL虚拟展示的全链路实现
3d·架构·webgl·虚拟实训
总要学点什么16 天前
第57篇:三维场景演示_基础版——同一秒,播过去和拖过去差出一千多米
3d·数据挖掘·数据分析·gis·webgl
总要学点什么17 天前
第55篇:空间分析-剖面分析——红线走大圆,剖面却按经纬直线采样
前端框架·gis·webgl·cesium·空间分析