关于八叉树及三维场景管理算法

Octree

(by J. Eder 9225396)

 

 What is an Octree?
 Using an Octree
 Types of Octrees
    What does it represent?
    How is it managed?
How to structure Octree-structures
Pointerless full Octree
Traditional design of Pointer Octrees
Branch-On-Need-Octree
 
 
 
 

What is an Octree ?

(First I have to say, that it was discussed, if an Octree which doesn't branchs in all directions in the same moment is not an Octree: this paper doesn't matter when to branch, it just assumes that you have not less than zero and not more than eight child-nodes and in any meaning its matter is volumes)
Because many volume data-sets are very large it isn’t possible to interactively render such a scene. The implementation of hierarchical data structures could make this better. An Octree is such a hierarchical data structure.
It is a kind of a tree, which nodes (except the leaves) each has max. eight child-nodes. Mostly the nodes represent volume-pieces which are further divided into max. eight “Octants” which are represented by the child-nodes. The Octants mostly are regular, but also irregular shapes are imaginable. If the maximum resolution is reached at one node, it’s a leave of the tree. If the leaves of a node have exactly the same attributes, it is possible to reduce them to a node. This is called condensation.
 

Using an octree

Usually an Octree represents a volume, but of course it is possible to use it for different applications, such as to represent spatial relationships of geometrical objects. But the most common use is to represent a volume. Some calculations work very much faster if they can be performed on condensed node. Of course each node "carries" a type of information in it. If its job is just to represent if the "voxel" is there or not it's a boolean data type. On the other hand it is arguable to store a complete set of values, for instance dense values, temperature values and the further. This data type is non boolean.
 

Type of Octrees

There are several methods to distinguish the "family" of octrees, but I would say the best are:

 What does it represent?
 How is it managed?
 

 What does it represent?

There you can distinguish two mainly different types of information: Boolean and Non-Boolean Information. At the boolean information there's just the information saved, if the volume is interesting (BLACK) or not (WHITE). This binary decision only works clear for leaves, but for any ancestor-nodes there can be a third possibility: Some of the nodes could be BLACK and some of the nodes could be WHITE, then these ancestor-nodes are called "GRAY".
With Non-Boolean data it's not so easy: The information can be any value and its the more unlikely that you can condense the nodes than the more possibilities the attributes of the nodes have to get set. Therefore mostly all ancestor-nodes are gray nodes, which means that cannot be condensed and for nearly all discrete volume points you need its ancestors, which need also memory. This can be enhanced until about the double amount.

How is it managed?

There are any positibilities to implement an octree, the mostly common approaches are pointer based and with array (pointerless). The pointerbased octrees have for each child node a pointer which shows the position of the child. Sometimes there is also a pointer to the ancestor. (Of course not to forget the attributes of the node) If a gray node is defined it means, that all pointers are filled and this of course takes memory. The next approach is the linear octree which works (nearly) without pointers. If you decide, always to make a "full" octree (all ancestors have all child-nodes), you exactly know for a given size of a volume the amount of nodes in one hierarchical plane. Thats why it's directly possible to calculate the address of the wanted node. This method is very memory effortable, because it assumes a full octree.
If the path is saved, how to get from the root to the leaf,  it needs not a full octree. Each node now possess' a key which describes to take which turn-off. These keys can be accessed through an array or a Hash-table.

How to structure Octree-structures

If you are working with trees it's hard enough to remember the correct pointer for the wanted child and not to accidentally exchange it. But with octrees is really tricky. That's why we should consider a name-giving convention. It's called the ZYX-convention. In this convention you take the binary number of 0-7 to describe the nodes. Then you say: first bit=Z-Coordinate, second bit is Y-Coordinate, third bit is X-Coordinate. For each of these coordinates you say "less" half or "greater" half (in coordinate direction) and for less you say 0 and for greater you say 1.

Pointerless full Octree

In a full Octree each node has exactly eight children for each ancestor. Therefore you can directly calculate the total number of nodes.

