Help with my BIH back

Board: Board index ‹ Ray tracing ‹ !Real time ‹ Off-line

(L) [2009/01/16] [cignox1] [Help with my BIH] Wayback!

Ok, i never wrote acceleration structures. I've read many times the BIH paper and the relevant threads on this forum. I used the code from [LINK http://ompf.org/forum/viewtopic.php?f=4&t=718] as a reference and rewrote it for my renderer.
It renders the image, but it is also slower than simply using a list for the shapes.
Since I don't have a scene loader yet, I'm using a hard coded one. Here I have more or less 20 big triangles and a couple of spheres. So I have a few questions:
-Can big tris be so evil to make a bih slower than a list?
-Should I increase the scene complexity to see any improvement over a list?
I'm currently doing some test, here is the bih construction and traversal related code for those willing to give it a look (it is bad code, I know).
Code: [LINK # Select all]   class BIHNode
   {
   public:
      
      BIHNode* left;
      BIHNode* right;
      int axis;
      union
      {
         unsigned int shapes[2];
         real planes[2];
      };
      enum AXIS {AXIS_X, AXIS_Y, AXIS_Z, AXIS_NONE};
      BIHNode() : left(NULL), right(NULL), axis(BIHNode::AXIS_NONE) {}
      ~BIHNode() { if(left != NULL) delete left; if(right != NULL) delete right; }
   };
   class QLBIHTree : public QLShapesCollection
   {
   private:
      BIHNode *root;
      qlaabox<real> worldbb;
      
      std::list<QLShape*> shapes;
      
      QLShape** shapes_array;
      unsigned int shapes_count;
   public:
      QLBIHTree() : QLShapesCollection("No Name (BIH Tree)") {}
      QLBIHTree(const std::string name) : QLShapesCollection(name)
      {
         shapes_array = NULL;
         shapes_count = 0;
         root = NULL;
      }
   
      //Shape overridden methods
      bool Intersect(const ray<vector4<real>> &r, SurfacePoint &hitdesc) const
      {
         //Check the ray against the world bounding box
         real min_t = -100.0, max_t = 100.0;
         /*if(worldbb.BoundingHit(r, min_t, max_t) == false)
            return false;*/
         //Clip ray bounds
         real ttmin = qlmath::Max (real(0), min_t);
         real ttmax = qlmath::Min(r.maxdistance, max_t);
         return IntersectTree(root, r, -qlmath::Min(ttmin, ttmax), qlmath::Max(ttmin, ttmax), hitdesc);
      }
      void Preprocess()
      {
         shapes_array = new QLShape*[(unsigned int)shapes.size()];
         shapes_count = (unsigned int)shapes.size();
          std::list<QLShape*>::iterator iter;
         int i = 0;
         for (iter = shapes.begin(); iter != shapes.end(); ++iter)
            shapes_array[i++] = *iter;
         worldbb = GetNodeAABB(0, shapes_count - 1);
         root = CreateNode (0, shapes.size() - 1, worldbb.min, worldbb.max, BIHNode::AXIS_X);
      }
   private:
      BIHNode* CreateLeafNode(unsigned int l, unsigned int r)
      {
         BIHNode *newnode = new BIHNode();
         newnode->left = NULL;
         newnode->right = NULL;
         newnode->shapes[0] = l;
         newnode->shapes[1] = r;
         
         newnode->axis = BIHNode::AXIS_NONE;
         return newnode;
      }
      BIHNode* CreateNode(unsigned int l, unsigned int r, const vector4<real> &minValue, const vector4<real> &maxValue, BIHNode::AXIS axis)
      {
         if(shapes.size() == 0)
            return NULL;
         if(l == r)
         {
            return CreateLeafNode(l, r);
         }
         
         unsigned int i;
         unsigned int j;
         bool left_empty = false;
         bool right_empty = false;
         bool left_doesshrik = false;
         bool right_doesshrik = false;
         bool both_balanced = false;
         unsigned int axisToTest = 3;
         real split;
         while(axisToTest--)
         {
            split = EvaluateBounds(l, r, i, j, minValue, maxValue, axis);
            unsigned int left_l = l;
            unsigned int left_r = j;
            unsigned int right_l = i;
            unsigned int right_r = r;
            if (l == j)
            {
               left_empty = true;
            }
            if (left_l > l || left_r < r)
            {
               left_doesshrik = true;
            }
            if (i == r)
            {
               right_empty = true;
            }
            if (right_l > l || right_r < r)
            {
               right_doesshrik = true;
            }
            if (!right_empty && !left_empty)
            {
               both_balanced = true;
            }
            if (both_balanced)
            {
               axisToTest = 0;
            }
            else
            {
               switch (axis)
               {
               case BIHNode::AXIS_X : axis = BIHNode::AXIS_Y; break;
               case BIHNode::AXIS_Y : axis = BIHNode::AXIS_Z; break;
               case BIHNode::AXIS_Z : axis = BIHNode::AXIS_X; break;
               }
            }
         }
         //Build nodes         
            BIHNode *newnode = new BIHNode();
         newnode->axis = axis;
         newnode->right = newnode->left = NULL;
            // NODE LEFT
         {
            qlaabox<real> bbox = GetNodeAABB(l, i);
            newnode->planes[0] = qlmath::Max(bbox.min[axis], bbox.max[axis]); //The left node right border is the plane most right
            if ((!right_empty) && ( left_doesshrik ))
            {
               //Create left node
               vector4<real> left_value = minValue;
               vector4<real> right_value = maxValue;
               right_value[axis] = split;
               BIHNode::AXIS new_axis;
               switch (axis)
               {
               case BIHNode::AXIS_X : new_axis = BIHNode::AXIS_Y; break;
               case BIHNode::AXIS_Y : new_axis = BIHNode::AXIS_Z; break;
               case BIHNode::AXIS_Z : new_axis = BIHNode::AXIS_X; break;
               }
               newnode->left = CreateNode (l, j, left_value, right_value, new_axis); //Recursive call
            }
            else
            {
               //Stoprecursion with all those nodes         
               newnode->left = CreateLeafNode(l, i);
            }
         }
         qlaabox<real> bbox = GetNodeAABB(j, r);
         newnode->planes[1] = qlmath::Min(bbox.min[axis], bbox.max[axis]); //The right node left border is the plane most left
         // NODE RIGHT
         {
            if ((!left_empty) && (right_doesshrik))
            {
               //Create left node
               vector4<real> left_value  = minValue;
               left_value[axis] = split;
               vector4<real> right_value = maxValue;
               BIHNode::AXIS new_axis;
               switch (axis)
               {
               case BIHNode::AXIS_X : new_axis = BIHNode::AXIS_Y; break;
               case BIHNode::AXIS_Y : new_axis = BIHNode::AXIS_Z; break;
               case BIHNode::AXIS_Z : new_axis = BIHNode::AXIS_X; break;
               }
               newnode->right = CreateNode(j, r, left_value, right_value, new_axis);
            }
            else
            {
               newnode->right = CreateLeafNode(j, r);
            }
         }
         return newnode;
      }
      real EvaluateBounds(unsigned int l, unsigned int r, unsigned int &i, unsigned int &j, const vector4<real> &minValue, const vector4<real> maxValue, BIHNode::AXIS axis)
      {
         real split = (maxValue[axis] + minValue[axis]) / real(2);
         SortSubVector(shapes_array, l, r, i, j, split, axis);
         return split;
      }
      //Get the closer intersection found with the shapes in the provided node. Note that node must be a leaf, no check will be performed!
      bool IntersectNode(const BIHNode* node, const ray<vector4<real>> &r, SurfacePoint &hitdesc) const
      {
         bool intersected = false;
         for(int i = node->shapes[0]; i <= node->shapes[1]; ++i)
            intersected = intersected | shapes_array[i]->Intersect(r, hitdesc);
         return intersected;   
      }
      bool IntersectTree(const BIHNode* parent, const ray<vector4<real>> &r, real tmin, real tmax, SurfacePoint &hitdesc) const
      {      
         real left_plane  = (parent->planes[0] - r.origin[parent->axis]) / r.direction[parent->axis]; //Intersection with the left plane
            real right_plane = (parent->planes[1] - r.origin[parent->axis]) / r.direction[parent->axis]; //intersection with the right plane
         BIHNode *near_node = NULL;
         BIHNode *far_node = NULL;
         real near_plane;
         real far_plane;
          if (r.direction[parent->axis] <= real(0))
            {
               near_plane = right_plane;
               far_plane = left_plane;
               near_node = parent->right;
               far_node = parent->left;
            }
            else
            {
               near_plane = left_plane;
               far_plane = right_plane;
               near_node = parent->left;
               far_node = parent->right;
            }
         bool hitfound = false;
         //Check against split planes
         if(near_plane > tmin)
         {
            if(near_node->axis == BIHNode::AXIS_NONE)
            {
               //Is a leaf, test against shapes
               hitfound = IntersectNode(near_node, r, hitdesc);
            }
            else
            {
               //Traverse left tree
               hitfound = IntersectTree(near_node, r, tmin, qlmath::Max(tmax, near_plane), hitdesc);
            }
         }
         if(far_plane < tmax)
         {   
            if(far_node->axis == BIHNode::AXIS_NONE)
            {
               //Is a leaf, test against shapes
               hitfound = hitfound | IntersectNode(far_node, r, hitdesc);
            }
            else
            {
               //Traverse left tree
               hitfound = hitfound | IntersectTree(far_node, r, qlmath::Min(tmin, far_plane), tmax, hitdesc);
            }      
         }
      
         return hitfound;
      }
      /*
      SortSubVector sorts the primitives in the specified range in a quicksort like way. When the function returns, i and j contain the last index
      of the left node and the first index of the right node. Indeces can overlap if shapes crossing both partitions are present.
      */
      void SortSubVector(QLShape** vector, unsigned int l, unsigned int r, unsigned int &i, unsigned int &j, real split, BIHNode::AXIS axis)
      {
         //The following algorithm can be made faster by increasing i and decrasing j in two loops as long as the shapes are correctly placed
         //i.e. while(CompareShape(vector[i], split, axis) < 0) ++i;
         //while(CompareShape(vector[j], split, axis) > 0) --j;
         i = l;
         j = r;
         unsigned int bothi = l + 1; //Indicates the index to swap for the next both shape. Will be incremented with each left and both
         do
         {
            qlaabox<real> ab = vector[i]->GetBoundingBox();
            int res = CompareShape(vector[i], split, axis);
            if(res > 0)
            {
               QLShape* tmp = vector[i];
               vector[i] = vector[j];
               vector[j] = tmp;
               --j;
            }
            else if(res == 0)
            {
               if(bothi > r)
                  break;
               QLShape* tmp = vector[bothi];
               vector[bothi] = vector[i];
               vector[i] = tmp;
               ++bothi;
            }
            else
            {
               ++i;
               ++bothi;
            }
         }while(i < j);
         
         if(j < r) ++j;
         //Change i and j to include 'both' shapes
         while(CompareShape(vector[i], split, axis) == 0 && i < r) ++i;
         while(CompareShape(vector[j], split, axis) == 0 && j > l) --j;
      }
      /*
      GetNodeAABB returns the bounding box of all the shapes in the subvector specified
      */
      const qlaabox<real> GetNodeAABB(unsigned int l, unsigned int r) const
      {
         qlaabox<real> aabb;
         for(int i = l; i <= r; ++i)
            aabb = aabb.Union(shapes_array[i]->GetBoundingBox());
         return aabb;
      }
      /*
      CompareShape checks a Shape against the split plane: it returns -1 if the shape bounding box completely falls left to the plane, 1 if it falls
      completely right to the plane and 0 if it intersect the plane.
      */
      int CompareShape(QLShape* shape, real split, BIHNode::AXIS axis)
      {
         qlaabox<real> aabb = shape->GetBoundingBox();
         real max = Max(aabb.max[axis],aabb.min[axis]);
         real min = Min(aabb.max[axis],aabb.min[axis]);
         return (min > split) ? 1 : (max < split) ? -1 : 0;
      }
   };
(L) [2009/01/16] [Michael77] [Help with my BIH] Wayback!

>> cignox1 wrote:-Can big tris be so evil to make a bih slower than a list?
-Should I increase the scene complexity to see any improvement over a list?
Yes.
Yes.
20 Triangles don´t need a acceleration structure, 20.000 do.
(L) [2009/01/20] [cignox1] [Help with my BIH] Wayback!

OK, I added 3ds support. I load a teapot model made by 2812 triangles (1461 vertices). For that model my tree generates 2688 nodes, it tests each ray against 90-100 nodes, most of the times (80-85) it takes both path (left and right children). Each ray is tested against a number of primitives comparable with the primitives count in the model. Render times are only slightly better than unsing a list (i.e. 38 seconds instead than 42, 1024x768, two point lights, no aa). In addition, a few polygons seem missing from the model when rendererd with the BIH.
I'm investigating these issues, do anyone recognize a common mistake from these numbers or has any idea? I'm trying understand if the error is in the traversal or in the construction code (I fear the construction, given the hight number of nodes). Are there ways to test that?
(L) [2009/01/20] [Shadow007] [Help with my BIH] Wayback!

From a quick glance at the code/results, (so you may want to take that with a grain of salt) I have some comments :
- I see no "early out" in there ie : when you get an intersection in the near node, and the point is closer than the far node, you don't need to intersect with the far one ! That should cut out some traversing [SMILEY :)]
- What is your number of leafs, what is your tree's max depth?
- What is you mean number of primitives in a leaf ? I think you may want to group them...
- Did you try an other splitting heuristic ?
(L) [2009/01/20] [cignox1] [Help with my BIH] Wayback!

Thank you for the help. I'm still studing the algorithm, som I'm not really sure about which is the distance of the far node. I suppose that it is tmax, but adding if(intersectiondistance < tmax) return true; did not help much (thought I'm sure this path is taken).
Here the statistics you asked
-Leaf count: 104
-Node count: 207
-Tree Depth: 20 (a bit high, isn't it?)
-Average primitives in a leaf: 25
Are these common values?
(L) [2009/01/20] [Shadow007] [Help with my BIH] Wayback!

I don't know for sure about the numbers you give (but indeed a max depth of 20 seems quite high):
The algorithm should be (I think)
find out near-far planes / near-far nodes
(distance to the far plane is "far_plane")
if(ray touches the near node (ie tmin < near_plane))
   Intersect with near node. If distance less than previous, record hit point and get the updated hit distance
If (ray touches the far node (ie tmax > far_plane)) and (closest_intersection > far plane), // That one's the early out
   intersect with far node(same as with near child)
BTW you may NOT do that "hitfound = hitfound | IntersectNode" trick with a BIH : the two nodes may overlapp. It can be the source of your missing triangles.
(L) [2009/01/20] [cignox1] [Help with my BIH] Wayback!

Thank you. OK, the '|' trick should be read as '||', for some reason I used binary o  [SMILEY :oops:]  It jsut tells if a primitive has been hit, (in the left, in the right or both). I don't think it should cause problems, but I will check. I did
if(hitfound && hitdesc.distance < far_plane) return true; //Early out
but nothing changed.
I need more testing  [SMILEY :D]
(L) [2009/01/20] [Shadow007] [Help with my BIH] Wayback!

Nope, after more time thinking, your "|'"was right : it disabled the "lazy evaluation", so hit or not hit, the far child was beeing intersected, as it should.
In constrast "hitfound || IntersectNode" doesn't call IntersectNode if hitfound is true, which would have given a bad result.
(L) [2009/01/20] [cignox1] [Help with my BIH] Wayback!

>> Shadow007 wrote:Nope, after more time thinking, your "|'"was right : it disabled the "lazy evaluation", so hit or not hit, the far child was beeing intersected, as it should.
In constrast "hitfound || IntersectNode" doesn't call IntersectNode if hitfound is true, which would have given a bad result.
I forgot lazy evaluation, probably because I associate it with the 'if' statement. Strange, because I already use code similar to this elsewhere without problems. Anyway, I tried forcing the IntersectNode call without changes in the rendered image (or rendering times).
I will do some tests on the build steps, thought I don't really know what to look for or how to recognize errors in the tree.
EDIT:
I just discovered that there is at least one leaf with 497 primitives, and that if I ignore the leaves with more than 50 primitives only a small portion of the teapot is rendered. Don't know if this helps.
(L) [2009/01/23] [cignox1] [Help with my BIH] Wayback!

Ok, I've found several errors in my code (both when building and when traversing the tree). I've solved the polys missing problem among others. As far as I can tell, my tree is highly unbalanced: most of the leaveas are built by only 1 shape (and I suspect that my building code still can't distinguish empty leaves from 1 shape ones), and there are a few nodes with more than 500 primitives. This is the new code for the tree build (I hypotize that the sorter works)
Code: [LINK # Select all]      BIHNode* CreateLeafNode(unsigned int l, unsigned int r)
      {
         std::cout<<r-l<<std::endl;
         BIHNode *newnode = new BIHNode();
         newnode->left = NULL;
         newnode->right = NULL;
         newnode->shapes[0] = l;
         newnode->shapes[1] = r;
         
         newnode->axis = BIHNode::AXIS_NONE;
         return newnode;
      }
      BIHNode* CreateNode(unsigned int l, unsigned int r, const vector4<real> &minValue, const vector4<real> &maxValue, BIHNode::AXIS axis)
      {
         if(shapes.size() == 0)
            return NULL;
         if(l == r)
         {
            return CreateLeafNode(l, r);
         }
         
         unsigned int i;
         unsigned int j;
         bool left_empty = false;
         bool right_empty = false;
         bool left_doesshrik = false;
         bool right_doesshrik = false;
         bool both_balanced = false;
         unsigned int axisToTest = 3;
         real split;
         while(axisToTest--)
         {
            split = EvaluateBounds(l, r, i, j, minValue, maxValue, axis);
            unsigned int left_l = l;
            unsigned int left_r = j;
            unsigned int right_l = i;
            unsigned int right_r = r;
            if (l == j)
            {
               left_empty = true;
            }
            if (left_l > l || left_r < r)
            {
               left_doesshrik = true;
            }
            if (i == r)
            {
               right_empty = true;
            }
            if (right_l > l || right_r < r)
            {
               right_doesshrik = true;
            }
            if (!right_empty && !left_empty)
            {
               both_balanced = true;
            }
            if (both_balanced)
            {
               axisToTest = 0;
            }
            else
            {
               switch (axis)
               {
               case BIHNode::AXIS_X : axis = BIHNode::AXIS_Y; break;
               case BIHNode::AXIS_Y : axis = BIHNode::AXIS_Z; break;
               case BIHNode::AXIS_Z : axis = BIHNode::AXIS_X; break;
               }
            }
         }
         //Build nodes         
            BIHNode *newnode = new BIHNode();
         newnode->axis = axis;
         newnode->right = newnode->left = NULL;
            // NODE LEFT
         {
            qlaabox<real> bbox = GetNodeAABB(l, j);
            newnode->planes[0] = qlmath::Max(bbox.min[axis], bbox.max[axis]); //The left node right border is the plane most right
            if ((!right_empty) && ( left_doesshrik ))
            {
               //Create left node
               vector4<real> left_value = minValue;
               vector4<real> right_value = maxValue;
               right_value[axis] = split;
               BIHNode::AXIS new_axis;
               switch (axis)
               {
               case BIHNode::AXIS_X : new_axis = BIHNode::AXIS_Y; break;
               case BIHNode::AXIS_Y : new_axis = BIHNode::AXIS_Z; break;
               case BIHNode::AXIS_Z : new_axis = BIHNode::AXIS_X; break;
               }
               newnode->left = CreateNode (l, j, left_value, right_value, new_axis); //Recursive call
            }
            else
            {
               //Stop recursion with all those nodes         
               newnode->left = CreateLeafNode(l, j);
            }
         }
         qlaabox<real> bbox = GetNodeAABB(i, r);
         newnode->planes[1] = qlmath::Min(bbox.min[axis], bbox.max[axis]); //The right node left border is the plane most left
         // NODE RIGHT
         {
            if ((!left_empty) && (right_doesshrik))
            {
               //Create left node
               vector4<real> left_value  = minValue;
               left_value[axis] = split;
               vector4<real> right_value = maxValue;
               BIHNode::AXIS new_axis;
               switch (axis)
               {
               case BIHNode::AXIS_X : new_axis = BIHNode::AXIS_Y; break;
               case BIHNode::AXIS_Y : new_axis = BIHNode::AXIS_Z; break;
               case BIHNode::AXIS_Z : new_axis = BIHNode::AXIS_X; break;
               }
               newnode->right = CreateNode(i, r, left_value, right_value, new_axis);
            }
            else
            {
               //Stop recursion with all those nodes         
               newnode->right = CreateLeafNode(i, r);
            }
         }
         return newnode;
      }

Does someone spot anything wrong?
(L) [2009/01/23] [Shadow007] [Help with my BIH] Wayback!

You're right : if for 2000+ triangles you have more than 4 nodes wit 500+ triangles in them there REALLY is a BIG problem in your tree !
If it were constructed correctly, I'd guess you should have most have 10-20 triangles in a leaf.
And since I think that in the Teapot there should be no "overlapping/crossing" triangles, it should even be closer to 1 ...
(L) [2009/01/23] [cignox1] [Help with my BIH] Wayback!

I've discovered a little error while computing a node aabb wich always wrong results. Now statistics are really better (max shapes in a leaf: 127 average 4 a a tree depth of 27). Unluckily, i have a problem with ray/aabb intersection wich prevents me from rendering the scene (strange thing I didn't encountered it before), so I don't know wich performance improvements I gained or if it is correct at all. I will report new rendering times as soon as I fix it.
EDIT: Rendering looks correct, but it is waaay slower  [SMILEY :evil:]
(L) [2009/01/27] [cignox1] [Help with my BIH] Wayback!

OK, I had a bug in my traversal code. Now I can render my teapot in 7 seconds (previous rendertime was 30). I still intersect too many primitives (several hundreds), but at least this is a big improvement! I will try different heuristics, and check again both the buid and the traversal code looking for more speed!
(L) [2009/01/27] [Shadow007] [Help with my BIH] Wayback!

Well done !
There is one "trick" you may want to use in addition :
From Wikipedia :
 >> It is also possible to add a 5th traversal case, but which also requires a slightly complicated construction phase. By swapping the meanings of the left and right plane of a node, it is possible to cut off empty space on both sides of a node. This requires an additional bit that must be stored in the node to detect this special case during traversal. Handling this case during the traversal phase is simple, as the ray
- just intersects the only child of the current node or
- intersects nothing

IIRC it quite enhances the tree's quality.
(L) [2009/01/27] [cignox1] [Help with my BIH] Wayback!

Thank you for your hint! In the meantime, I've read the BIH paper again, and I discovered that I was passing shared primitives to both children. Now I always send them to the left child, and never to the right: now it renders in 3 seconds  [SMILEY :D]
Now I will try by using the dominant aabb axis to split volumes...
(L) [2009/01/27] [toxie] [Help with my BIH] Wayback!

you can pimp the construction by copying shared primitives f.e. to the smaller volume'd/area'd child node, instead of always to the left one.. even a random decision should help..
(L) [2009/01/27] [cignox1] [Help with my BIH] Wayback!

Thank you toxie, apparently this gave me a little speedup (20% more or less) but a few polys are missings: most probably I'm doing something wrong with the indeces, I need to check it out.
EDIT: Now it works  [SMILEY :lol:]  2 seconds.

back