E D R S I H C RSS
ID
Password
Join
ì¼ë§Œ 하고 휴ì‹ì„ 모르는 ì‚¬ëžŒì€ ë¸Œë ˆì´í¬ê°€ 없는 ìžë™ì°¨ 같아서 위험하기 ì§ì´ 없다. ë˜í•œ ì¼í•  줄 ì„ ëª¨ë¥´ëŠ” ì‚¬ëžŒì€ ëª¨í„°ê°€ 없는 ìžë™ì°¨ 같아서 아무 ì†Œìš©ì´ ì—†ë‹¤. -ì¡´ í¬ë“œ

 * ì›ë³¸ ë§í¬ : [http]http://www.3dtechdev.com/tutorials/leafbsp/3db1sptrees.html
  • ì €ìž : Gerald Filimonov Mk9megahertz@yahoo.com
  • BSP 중ì—ì„œë„ leaf bspë¼ëŠ” ë°©ì‹ì˜ ì•„í‹°í´ìž…니다.

Ever since Quake came out I had wanted to program a 3d engine just like I saw in the game. I learned that the Quake engine used BSP Trees to do its rendering. So I few years ago I began scouring the internet trying to find out what I could about this mysterious rendering technique. Unfortunately at that time there wasn뭪 a whole lot of good information available to figure out how to do an engine using BSP Trees. Sadly, that is still somewhat true today. Until a month ago I was still missing a few key pieces of information that would help me fully understand BSP Trees and their implementation in games such as doom and Quake. I had read the BSP FAQ until I was blue in the face. It laid out the basic principles behind BSP Trees and how to use them, but it didn't go into any great detail, which is what a beginner needs, lots of details. Fortunately a month or two ago I had finally concentrated on it enough that things were beginning to come together and I was starting to understand them more and more. It is within this text that I hope to share the knowledge I have gained in writing a simple Quake-style engine with the rest of the community. I hope that by you reading this text you will leave with a clearer and shaper understanding of BSP Trees, and even more importantly, how to use this knowledge in creating your own engine. Ill start off reviewing and explaining the content that's already out there on the Internet and build upon that.

Binary Space Partitioning Trees, also known as BSP Trees, deals with spatial subdivision. Spatial subdivision is taking a given space and subdividing it down into smaller pieces. For example, take your bedroom, it's a normal room just like any other, it has a ceiling and a floor and four walls. If you take your room and slice it in half right down the middle well end up with a left half and a right half. Now what if we were to take each of these halves and subdivide each half into half again. Then take each of those four parts of the room and subdivide them in half again and again and again until we decide we don뭪 want to subdivide any more and are content with what we have. This is spatial subdivision. Now in the above example we subdivided the room into half each time. We don't necessarily have to divide in half, we could divide the room into thirds or fourths each time, then recursively subdivide each of those thirds or fourths into thirds and fourths again. Since we want to deal with BSP Trees we'll only divide each space into a left half and a right half. Hence the name Binary. Also in my example above I said that we would split the room right down the middle, this would give us two halves of the room that are equal in size, when dealing with BSP Trees we don't necessarily have to divide the space exactly in half. Just as long as we have a left part and a right part well be fine.

A 3D BSP Tree is different from its cousin, the 2D BSP Tree in just a few ways. One of the obvious differences is the fact that we are now dealing with 3D space rather than just 2D space. In a 2D BSP our room is defined by four line segments, one for each of the four walls. If we were to subdivide this space down we would use a line to define where the division would take place. You can see an example of this in fig1.gif. The black lines are where the four walls of our room are and the red line is where we are going to divide this space into two parts. If we were to say that the line has front side to it as well as a back side, we could then classify each part of the room to be either on the front of the line or the back. This is what makes a BSP Tree binary. Each subdivision of space results in two parts, one of which lies on the front of our subdivision, while the other lies on the back. We would then repeat this process with both the front part and the back part until we decide we do not need to subdivide any more. Now that you understand how spatial subdivision works, lets look more at why we need it.


