后处理之SDF与Ray Marching(射线步进)

文章介绍了SDF(有向距离场)的概念,它是计算点到目标图形距离的方法,用于RayMarching(一种模拟点在射线方向上行进并检测碰撞的技术)。通过SDF和RayMarching的结合,可以实现不规则形状的图形渲染。文中还探讨了如何处理精度和效率的问题,并提供了代码示例来解释计算碰撞点的法线和着色的过程,以创建更真实的图像效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、相关概念

SDF,百度下就知道它的名字,有向距离场。光从字面可能理解不了,如果我要说,它表示的是一种计算任意点到目标图形距离的方法,我想应该就都明了了。为何称为有向呢?由于点与物体关系无非就三种情况,在物体内,在物体表面,在物体外,有向即是通过这个距离对三种情况都能区别的表现。

Ray Marching,是模拟点在指定射线方向上不断行进,配合SDF检测碰撞的一种方法,具体接下来详细介绍。

二、具体原理解剖

1.SDF

对于计算任意点到某个物体的距离,目前针对更多的是一些规则图形,可参考iq大神的文章:

https://iquilezles.org/articles/distfunctions/

对于不规则的形状怎么办呢?我通过观察iq大神做的一些不规则物体的绘制,进行拆分,发现其创造不规则图形的方式其实是通过一些规则图形的叠加,任意两个相交的图形经过取并集都能形成一个不规则的图形,只是混合比例不一样罢了,万物的构成皆是如此。其原理推导和演示可在这里找到:

https://iquilezles.org/articles/smin/

2.Ray Marching

由于SDF得到的是点到物体的距离,所以Ray Marching做的也就是提供点了,不断地往前探,提供点去进行距离比对,如果小于或等于0,就说明当前点碰到了物体。那么有的同学就要说了,即使碰到了点又能怎样?碰到了点,我们可以着色啊!我们如果将片段着色器里的坐标作为射线方向的两个分量,那么做的工作其实是将物体投射到一个面上罢了,因此我们对它进行着色,如果同一个色,那么表现的就是一个带有颜色的影子。根据肉眼看东西的特点,我们看到的都是一个面,只是颜色不一导致的空间感罢了。因此对这个影子加入光照进行着色,他就会显得真实生动了,这就是Ray Marching成像的原理。

根据对Ray Marching方法的分析,需要给定一个最大距离和最小精度,理想上是这么干的,通过最小精度去递增,不断得到点,然后进行SDF比对。但是会有一个问题,精度过大或者过小都不行啊,过大,检测的距离不准确,可能错过,过小,检测次数过多,性能耗不起啊。

所以大神给的方法里面也考虑了这个问题,具体是这么干的:从起点就开始SDF检测距离,得到一个距离值,下一次直接步进这个距离值再进行检测,这样就省了距离值的步进次数,更快得到结果。那么有些同学又要问了,既然第一次能测到距离,我直接返回这个距离不就好了,为何要继续检测呢?起初,我也觉得纳闷,于是乎,我去尝试了下,会得到糊在一起的图像,就像这个:

边界很不清晰,大小远近甚至都有些不对,于是我开始将检测次数调大,比如调到128,我得到了这样的结果:

这才是正常的效果,差别实在是太大了。这是为什么呢?

因为代码中每次加的距离值都是上一次点到物体的最近距离,其方向与步进方向一般是不同的,因此每次测到的距离比实际距离基本都要小,所以只能通过多次迭代去无限接近,次数越多,误差越小。

3.计算碰撞点的法线

像前面所说,步进碰撞后得到的点的集合其实只是个影子范围,要进行着色才能更生动,而着色要模拟物体表面的样子,才能看出物体的轮廓,否则单色的话就只有个带颜色的影子了。我们之所以能看到物体有不同的轮廓,其实是其表面不平导致,因为表面不平,法线就不一样,环境光反射的颜色也就不一样了。因此这里要计算法线,通过法线控制每个位置的颜色强度以实现轮廓色差。对于法线的计算方法,我也是参考的iq大神的,这里有他的详细推导过程:

