如何处理离散光线追踪的不正确索引计算?
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
的用途,没有其他条件或光线结果的组合需要)。因此,我的处理方法是从与同一轴的网格的第一个交点开始,为每种类型的交点 (x
、y
、z
) 投射光线。您需要考虑起始偏移量,因此 l,ll
类似于到射线实际起点的相交距离,而不是偏移一个 ...
另外一个好主意是首先在 CPU 侧执行此操作,当 100% 工作端口到 GLSL 时在 GLSL 中很难调试这样的东西。
情况如下。我正在尝试在 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
的用途,没有其他条件或光线结果的组合需要)。因此,我的处理方法是从与同一轴的网格的第一个交点开始,为每种类型的交点 (x
、y
、z
) 投射光线。您需要考虑起始偏移量,因此 l,ll
类似于到射线实际起点的相交距离,而不是偏移一个 ...
另外一个好主意是首先在 CPU 侧执行此操作,当 100% 工作端口到 GLSL 时在 GLSL 中很难调试这样的东西。