Take a look around your bedroom, most of you will have four walls, a ceiling and a floor. If you have more than four walls to your room then just use a little imagination and pretend your in a room with only four walls, each wall is the same size and shape. Now because were still in 2D land lets forget about your ceiling and floor for a moment, because no matter where you are in the room you can always look and see your ceiling or your floor. You might be saying to yourself, 밷ut I can see any of my walls no matter where I am in the room too!Well this is one of the nifty things about this four walled room, it forms what we call a convex space, meaning that no matter where you are in your room, no wall will ever block the view of another wall. Now lets look at a room that is not convex, meaning that in certain areas of the room depending on what way your looking, one or more of the walls in the room will block the view of seeing the entire surface of another wall. Take a look at fig2.gif to see what I mean. The green dot is the viewpoint is and the green lines depict how much can be seen by the viewer if they were looking in that direction. This viewing area is known as the viewers Field of Vision or FOV for short. If we divide up the walls into segments of what can be seen and what can not and give each one a number. We can see that from where the viewer is positioned and where he is looking that the red segments are what the viewer will see and the black segments are what the viewer will not. Notice we cannot see segments two and three because segment four is blocking our view of them. The blue line shows the 'shadow' that wall segment four casts on the others. If in our graphics engine we draw the wall formed by segments one and two first and the draw the wall formed by segments four and five our scene will look right. But how do we know to draw the wall formed by segments one and two first and not the ones formed by four and five. The fact is we don뭪. We could try calculating and comparing the end points of the walls and try to determine which ones to draw first, but sometimes this would be inaccurate and slow to say the least.


Here's where BSP Trees can come to the rescue. Lets take a look at just the room as shown in fig3.gif. What if we were to divide this room up into two parts with each part being convex, so that we could draw each convex part individually? Then if we were in one part we could draw the other part first then draw the part we are currently in. This would give us correct drawing order of the walls so our scene would look right. Ok that sounds good, but how do we decide to divide the room up so that it will give us two convex parts? Well we can use the walls as a guide to help us find a line that will do just that. Take a look at fig4.gif. If we use one of the small walls as a guide and construct a line that is parallel we can see that it will divide the room up into two smaller rooms, one of which is colored green and the other blue. Now if we were standing anywhere in the green room we can see that if we draw the walls of the blue room first and then the walls of the green room second, the scene will look correct. The same is true if we were standing in the blue room, by drawing all of the walls in the green room first and then drawing the blue ones, again our scene will look correct. Pretty neat huh? If we were to draw this relationship out in a tree like form, it would look something like fig5.gif. You'll notice on the back side of the partitioning line we have listed walls three, four, and five and on the front side we have listed walls one, two, six, seven, and eight. This is a fairly simple example in that after the first subdivision of space we end up with two convex areas, but what if we ended up with two areas that were still not convex? What we would do is take each of these spaces and subdivide them down again and see if we end up with a convex space on the front and on the back of the partitioning line. If we do, we are done subdividing that space, if not we further subdivide the subspace that뭩 on the front of the partitioning line and then with the subspace that뭩 on the back of the partitioning line until every subspace is convex. Lets look at an example of a more complex level. Now that we are dealing with more and more rooms, we can now begin using the term 'level' to refer to our collection of walls.


Take a look at the level I have drawn in bsp00.gif. You're probably wondering what all those little lines are sticking out of the walls. These are known as normals. They stick out of the wall so that the normal and the wall are perpendicular to each other. Basically they just tell us which way our walls are facing. Anything that뭩 on the same side of the wall as the normal is considered on the front side of the wall and anything that뭩 on the opposite side of the wall as the normal is considered on the back side of the wall.


Let's calculate a BSP Tree for this level. We'll begin by selecting one of the walls and use that as a guide to position our splitting line. If you take a look at bsp01.gif, you can see I뭭e selected one of the walls as a guide and put in the partitioning line. You want to choose a wall to use as a guide that will subdivide the other walls in the level so that some fall on the front of the partition and some fall on the back. It would be rather pointless to choose, say for example, the wall on the far left. By choosing this wall, we can see that all of the other walls are on the front side of it. This really doesn뭪 help us subdivide the level down at all, and will only make our BSP Tree bigger in the long run. The optimal wall choice would be to pick one that would subdivide the level so that an equal amount of walls are on the front side as are on the back side. This will help to create a 'balanced' tree that will be more efficient to render.