It is a regular structure and therefore the adress of the node can be directly calculated. It cost much memory ( for 320x320x40 you need about 40 Mill words) and therefore the traditional design is with pointers.

Traditional design of Pointer Octrees

 Each ancestor-node has eight pointers which can be empty or can point to the child-node. It works with the "even-subdivision strategy". This means, that each range is devided in nearly similar parts and the child nodes nearly exactly describe the same volume. But the disadvantage is described below with an example of 16x8x4 :
 
X
Y
Z
16
8
4
8,8
4,4
L,L
4,4,4,4
L,L,L,L
 
L,L,L,L,L,L,L,L
 
 
The tree is branching seperated in all dimensions which means that the lowest level the leaves (the "L"s) are not efficient because all have much Y and Z informations equal to another. Additionally they are the most.

Branch-on-Need Octree

The basic idea is, that the tree is only branching if "necessary" trying branching similar to the power of 2. This is realized by dividing at that moment at the binary number of the range is losing its leftmost bit. That works like following: You write down the binary numbers of the ranges which are the differences of their "upper" and their "lower" limits. Then you write all leading zeros you need that you have the same number of digits of all dimensions. You now only branch in that dimensions which have a "1" in their leftmost bit. For example you have (16/15/8) or (10000/01111/01000): It is branching only in the X direction. You can determin the childs ranges just for the "upper" by simply removing the leftmost 1. It results in 0000, which means "no further branch". The "lower" child you just take as "1"s in the number of the number of digits of the remaining "upper" range. The "lower" child has 1111 which means it now is a "power"-node which means it branches in that dimension down for all nodes (like the power of 2: 15-7-3-1).
 
 
 (Source: ACM Transactions on Graphical, Vol.11 No3,July '92)
//------------------------

OCTREE 教程

 
 
对OCTREE的描述

  OCTREE 是对3D空间进行划分,也可以叫空间分割。他允许你只对你的3D世界中摄象机照射的区域进行作画。他也能用于冲突检测。下面讲一下为什么要进行空间分割。假设你建立了一个游戏世界,这个世界有超过100,000个多边形要画。如果你建立一个循环并传递这些多边形,那速度是很慢的。即使你有一块很好的显示卡,他也会有很大的麻烦。但是玩你游戏的玩家的显示卡不会超过300$。有没有一种方法只渲染摄象机看见的多边形?那就是美丽的OCTREE。他允许你快速的找到你要渲染的多边形。


OCTREE是怎样工作的

  OCTREE工作在立方体中。最初,OCTREE从根接点开始,这个根接点对齐于立方体中整个世界,水平或场景的axis中心线。因此,把我们的整个世界想象成一个不可见的立方体。
 

关于八叉树及三维场景管理算法


  现在根接点存储了世界中的全部顶点。这是,他还是不能给我们任何好处,因为他还会画所有的东西。我们想把这个接点分成8个部分。一次,我们划分时,有8个立方体包含那最初的根接点的立方体。那意味着有4个立方体在上面,4个立方体在下面。请看下图:

关于八叉树及三维场景管理算法


  记住图中黄色轮廓线没有。
  我们刚刚把这个世界分成8个部分。想象一下如果我们有2,3,4个部分,我们的世界将是怎样?我们的摄象机在世界的中间,向着后面靠右的角落。如果你看这些线,你将注意到在OCTREE中8个结点中的第4个。这些结点包含2个后面的顶和底结点。这意味着我们只用画存储在这些结点中的顶点。

关于八叉树及三维场景管理算法


  我们如何检测出我们要画的结点?如果你学过破片拣选(frustum culling),这将是很简单的。你能得到这些破片的大小并检测每个结点,看他是否被截断或在你的视觉破片中。有一个关于破片拣选的教程在http://www.gametutorials.com/. 如果一个立方体截断破片,我们就将画这些结点。在这个例子中,我们切的数量是我们需要画的50%。记住,这只是我们世界中的一个部分。部分越多,我们就将越精确。当然,我们不会需要太多的点。下面,我们来看一下下面的图:

关于八叉树及三维场景管理算法


  在上面这幅图中,你将发现许多的不同。这个例子中,不是在原始的8个立方体的每一个结点中建立8个新的立方体。原始的8个结点的顶和底面没有细分。你总是把一个结点分成8个或更多的结点,但是,如果在这个面没有三角形可以存储,我们将忽视这个结点并不给他分配内存。如果我们进一步的细分,更多的结点将形成原始的世界。如果我们变换到另一个部分,那立方体将相似的变换。为了说明这一点,请看图:

关于八叉树及三维场景管理算法 关于八叉树及三维场景管理算法


  在图中,有两个球,但是方向相反。注意左边的部分,他将那世界分成2个结点,不是8个。这是因为球只要2个结点。请看右边的球是不是和左边的很相似。这幅图告诉我们:只显示需要的部分结点。如果没有三角形占用空间,结点将不会建立。


何时停止细分

  现在,我们明白了如何细分,但是我们也需要知道怎样停止细分。有许多的方法:
  1。如果当时的三角形数量少于我们定义的最大的三角形数量,我们可以停止细分当前的结点。举个例子,我们定义三角形的最大数量为100。这意味着我们在细分结点之前,要检测一下当前三角形的总数量是否小于或等于我们定义的最大数量,然后再做决定。如果数量少于或等于,我们将停止细分并指定这些三角形到那个结点。请注意我们从不指定任何三角形到任何结点,除非他是末结点。如果我们划分一个结点,我们不能存储三角形到那个结点,但是可以存储在他的子结点或他们的子结点中,甚至到他们的子中,等等。当我们复习了怎样画0C树后,将会有更多的了解。

  2。另一个方法是在停止细分时,看我们是否细分的数目是否超过了细分的标准。例如,我们可以先建立细分的标准为10,如果细分的数量大于这个标准,我们将停止并指定这些在立方体中这个面的顶点到那个结点。当我们说“大于这个标准”就意味着细分的数量有11个。

  3。最后的方法是看结点数是否超过结点变量定义时的值。例如,我们设置结点变量的值为500。每次,我们建立一个结点,相应的结点数增加1。然后在我们每次建立另一个结点时,检测一下我们当前的结点数是否超过结点变量。如果结点数为501,我们将不会细分这个结点,但是指定他的当前顶点给他。我自己用1和3的方法,但是1和2也很好,因为你可以测试不同的细分的标准。

 
怎样画OCTREE

  OCTREE建立后,我们就可以画我们需要的结点。那些立方体不是全部包含在我们的视觉中的,只有一部分。这就是我们为什么要计算每个结点的三角形的数量,如果我们只需要一个结点中的一部分,这样我们就不需要画成千上万个三角形。我们从根目录开始画OCTREE。对于每个结点都存储着一个中心点。这是非常好的,比如在下面这个函数中:

  //这个函数取走立方体(X,Y,Z)的中心点和他的大小(width/2)
  bool CubeInFrustum(float x,float y,float z,float size);

  这个函数返回true or false,是由立方体是否在破片中决定的。如果立方体在破片中,我们将检测所有他的结点,看他们是否在破片中,否则我们将忽约树中的整个分枝。当我们得到了破片中的结点,但是没有任何结点在他下面。我们想画的顶点存储在末结点中。记住,只有末结点有顶点存储。看下面的图:

关于八叉树及三维场景管理算法


  有阴影的立方体是在破片中。白色的立方体不是在破片中。这里显示了细分的2层。


OCTREE 中的冲突

  OCTREE 不是用于渲染,但是用于冲突检测很好。随着游戏的发展,冲突检测也在改变,你将不得不在游戏世界中运用自己的算式检测你的角色或目标是否冲突。下面是一些冲突检测的例子:
  1。 你将建立一个函数允许你转递3D空间中的点到你的OCTREE并返回在这个点周围的顶点。你将转递的那个点是你的角色和目标的中心点。这虽然可以工作一端时间,但如果这个点靠近一个立方体的边时呢?你的角色或目标可能和另一个立方体的顶点相冲突。为了解决这个问题,你将做一些事情。你可以转递角色/目标的半径或一定的空间,然后检测半径或一定的空间是否和周围的结点相冲突。这由你的角色/目标的形状决定。下面是一些典型的函数:

  //这个函数取走角色/目标(X,Y,Z)的中心点并返回靠近他的顶点
  CVector3 *GetVerticesFromPoint(float x,float y,float z);

  //这个函数取走角色/目标(X,Y,Z)的中心点和半径,然后返回和他冲突的结点中的顶点
  CVector3 *GetVerticesFromPointAndRadius(float x,float y,float z,float radius);

  //这个函数取走角色/目标(X,Y,Z)的中心点和立方体的大小,然后返回和他冲突的结点中的顶点
  CVector3 *GetVerticesFromPointAndCube(float x,float y,float z,float size);

  我相信你有更好的方法快速的检测,在这里只是给你一点基础。


总结

  这篇教程只是给你讲截如何建立自己的OCTREE。关于这篇文章中的代码在www.gametutorials.com,我希望这篇文章对你有用。


中文译者:antking
http://akinggame.gameres.com/
这篇文章的英文版在http://www.gametutorials.com/Tutorials/OpenGL/Octree.htm

Ben Humphrey (DigiBen)
Game Programmer
[email protected]
Co-Web Host of http://www.gametutorials.com/

 

Woo算法

候选平面的方向朝前,用粗线
条表示,与射线之间的交点用
灰色表示。由于左边的相交点
距离射线原点最远,因此将该
点作为潜在相交点。

针对射线与AABB之间的相交检测,Woo提出了一些巧妙的优化
方法。
基本思想:在组成AABB的6个平面中选出3个候选平面。对于每
一对互相平行的平面来说,忽略其背向平面而不作进一步考虑。
计算射线与候选平面的相交距离(t值),其中的最大值就可能对应
一个相交情形。如果找到潜在的相交情形(相交点位于AABB的相
应面上) ,再计算出实际的相交点。

Intesection intersect( const Ray &one, const AxisAlignedBox &two )//Ray用参数表示则在0,1之间


{
    OctreeSceneManager::intersect_call++;
    // Null box?
    if (two.isNull()) return OUTSIDE;
 // Infinite box?
 if (two.isInfinite()) return INTERSECT;

    bool inside = true;
    const Vector3& twoMin = two.getMinimum();
    const Vector3& twoMax = two.getMaximum();
    Vector3 origin = one.getOrigin();
    Vector3 dir = one.getDirection();

    Vector3 maxT(-1, -1, -1);

    int i = 0;
    for(i=0; i<3; i++ )
    {
        if( origin[i] < twoMin[i] )
        {
            inside = false;
            if( dir[i] > 0 )
            {
                maxT[i] = (twoMin[i] - origin[i])/ dir[i];//循环一次,求出与三个平面相交的参数(0, 1)
            }
        }
        else if( origin[i] > twoMax[i] )
        {
            inside = false;
            if( dir[i] < 0 )
            {
                maxT[i] = (twoMax[i] - origin[i]) / dir[i];
            }
        }
    }//--------------------------以上说明原点在包围盒内部,origin大于最小的点,小于最大的点

     //参数为0--1

    return INTERSECT;

 

 

  if( inside )
    {
        return INTERSECT;
    }
    int whichPlane = 0;
    if( maxT[1] > maxT[whichPlane])
        whichPlane = 1;
    if( maxT[2] > maxT[whichPlane])
        whichPlane = 2;                                 //求出最大的那个参数,就是潜在的求交平面

    if( ((int)maxT[whichPlane]) & 0x80000000 )
    {
        return OUTSIDE;
    }
    for(i=0; i<3; i++ )
    {
        if( i!= whichPlane )
        {
            float f = origin[i] + maxT[whichPlane] * dir[i];
            if ( f < (twoMin[i] - 0.00001f) ||                  //如果小于最小值或大于最大值,则不向交
                    f > (twoMax[i] +0.00001f ) )
            {
                return OUTSIDE;
            }
        }
    }

    return INTERSECT;

 

}


 

Intersection intersect( const AxisAlignedBox &one, const AxisAlignedBox &two )
{
    OctreeSceneManager::intersect_call++;
    // Null box?
    if (one.isNull() || two.isNull()) return OUTSIDE;
 if (one.isInfinite()) return INSIDE;
 if (two.isInfinite()) return INTERSECT;


    const Vector3& insideMin = two.getMinimum();
    const Vector3& insideMax = two.getMaximum();

    const Vector3& outsideMin = one.getMinimum();
    const Vector3& outsideMax = one.getMaximum();

    if (    insideMax.x < outsideMin.x ||
            insideMax.y < outsideMin.y ||
            insideMax.z < outsideMin.z ||
            insideMin.x > outsideMax.x ||
            insideMin.y > outsideMax.y ||
            insideMin.z > outsideMax.z )//最大值小于最小值,一定在外面
    {
        return OUTSIDE;
    }

    bool full = ( insideMin.x > outsideMin.x &&//内部最小的大于外部最小的,内部最大的小于外部最大的
                  insideMin.y > outsideMin.y &&
                  insideMin.z > outsideMin.z &&
                  insideMax.x < outsideMax.x &&
                  insideMax.y < outsideMax.y &&
                  insideMax.z < outsideMax.z );

    if ( full )
        return INSIDE;
    else
        return INTERSECT;

}

http://docs.google.com/viewer?a=v&q=cache:z8AWOjhGRAgJ:www.cad.zju.edu.cn/home/jin/course/chapter13.pdf+Woo+%E7%9B%B8%E4%BA%A4&hl=zh-CN&pid=bl&srcid=ADGEESih9zGwXYJIDhtbbrphcFuqTi18DK59zpcCT00MXhJLJPRy9DuIuzYYRpId42XIQz9nOxE9BfC8j2KzCPzu3ssof33BRsH2S0CfHu8vQFkmFbZwDVj4jeujxxdN1rExBLpNlVQG&sig=AHIEtbTq1uZ1bzcCQHN88u0wmEBcs6HrVQ

 

 

Intersection intersect( const Sphere &one, const AxisAlignedBox &two )
{
    OctreeSceneManager::intersect_call++;
    // Null box?
    if (two.isNull()) return OUTSIDE;
 if (two.isInfinite()) return INTERSECT;

    float sradius = one.getRadius();

    sradius *= sradius;

    Vector3 scenter = one.getCenter();

    const Vector3& twoMin = two.getMinimum();
    const Vector3& twoMax = two.getMaximum();

    float s, d = 0;

    Vector3 mndistance = ( twoMin - scenter );
    Vector3 mxdistance = ( twoMax - scenter );

    if ( mndistance.squaredLength() < sradius &&//最大点及最小点到球心的距离均小于半径,则在包围盒内部
            mxdistance.squaredLength() < sradius )
    {
        return INSIDE;
    }

    //find the square of the distance
    //from the sphere to the box
    for ( int i = 0 ; i < 3 ; i++ )                        //在最小点的左边,并且到最小点的距离小于半径
    {
        if ( scenter[ i ] < twoMin[ i ] )
        {
            s = scenter[ i ] - twoMin[ i ];             //在最大点的右边,并且到最大点的距离大于半径
            d += s * s;
        }

        else if ( scenter[ i ] > twoMax[ i ] )
        {
            s = scenter[ i ] - twoMax[ i ];
            d += s * s;
        }

    }

    bool partial = ( d <= sradius );

    if ( !partial )
    {
        return OUTSIDE;
    }

    else
    {
        return INTERSECT;
    }


}

 

 

你可能感兴趣的:(算法)