https://iquilezles.org/articles/normalsSDF/

三、代码实现

#version 430 core
#define PI 3.14159265359
precision highp float;

uniform sampler2D uTexture1;

in vec2 vTexPosition1;
out vec4 fragColor;

float sdCapsule( vec3 p, vec3 a, vec3 b, float r )
{
  vec3 pa = p - a, ba = b - a;
  float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
  return length( pa - ba*h ) - r;
}

float map(vec3 pos)
{
    //胶囊体与平面取并集
    return min(pos.y - 0.3,sdCapsule(pos, vec3(0.0,0.0,0.0),vec3(1.0,0.8,0.5),0.2));
}
float RayMarch(vec3 ro, vec3 rd)
{
    float Max_Steps = 128.0; //迭代次数
    float Max_Dist = 10.0; //最大步进距离
    float Surf_Dist = 0.001; //精度
    float d0 = 0.;
    for(int i = 0; i < Max_Steps; i++)
    {
        vec3 p = ro + rd*d0;
        float ds = map(p);
        d0+=ds;
        if(d0>Max_Dist || ds < Surf_Dist) 
            break;
    }
    if( d0>Max_Dist ) d0=-1.0;            
    return d0;     
}
vec3 calcNormal( in vec3 pos )
{
    vec2 e = vec2(1.0,-1.0)*0.5773;
    const float eps = 0.0005;
    return normalize( e.xyy*map( pos + e.xyy*eps ) + 
                      e.yyx*map( pos + e.yyx*eps ) + 
                      e.yxy*map( pos + e.yxy*eps ) + 
                      e.xxx*map( pos + e.xxx*eps ) );
}
void main()
{
    vec2 textSize = textureSize(uTexture1,0);
    vec4 nColor = vec4(0.0);
    vec2 vpos = vTexPosition1 - 0.5;
    float rate = textSize.y/textSize.x; //考虑视图尺寸
    vec3 ro = vec3(0.0,1.0,5.0); //视点位置
    float s_angXZ = PI / 6.0*vtime; //设置随时间转动角度
    mat3 rotaXZ = mat3(cos(s_angXZ),0.0,sin(s_angXZ),
                        0.0,1.0,0.0,
                       -sin(s_angXZ),0.0,cos(s_angXZ));
    ro *= rotaXZ; //绕y方向旋转
    vec3 lookat = vec3(0.0); //相机看向原点
    vec3 forward = normalize(lookat - ro); //相机朝向
    vec3 right = normalize( cross(forward,vec3(0.0,1.0,0.0)));//相机右位置
    vec3 up = normalize( cross(right,forward)); //相机上位置

    vec3 rd = normalize(vpos.x*right+vpos.y*rate*up+forward); //根据屏幕坐标设置步进方向
    float dist = RayMarch(ro,rd); //步进

    vec3 col = vec3(0.0);
    if(dist > -0.5) //根据需要过滤
    {
        vec3 pos = ro + dist*rd; //获得物体表面位置
        vec3 nor = calcNormal(pos); //计算法线
        float amb = clamp( 0.5+0.5*nor.y, 0.0, 1.0 ); //光方向与法线方向点积计算强度
        col = vec3(0.0,0.3,0.3)*amb;
    }
    col = pow(col, vec3(0.4545)); //伽马矫正
    nColor.rgb = col;
    FragColor = nColor;
}

四、结尾

这里以SDF开始介绍,实际上是因为我学习过程中先接触的SDF,其实很多大佬都用射线求交的方式了,SDF更多用做多个图形混合了。Ray Marching很多人习惯地翻译为光线步进,更多是因为它后面还可以用来做阴影,反射等效果,而那才是通过光的方向步进的,本文还没有做那些,姑且翻译为射线步进吧。希望有大佬看到不吝指点一下!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值