Ok, so I've drawn in partitioning line A on top of the level so we can see how the walls are gonna be classified. We can see that walls 1,2,4, and 5 are all on the backside of partitioning line A and walls 7,8,9,10, and 11 are all on the front side of partitioning line A, but what about walls 3, 6, and 12? Walls 6 and 12 are special case walls in that they don't lie completely on front side of partitioning line A or completely on the back side, instead they straddle or span the partition. So what do we now? Well if we take this wall and split it into two smaller walls so that one part is on the front and the other part is on the back of the partition we can then add each part to the appropriate side, either the front or the back. Wall 3 is another special case in that it lies exactly along the same line as partition A. In this case we can compare that walls normal with the normal of partitioning line A, if they are the same we would add the wall to the list of walls that are on the front side. If they are not the same we add it to the list of walls that are on the back. After we're processed all the walls classifying them to either the front of the partition or the back, we should see something like bsp02.gif. Notice how I've split walls 6 and 12 into two parts each labeled a and b.


Ok now that we've divided up the level using partition A into two separate subspaces, we need to recursively do the same with the front subspace and then the back subspace. Let's work on the front subspace first. Take a look at bsp03.gif, you'll notice we're faded the backside of partition A to show that we are focusing only on the front side of it in this step. We treat the front side like it was a level of its own and ignore everything else for now. We can see that this new subspace is not convex because there are some walls that could be used as partitions to divide the walls into front and back subspaces. Now that we're determined that we need to subdivide this subspace down further we need to select one of the walls and use it to create a partitioning line. We'll select wall 8 to use as a guide and create partitioning line B, which is shown in bsp04.gif. We then need to classify all of the walls in this subspace against partitioning line B, adding ones that are on the front side of it to the front list of walls, adding the ones that are on the back side of it to the back list of walls. Also if partitioning line B splits any walls we take care of those as well, but in this case we don't have any. Once we've added all the walls to the appropriate sides we again look at the front side of the partitioning line, (in this case B) and if necessary process it. Just for clarity I want to reiterate that we will only be dealing with this new front portion as if it were its own level and totally ignoring everything else for now. If we take a look at bsp05.gif we can see that the walls that make up the front area of partition B form a convex space. We can prove this by observing that no matter what wall we try to use as a new partition all the other walls are on the same side, which in this case would be the front.




So now that we've done all we can with the front side of partition B, its time for us to visit the back side of it. Just the same as when we were working with the front side, when working with the back side we will ignore everything else. By taking a look at bsp06.gif we can already see that the walls here do not form a convex subspace and will need to be partitioned down into smaller subspaces. If we turn our attention to bsp07.gif we can see that we뭭e created partitioning line C. Following the same procedure as before, we first concentrate on the front part first, and then once it has been completely processed we finish up with the back. I'm just gonna let you take over from here, and see if you can finish BSPing this level. I've drawn out the rest of the sequence to bsp this level in images bsp08.gif to bsp14.gif. I've also included images of what the tree would look like at each step in images bsp02tree.gif to bsp14tree.gif. Try making your own levels and bsping them for practice until you completely understand this otherwise you'll just be lost for the rest of this text.










Extending this to a 3D BSP Tree isn't that much more difficult. One of the biggest changes is that instead of using a line to partition our subspaces we'll be using a plane. For every n-dimension BSP Tree we work with, we use a primitive that is n-1 dimensions as a partition. Another change is the fact that polygons used to represent the ceilings and the floors can now be used as partitions. Basically any polygon that represents a wall, floor, or ceiling is a candidate to subdivide our space into two subspaces.

Our tree is comprised of nodes, some of these nodes are special nodes called leaves, these are the outer most nodes in the tree that do not have any children, are convex subspaces, and contain the polygons that define that convex subspace. The other nodes just contain the partitioning planes and a pointer to the front child and the back child. Here's a simple definition of a structure used for a node.

struct BSP_node {
    int nodeid; //unique identification number given to the node
    Plane3d partition; //well use this to store the partitioning plane that divides our
                               //nodes up
    BSP_node *backnode; //pointer to the node that is on the back side of our partitioning plane
    BSP_node *frontnode; //pointer to the node that is on the front side of our partitioning plane
    bool leaf; //true or false value for a node saying that this node has no children and only
                   //contains polygons
    int numpolys; //number of polygons in the node, this will be 0 if the node is not a leaf
    Polygon3d *nodepolylist; //pointer to the list of polygons in the leaf node, again this list will be empty if the node is not a leaf
};

