如何处理离散光线追踪的不正确索引计算?

How to handle incorrect index calculation for discretized ray tracing?

情况如下。我正在尝试在 glsl 着色器中实现线性体素搜索,以实现高效的体素光线追踪。用 toehr 的话来说,我有一个 3D 纹理,我在上面进行光线追踪,但我正在尝试进行光线追踪,这样我只检查一次与光线相交的体素。

为此,我编写了一个程序,结果如下:

效率不高但正确:

上图是通过多次添加一个小的epsilon射线并在每次迭代时从纹理中采样获得的。这会产生正确的结果,但效率很低。

看起来像:

loop{
     start += direction*0.01;
     sample(start);
}

为了提高效率,我决定改为实现以下查找函数:

float bound(float val)
{
    if(val >= 0)
        return voxel_size;
    return 0;
}

float planeIntersection(vec3 ray, vec3 origin, vec3 n, vec3 q)
{
    n = normalize(n);
    if(dot(ray,n)!=0)
        return (dot(q,n)-dot(n,origin))/dot(ray,n);

    return -1;
}

vec3 get_voxel(vec3 start, vec3 direction)
{
    direction = normalize(direction);

    vec3 discretized_pos = ivec3((start*1.f/(voxel_size))) * voxel_size;

    vec3 n_x = vec3(sign(direction.x), 0,0);
    vec3 n_y = vec3(0, sign(direction.y),0);    
    vec3 n_z = vec3(0, 0,sign(direction.z));

    float bound_x, bound_y, bound_z;

    bound_x = bound(direction.x);
    bound_y = bound(direction.y);
    bound_z = bound(direction.z);

    float t_x, t_y, t_z;

    t_x = planeIntersection(direction, start, n_x, 
        discretized_pos+vec3(bound_x,0,0));

    t_y = planeIntersection(direction, start, n_y, 
        discretized_pos+vec3(0,bound_y,0));

    t_z = planeIntersection(direction, start, n_z, 
        discretized_pos+vec3(0,0,bound_z));

    if(t_x < 0)
        t_x = 1.f/0.f;
    if(t_y < 0)
        t_y = 1.f/0.f;
    if(t_z < 0)
        t_z = 1.f/0.f;

    float t = min(t_x, t_y);
    t = min(t, t_z);

    return start + direction*t;
}

产生以下结果:

请注意某些表面左侧的三角形锯齿。

出现这种混叠似乎是因为某些坐标未设置为其正确的体素。

例如修改截断部分如下:

vec3 discretized_pos = ivec3((start*1.f/(voxel_size)) - vec3(0.1)) * voxel_size;

创造:

因此它解决了某些表面的问题并导致了其他表面的问题。

我想知道是否有一种方法可以更正此截断,以免发生此错误。

更新:

我已经缩小了问题范围。观察下图:

数字代表我希望访问这些框的顺序。

如您所见,对于某些点,第五个框的采样似乎被省略了。

以下为示例代码:

vec4 grabVoxel(vec3 pos)
{

    pos *= 1.f/base_voxel_size;

    pos.x /= (width-1);
    pos.y /= (depth-1);
    pos.z /= (height-1);
    vec4 voxelVal = texture(voxel_map, pos);

    return voxelVal;
}

是的,这就是我在您之前与此相关的问题中的评论中所说的 +/- 舍入。您需要做的是在其中一个轴上设置等于网格大小的步长(并且 |dx|=1 然后 |dy|=1 测试 3 次,最后 |dz|=1).

您还应该创建一个调试绘图 2D 切入您的地图以实际查看单个特定测试射线的命中位置。现在根据每个轴中的光线方向,您分别设置舍入规则。没有这个你只是盲目地修补一个案例并破坏其他两个......

现在实际看看这个(我把它链接到你之前但你显然没有):

特别注意:

在右侧,它向您展示了如何计算射线步长(您的 epsilon)。您只需缩放光线方向,使其中一个坐标为 +/-1。为简单起见,从地图的二维切片开始。红点是射线起始位置。绿色是垂直网格线命中的光线步长矢量,红色是水平网格线命中(z 将在类比上相同)。

现在您应该通过一些可见的高度切片(如左图)添加地图的 2D 概览图,向检测到的每个交叉点添加一个点或标记,但区分 x、y 和 z 命中颜色。仅对单条光线执行此操作(我使用视线中心)。当您查看 X+ 方向而不是 X- 时,拳头手柄视图,完成后移动到 Y,Z ...

在我的 GLSL volumetric 3D back raytracer 中,在查看这些行之前我还链接了您:

if (dir.x<0.0) { p+=dir*(((floor(p.x*n)-_zero)*_n)-ray_pos.x)/dir.x; nnor=vec3(+1.0,0.0,0.0); }
if (dir.x>0.0) { p+=dir*((( ceil(p.x*n)+_zero)*_n)-ray_pos.x)/dir.x; nnor=vec3(-1.0,0.0,0.0); }

if (dir.y<0.0) { p+=dir*(((floor(p.y*n)-_zero)*_n)-ray_pos.y)/dir.y; nnor=vec3(0.0,+1.0,0.0); }
if (dir.y>0.0) { p+=dir*((( ceil(p.y*n)+_zero)*_n)-ray_pos.y)/dir.y; nnor=vec3(0.0,-1.0,0.0); }

if (dir.z<0.0) { p+=dir*(((floor(p.z*n)-_zero)*_n)-ray_pos.z)/dir.z; nnor=vec3(0.0,0.0,+1.0); }
if (dir.z>0.0) { p+=dir*((( ceil(p.z*n)+_zero)*_n)-ray_pos.z)/dir.z; nnor=vec3(0.0,0.0,-1.0); }

我就是这样做的。如您所见,我对 6 种情况中的每一种都使用了不同的 rounding/flooring 规则。这样你就可以在不破坏另一个的情况下处理案件。舍入规则取决于很多东西,比如你的坐标系如何偏移到 (0,0,0) 等等,所以它可能在你的代码中有所不同,但 if 条件应该是相同的。同样如您所见,我通过稍微偏移光线起始位置来处理此问题,而不是在光线遍历循环 castray.

中设置这些条件

该宏投射光线并寻找与网格的交点,并在此基础上实际对交点进行 zsort 并使用第一个有效的(这就是 l,ll 的用途,没有其他条件或光线结果的组合需要)。因此,我的处理方法是从与同一轴的网格的第一个交点开始,为每种类型的交点 (xyz) 投射光线。您需要考虑起始偏移量,因此 l,ll 类似于到射线实际起点的相交距离,而不是偏移一个 ...

另外一个好主意是首先在 CPU 侧执行此操作,当 100% 工作端口到 GLSL 时在 GLSL 中很难调试这样的东西。