Ray Marching: 从基础到实践

发布时间:2023-05-19

Ray Marching是一种基于射线追踪的图形渲染算法。与传统的光栅化算法不同,Ray Marching可以处理一些非常复杂的几何形状,并且具有自适应性、灵活性等优点。

一、基础知识

Ray Marching的核心概念在于射线与物体的交点,可以运用向量运算求出这个交点。对于某一条射线,我们可以将其起点设为摄像机位置,方向可以通过计算像素所对应的角度获得,然后使用向量运算,不断迭代计算,直到找到这条射线与物体交点的位置。 Ray Marching的精髓在于,它可以判断当前位置是否在物体内部。若在,则可以通过某一种函数迭代求解,得到距离交点最近的表面。这种方法称作 Signed Distance Function(SDF),即符号距离函数。SDF的定义方式与物体形状有关。例如,对于球体,其SDF的定义为:

float sdSphere(vec3 p, float r)
{
    return length(p) - r;
}

其中p为当前点,r为球体半径。如果p在球体内部,则返回值为负数,如果p在球体外部,则返回值为正数。

二、进阶实践

使用SDF,我们可以绘制出一些简单的物体,但是如果要绘制更加复杂的形状,则需要进行组合。这可以通过一些基本的空间变换来实现。

1. 平移变换

平移变换可以通过将SDF中的坐标进行平移来实现。例如,如果我们要将一个物体沿着x轴向右平移m个单位,只需在SDF中将x坐标减去m即可。

float sdTranslatedSphere(vec3 p, vec3 pos, float r)
{
    return sdSphere(p - pos, r);
}

其中,pos为平移向量。

2. 旋转变换

旋转变换可以通过将SDF中的坐标进行旋转来实现。例如,如果我们要将一个物体绕着x轴旋转a角度,只需在SDF中先绕着x轴旋转-a角度,再计算。

float sdRotatedSphere(vec3 p, vec3 axis, float angle, float r)
{
    p = rotate(p, axis, -angle);
    return sdSphere(p, r);
}

其中,axis为旋转轴,angle为旋转角度。

3. 组合变换

组合变换可以通过将多个变换结合起来实现。例如,如果我们要将一个物体旋转b角度,并且沿着y轴平移m个单位,只需先进行旋转变换,再进行平移变换。

float sdTransformedSphere(vec3 p, vec3 pos, vec3 axis, float angle, float r)
{
    p = rotate(p - pos, axis, -angle) + pos;
    return sdSphere(p, r);
}

三、实战演练

现在我们来演示一个用Ray Marching绘制的3D场景:一个隧道内的光影效果。 首先我们需要生成一个隧道的SDF函数:

float sdTunnel(vec3 p, float r)
{
    float d1 = length(p.xy) - r;
    float d2 = abs(p.z) - r;
    vec2 d = vec2(d1, d2);
    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}

其中,p为当前点,r为隧道半径。 然后我们需要定义一个灯光的SDF函数:

float sdLight(vec3 p)
{
    float d1 = length(p) - 0.2;
    float d2 = abs(p.z) - 0.2;
    vec2 d = vec2(d1, d2);
    return length(max(-d, 0.0)) + min(max(d.x, d.y), 0.0);
}

将这两个SDF函数结合起来,就可以绘制出隧道内的光影效果了。

vec3 marchRay(vec3 ro, vec3 rd)
{
    float t = 0.0;
    for(int i = 0; i < MAX_STEPS; i++)
    {
        vec3 p = ro + rd * t;
        float d = sdTunnel(p, 1.0);
        if(d < EPSILON)
        {
            vec3 lightPos = vec3(10.0, 0.0, 0.0);
            vec3 lightDir = normalize(lightPos - p);
            float diffuse = max(dot(rd, lightDir), 0.0);
            vec3 color = vec3(1.0, 1.0, 0.0) * diffuse;
            float dLight = sdLight(p - lightPos);
            if(dLight < EPSILON)
            {
                color += vec3(1.0, 1.0, 1.0) * pow(1.0 - dLight, 2.0);
            }
            return color;
        }
        t += d * 0.5;
        if(t > MAX_DISTANCE) break;
    }
    return vec3(0.0);
}

其中,MAX_STEPS为光线最大迭代次数,EPSILON为一个极小值,MAX_DISTANCE为光线的最大距离。

四、总结

Ray Marching是一种非常有趣的图形渲染算法。通过SDF函数和空间变换,我们可以轻松绘制出各种形状。当然,在实际应用中,我们还需要考虑性能,例如使用GPU进行加速等。