This is about as simple as it gets for a definition of a node. The node id is just a number that is incremented and assigned to the node when it is created. The partition is just a plane that we will classify all of the polygons in the node with respect to this plane. A pointer to the node that contains the polygons that are on the front side of the partition as well as a pointer to the node that contains the polygons on the back. The leaf variable is a flag used to tell us that this node has no children and is a convex subspace. The numpolys variable tells us how many polygons are in this node. This value is 0 if the node is not a leaf. Finally we have a pointer to the list of polygons in the leaf. This is only used if the node is a leaf, otherwise its value is NULL.

The first node in any BSP Tree that we create will be the root node. This node is what the entire rest of the tree stems from, hence the name root. To create this node we simply do the following.:

BSP_node *root;
root = (BSP_node *)malloc(sizeof(BSP_node));

There are many ways to keep track of which polygons are in what node when creating the tree. I personally just throw all the polygons in the level into the list of polygons in root->nodepolylist and sort them into the front node or the back node as a create the tree. Later I remove these and free up the memory from any node that is not a leaf.

Creating the BSP Tree is fairly straightforward and involves using a recursive function to subdivide the level. Here is some pseudo code that explains how this is done.

BuildBSP (BSP_node *node)
{
    partplane = SelectPartitionfromList (node->nodepolylist, node->numpolys);

    if (partplane = -1 ) {
        node->leaf=true;
        return;
    }

    //Count how many polygons will end up on the front of this partition as well as on the back
    //Allocate memory for a front and back node

    //Classify each polygon in the current node with respect to the partitioning plane.

    //If it's on the front add it to the list of polygons in the front node

    //If it's on the back add it to the list of polygons in the back node

    //If it crosses the partition, split the polygon along the partition and add fragment that falls on the front of the partition to the front node, 
    //and the fragment that falls on the back to the back node

    //If its coplanar with the partition, compare the normals of the polygon with that of the partition, 
    //if they are the same add the polygon to the front node, otherwise add to the back.

    BuildBSP (node->frontnode);
    BuildBSP (node->backtnode);
}

SelectPartitionFromList works by taking the first polygon in the list and pretending it's a partitioning plane. We then classify the rest of the polygons in the list against this polygon. After we've done this we compare the number of polygons that end up on the front with the number that end up on the back. If only one of these values are 0 then we know that all of the polygons fell to one side of the partition and that this would make a poor choice for a partitioning plane. We then move on using the second polygon in the list as a pretend partitioning plane, and repeat the same comparison and continue this until we've used every polygon as a pretend partitioning plane. If during this procedure we do find a pretend partitioning plane that gives us polygons that fall on the front as well as on the back, we take the absolute value of the difference of the number of front and back polygons and store this in a variable. We also store what pretend partitioning plane we used as well. The next time we find that we have polygons on the front as well as the back of the pretend partitioning plane, we do the same calculations and compare this value with that of the previous value. If the current value is lower than the previous we know that this current pretend partition plane would give us a more balanced tree, so we store this as the new candidate. If after going through the entire list, using each polygon as a pretend partitioning plane we do not find one that will subdivide the list into front and back lists we know that this list is convex and return the value -1. Otherwise we return the number of the pretend partition plane that gave us the best balance.

If the return value from SelectPartitionFromList is indeed -1, we know that the polygons in this node form a convex room and cannot be subdivided any further. We then mark this node as a leaf and instantly return from the function.

There are a number of ways to calculate which side of a partitioning plane a polygon is on. One of the easiest and most straightforward ways of doing this is to classify each point in the polygon against the partitioning plane. If all of the points are on the frontside of the plane or on the plane itself we can consider this polygon to be on the front of the partitioning plane. If all of the points are on the backside of the plane or on the plane itself we can classify the polygon to be on the backside of the partitioning plane. Another case would be if the polygon spanned the partitioning plane, we would then have some points on the front of the partitioning plane as well as some on the back. The final case would be if the polygon was actually coincident with the partitioning plane, in this case all of the points of the polygon would be on the partitioning plane itself.

