如何处理离散化光线追踪中的错误索引计算?

3
以下是需要翻译的内容:

情况如下。我正在尝试在GLSL着色器中实现线性体素搜索,以实现高效的体素光线追踪。换句话说,我有一个3D纹理,我正在对其进行光线追踪,但我正在尝试进行光线追踪,以便仅检查光线只相交一次的体素。

为此,我编写了一个程序,具有以下结果:

不够高效,但正确:

enter image description here

上面的图像是通过多次添加小的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;
}

这将产生以下结果:

enter image description here

请注意一些表面左侧的三角形走样。
似乎这种走样是因为某些坐标没有被设置为它们正确的体素。
例如,将截断部分修改如下:
vec3 discretized_pos = ivec3((start*1.f/(voxel_size)) - vec3(0.1)) * voxel_size;

创建:

enter image description here

因此,它已经解决了一些表面的问题,并引起了其他表面的问题。

我想知道是否有一种方法可以纠正这种截断,以便不会发生此错误。

更新:

我已经将问题缩小了一点。请观察以下图像:

enter image description here

这些数字代表着我期望盒子被访问的顺序。
正如您所看到的,对于一些点来说,第五个盒子的采样似乎被省略了。
以下是采样代码:
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;
}
1个回答

3
我之前在你的问题评论中提到的是加减取整问题。你需要做的是在一个轴上将步长设为网格大小(并测试三次,一次为|dx|=1,另一次为|dy|=1,最后一次为|dz|=1)。
此外,您应该创建一个调试绘制2D切片来查看单个特定测试光线的命中位置。现在根据每个轴上的光线方向单独设置舍入规则。如果没有这样做,您只是盲目修补一个情况,而破坏了其他两种情况...
现在实际看看这个(我之前链接过它给你,但显然你没有看): 尤其要注意:

img

右边显示了如何计算光线步长(您的epsilon)。您只需缩放光线方向,使其中一个坐标为+/-1即可。为简单起见,请从通过地图的2D切片开始。红点是光线起始位置。对于垂直网格线命中,绿色是光线步进向量,对于水平网格线命中,红色是光线步进向量(z将同样如此)。
现在,您应该添加通过某个可见的高度切片的2D地图概述(如左侧图像),对每个检测到的交点添加一个点或标记,但要通过颜色区分x、y和z的命中。仅对单个射线进行此操作(我使用视图射线的中心)。首先处理观看X+方向,然后是X-,完成后移动到Y,Z...
在我之前链接过的GLSL体积三维背向跟踪器中,请查看以下行:
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); }

这是我完成这个任务的方法。你可以看到,我对于每一种情况都使用了不同的四舍五入/向下取整规则。这样可以处理每个情况而不会影响其他情况。四舍五入规则取决于很多因素,比如你的坐标系相对于(0,0,0)的偏移量等,所以在你的代码中可能会有所不同,但是if条件应该是相同的。此外,你可以看到,我通过稍微偏移光线起始位置来处理这个问题,而不是在射线遍历循环castray内部设置这些条件。
那个宏cast ray并查找与网格的交点,最重要的是对交点进行z排序并使用第一个有效的交点(这就是l、ll的作用,不需要其他条件或射线结果的组合)。因此,我的处理方法是为每种类型的交点(x、y、z)分别投射光线,从同一轴上的网格的第一个交点开始。你需要考虑起始偏移量,使l、ll类似于交点距离真实射线起点的距离,而不是偏移后的距离…
另外一个好主意是先在CPU端完成这个任务,当100%工作时再将其移植到GLSL中,因为在GLSL中调试这样的问题非常困难。

我已经尝试了所有的方法来调试这个问题,但我不知道错在哪里。因为有多种原因,我不能真正像你做的那样实现垂直切片,所以我在孤立地测试算法,并得到了我预期的结果(连接点的线),但是当我试图在着色器中使用该算法时,我遇到了上述的问题。我正在慢慢地疯狂 :p - Makogan
@Makogan,你需要通过将相机围绕某个测试对象移动来进行测试,否则可能会错过一些边缘情况。如果GLSL中的结果与您得到的结果不同,则可能存在精度问题。这可能是许多原因之一,例如夹紧不是GL_CLAMP_TO_EDGEfloat不足(如果使用double可以改善情况)。1.f/0.f;是什么意思?它是除以零,而着色器不像CPU代码那样崩溃。是的,在GLSL中解码可能会让你发疯,这就是为什么我通常会编写一个C++代码(与着色器代码相同),当它工作正常时,只需将其用作着色器。 - Spektre
为此,我编写了一个向量数学库,使用(d/i)vec(2,3,4),并复制了GLSL的行为。这是一段疯狂的模板代码,需要正确处理getter/setter和模板递归,但它确实有效 :) CPU和Shader之间唯一的区别是纹理访问,这由#define和调试渲染处理,因此我甚至不必更改代码。如果没有2D切片概述,我会在我的一些项目中陷入困境。我仍然相信,如果没有2D切片概述,您将无法像完整的3D渲染那样看到实际发生的情况。 - Spektre
@Makogan 另一个选项是从深度缓冲区中提取中间水平线(我会使用线性深度缓冲区来实现这个目的),然后在CPU端从中呈现切片。 - Spektre
我的立方体没有经过光线追踪,只有阴影是这样的。立方体是通过矩阵投影方法普通生成的,这就是为什么切片测试在这里实际上并不起作用。然而,问题似乎出在采样上。在每次迭代中沿着距离添加一个小的正 epsilon 似乎已经解决了这个问题。我会继续测试以确保问题已经消失。 - Makogan
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接