我正在寻找一种良好的光线八叉树交叉算法,它以迭代的方式为我提供光线穿过的叶子。我打算在CPU上实现它,因为我还不想潜入CUDA:)
目前,我的Voxel raycaster只在XxYxZ体素的非分层阵列上进行3D DDA(Amanatides / Woo版本)。您可以想象,当存在大量空白空间时,这是非常昂贵的,如下图所示(更亮的红色=更多工作:) :):
我已经发现这项任务有两种算法:自下而上,从叶子向上运行,自上而下 ,这是基本的深度优先搜索。
我已经从2000年发现了Revelles的算法,名为 An efficient parametric algorithm for octree traversal ,看起来很有趣,但已经很老了。这是一种自上而下的算法。
最受欢迎的自下而上的方法似乎是 K.用于射线追踪的DDA八叉树遍历算法,Eurographics'91,North Holland-Elsevier,ISBN 0444 89096 3,p。 73-85。问题是大多数DDA八叉树遍历算法都希望八叉树具有相同的深度,这是我不想要的 - 空子树应该只是一个空指针或类似的东西。
在最近关于Sparse Voxel Octrees的文献中,我设法通读了(最值得注意的是Laine's work on SVO's,它们似乎都基于某种GPU实现的DDA版本(Amanatides / Woo风格) )。
现在,这是我的问题:有没有人有任何实现基本的,没有多余的光线八叉树交叉算法的经验?你会推荐什么?
答案 0 :(得分:10)
为了记录,这是我最终使用的Revelles论文的实现:
#include "octree_traversal.h"
using namespace std;
unsigned char a; // because an unsigned char is 8 bits
int first_node(double tx0, double ty0, double tz0, double txm, double tym, double tzm){
unsigned char answer = 0; // initialize to 00000000
// select the entry plane and set bits
if(tx0 > ty0){
if(tx0 > tz0){ // PLANE YZ
if(tym < tx0) answer|=2; // set bit at position 1
if(tzm < tx0) answer|=1; // set bit at position 0
return (int) answer;
}
}
else {
if(ty0 > tz0){ // PLANE XZ
if(txm < ty0) answer|=4; // set bit at position 2
if(tzm < ty0) answer|=1; // set bit at position 0
return (int) answer;
}
}
// PLANE XY
if(txm < tz0) answer|=4; // set bit at position 2
if(tym < tz0) answer|=2; // set bit at position 1
return (int) answer;
}
int new_node(double txm, int x, double tym, int y, double tzm, int z){
if(txm < tym){
if(txm < tzm){return x;} // YZ plane
}
else{
if(tym < tzm){return y;} // XZ plane
}
return z; // XY plane;
}
void proc_subtree (double tx0, double ty0, double tz0, double tx1, double ty1, double tz1, Node* node){
float txm, tym, tzm;
int currNode;
if(tx1 < 0 || ty1 < 0 || tz1 < 0) return;
if(node->terminal){
cout << "Reached leaf node " << node->debug_ID << endl;
return;
}
else{ cout << "Reached node " << node->debug_ID << endl;}
txm = 0.5*(tx0 + tx1);
tym = 0.5*(ty0 + ty1);
tzm = 0.5*(tz0 + tz1);
currNode = first_node(tx0,ty0,tz0,txm,tym,tzm);
do{
switch (currNode)
{
case 0: {
proc_subtree(tx0,ty0,tz0,txm,tym,tzm,node->children[a]);
currNode = new_node(txm,4,tym,2,tzm,1);
break;}
case 1: {
proc_subtree(tx0,ty0,tzm,txm,tym,tz1,node->children[1^a]);
currNode = new_node(txm,5,tym,3,tz1,8);
break;}
case 2: {
proc_subtree(tx0,tym,tz0,txm,ty1,tzm,node->children[2^a]);
currNode = new_node(txm,6,ty1,8,tzm,3);
break;}
case 3: {
proc_subtree(tx0,tym,tzm,txm,ty1,tz1,node->children[3^a]);
currNode = new_node(txm,7,ty1,8,tz1,8);
break;}
case 4: {
proc_subtree(txm,ty0,tz0,tx1,tym,tzm,node->children[4^a]);
currNode = new_node(tx1,8,tym,6,tzm,5);
break;}
case 5: {
proc_subtree(txm,ty0,tzm,tx1,tym,tz1,node->children[5^a]);
currNode = new_node(tx1,8,tym,7,tz1,8);
break;}
case 6: {
proc_subtree(txm,tym,tz0,tx1,ty1,tzm,node->children[6^a]);
currNode = new_node(tx1,8,ty1,8,tzm,7);
break;}
case 7: {
proc_subtree(txm,tym,tzm,tx1,ty1,tz1,node->children[7^a]);
currNode = 8;
break;}
}
} while (currNode<8);
}
void ray_octree_traversal(Octree* octree, Ray ray){
a = 0;
// fixes for rays with negative direction
if(ray.direction[0] < 0){
ray.origin[0] = octree->size[0] - ray.origin[0];
ray.direction[0] = - ray.direction[0];
a |= 4 ; //bitwise OR (latest bits are XYZ)
}
if(ray.direction[1] < 0){
ray.origin[1] = octree->size[1] - ray.origin[1];
ray.direction[1] = - ray.direction[1];
a |= 2 ;
}
if(ray.direction[2] < 0){
ray.origin[2] = octree->size[2] - ray.origin[2];
ray.direction[2] = - ray.direction[2];
a |= 1 ;
}
double divx = 1 / ray.direction[0]; // IEEE stability fix
double divy = 1 / ray.direction[1];
double divz = 1 / ray.direction[2];
double tx0 = (octree->min[0] - ray.origin[0]) * divx;
double tx1 = (octree->max[0] - ray.origin[0]) * divx;
double ty0 = (octree->min[1] - ray.origin[1]) * divy;
double ty1 = (octree->max[1] - ray.origin[1]) * divy;
double tz0 = (octree->min[2] - ray.origin[2]) * divz;
double tz1 = (octree->max[2] - ray.origin[2]) * divz;
if( max(max(tx0,ty0),tz0) < min(min(tx1,ty1),tz1) ){
proc_subtree(tx0,ty0,tz0,tx1,ty1,tz1,octree->root);
}
}
答案 1 :(得分:1)
自上而下对我很有用;八叉树的上半部分可能是基于指针的,所以大的空子体积不占用记忆;下半部分更有效地实现无指针...触及墙的时间复杂度是log2(N)(显然是最好的情况)。递归实现非常简单,因此更容易优化代码。所有数学都可以通过整数SSE操作有效实现 - 它需要大约x30个CPU周期来计算每个子体积跳跃的新XYZ坐标。顺便说一下,八叉树遍历的公共版本仅适用于教育 - 掌握真正有效的实施可能需要几个月......
的Stefan