情况如下。我试图在glsl着色器中实现线性体素搜索,以实现高效的体素光线跟踪。在toehr的话,我有一个3D纹理,我在它上面进行光线追踪,但我试图光线追踪,这样我只能检查一次与光线相交的体素。
为此,我编写了一个程序,其中包含以下结果:
效率不高但是正确:
通过多次添加小ε射线并在每次迭代中从纹理采样来获得上述图像。这会产生正确的结果,但效率非常低。
这看起来像是:
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;
}
答案 0 :(得分:3)
+/-
四舍五入,我在你之前关于此问题的某些地方的评论中谈到了这个问题。您需要做的是在其中一个轴上使用等于网格大小的步长(对|dx|=1
测试3次,然后对|dy|=1
测试3次,最后|dz|=1)
。
此外,您应该在地图中创建调试绘制 2D 切片,以实际查看单个特定测试光线的命中位置。现在根据每个轴的光线方向,分别设置舍入规则。如果没有这个,你只是盲目修补一个案子并腐蚀其他两个......
现在实际看看这个(我之前把它链接到了你,但你显然没有):
特别注意:
在右侧它显示了如何计算射线步骤(你的epsilon)。您只需缩放光线方向,使其中一个坐标为+/-1
。为简单起见,请从地图开始使用2D切片。红点是射线起始位置。绿色是垂直网格线命中的光线步长向量,红色用于水平网格线命中(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个案例中的每一个使用不同的舍入/地板规则。这样你就可以处理案件而不会破坏另一个案件。舍入规则取决于许多内容,例如您的坐标系如何与(0,0,0)
相关联,因此代码可能会有所不同,但if
条件应该相同。另外正如您所看到的那样,我通过稍微偏移光线开始位置而不是在光线遍历循环castray
内具有这些条件来处理此问题。
那个宏投射光线并寻找与网格的交点,并在其上面实际上对交叉点进行分类并使用第一个有效的交叉点(这是l,ll
所针对的,并且不需要其他条件或光线结果的组合)。所以我处理这个问题的方法是为每个类型的交叉点(x
,y
,z
)投射射线,从与同一轴的网格的第一个交叉点开始。您需要考虑起始偏移量,因此l,ll
类似于到实际射线开始的交叉点距离,而不是偏离一个...
另外一个好主意是首先在 CPU 方面执行此操作,而在 GLSL 中100%正常工作端口 GLSL 非常困难调试这样的事情。