Another question you may be asking is how do we determine what side of a plane a point is on. This is really easy to calculate if you have the normal of the partitioning plane and the location of the point. We just take the DotProduct of the point and the partitioning plane's normal, if this value is greater than 0 we know the point is on the front, if its less than 0 we know its on the back. If the result of the DotProduct is 0 or is really close to 0, we know this point is on the plane. The reason I say really close is due to the limited precision in floating point numbers. The result of the DotProduct could be 0.0001, technically this value should be 0, but we can see that due to the errors in the floating-point math this is not the case. We can handle this by just saying that any value between -0.01 and 0.01 can be considered as 0.

Another piece of the puzzle is what's a good method to split a polygon using a plane. One of the easiest ways I have found to do this is to start off with the first vertex of the polygon and find out what side of the plane its on. Add this vertex to the first new polygon and move onto the second vertex in the original polygon. We again find out what side of the plane this vertex is on, if its on the same side as the last one we tested, we again add this vertex to the first new polygon. However, if the vertex is on the opposite side of the last one we know that the edge formed between this vertex and the last one intersects the partitioning plane. We calculate the point of intersection and add this point as a vertex to the first new polygon and the second new polygon as well. We then continue with the same tests as before, but now that we've passed the first intersection point, any vertices we add will be added to the second new polygon, until we pass the next intersection point. After passing the next intersection point we will revert to adding the vertices to the first new polygon. After we've looped through all of the vertices in the original polygon we should now have two new lists of vertices, each of which will describe the polygon fragment that is on the front of the partitioning plane as well as the back.

Calculating the intersection point of a line and a plane is pretty simple once you have the formulas to do so. Take a look at intersection.gif. Here you can see a plane and line that intersects it. The two 3D points L0 and L1 represent the line and the plane is defined by a point on the plane and a normal to the plane. The small dots in the equation in the lower right hand corner of the image mean that we take the dot product of the values that are to the left and to the right of the dot. Please note that if the denominator in the equation evaluates to 0 then the line is parallel to the plane and does not intersect.


That should about cover it for the creation of the BSP Tree. Next we'll cover traversing the tree in a back to front manner and rendering the polygons so that they are perfectly ordered.

Traversing the BSP Tree is pretty simple. We start at the root node and test to see if this node is a leaf. If it is not we then compare the position of the player or camera with the plane stored at that node. If we are on the front side of the plane, we traverse down the back side of the tree, if we are on the backside of the node we traverse down the front side of the tree. We continue doing this until our test for a leaf node evaluates to true. At this point we have hit a leaf node and instead of traversing down the tree any farther we just draw the polygons in that leaf and return from the function. Once we've returned from this level in our recursive function, we then traverse down the side of the tree that corresponds to the side of the plane we are on. All of this is really easier than it sounds.

Take a look at this pseudo code for the traversal/rendering of the tree.

int RenderBSP (BSP_node *node)
{

    int side;
    Vector3d Position;

    //The current position of the player/viewpoint
    Position.x=xpos;
    Position.y=ypos;
    Position.z=zpos;

    if (!node->leaf) {

        side=ClassifyPoint (&node->partition, &Position);

        if (side==CP_BACK) {
            RenderBSP (node->frontnode);
            RenderBSP (node->backnode);
        }
        else {
            RenderBSP (node->backnode);
            RenderBSP (node->frontnode);
        }
    }

    if (node->leaf) {
        //Draw polygons that are in the leaf
        }
    }
    return 1;
}

That's about all there is to traversing and rendering the BSP Tree.

Well, I've poured a good portion of what I know about BSP Trees into this document. I'm sure there are areas that I could have either written better or explained more thoroughly, but the ideas presented here are more than enough to get up to speed with BSP Trees and their uses in 3D graphics. BSP Trees are extremely powerful, and combining them with other technologies such as portals, potentially visible sets, and back-face culling can produce an extremely fast and solid engine. I hope that by reading this tutorial you've learned enough about BSP Trees that you can get a small engine up and running with a lot less effort than you would have used prior to reading this. At this time I would like to thank the people in #flipcode and #opengl on efnet irc for putting up with me and taking the time to answer my questions, without them, the writing of this document would not have been possible. Thanks guys!



Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2010-10-28 12:42:52
Processing time 0.4885 sec