Populating the Landscape: Billboards


Now that we have some ground under our feet, it is time to start adding more objects to the scene. One of the tricks for adding many objects to a 3D scene without a corresponding reduction in rendering speed is to use two-dimensional objects. These objects are referred to as billboards due to their similarity to the billboard signs found along the highway . The billboard is a two-dimensional image on a vertical quad positioned within the scene.

A combination of tricks gives our billboards the illusion of being three dimensional. The first trick is the use of transparency. Portions of an image used for a billboard are flagged as being transparent. This gives the billboard an irregular outline so that it does not appear rectangular like the original image. The second and even more important trick is the orientation of the billboard. The billboard quad is always oriented so that it is vertical and facing the camera. This way, no matter what direction we are looking from, we see the same image.

The obvious drawback to this technique is that the object being portrayed should be fairly symmetrical about the vertical axis. By being symmetrical, the observer is not bothered by the fact that the object looks the same from all directions. Trees and shrubs are common uses for billboards. Even though trees in real life do not tend to look exactly the same from all directions, the typical observer ignores this fact. Most people will perceive the billboard as a three-dimensional tree and think no more about it. To truly model a tree in three dimensions would require thousands of polygons. Billboards allow us to add a tree to the scene using only two.

For our sample game, we will use a number of different billboards to dress up the scene. We will add palm trees, cacti, and course markers. The course markers will be a series of red or blue posts. The posts will work like those seen in slalom racing, where the drivers in the race must stay to the right of the red posts and to the left of the blue posts. Placing the posts will allow us to lay out the course among the dunes.

The BillBoard class will provide the implementation of the billboard concept. The definition of the BillBoard class is shown in Listing 4 “24. One of the features of this class that should jump out at you is the fact that several of the class members are defined as static, which means that these variables will be shared by all instances of the class. One of these static variables is a list of all of the billboards. A static Render method provides rendering of all of the visible billboards with a single call.

Listing 4.24: BillBoard Class Definition
start example
 public class BillBoard : Object3D, IDisposable  {   #region Attributes   private CustomVertex.PositionNormalTextured[] m_Corners;   private Texture              m_Texture;        // Image for face   private bool                 m_bValid=false;   private string               m_sTexture;   private static string        m_sLastTexture;   private static VertexBuffer m_VB;   private static Matrix     m_TheMatrix;   private static ArrayList m_List = new ArrayList();       public static ArrayList Objects { get { return m_List; } }   public bool Valid { get { return m_bValid; } }  #endregion 
end example
 

The SetupMatrix method (Listing 4 “25) generates a rotation matrix that will orient any billboard in the scene so that it faces the camera. This orientation is based on the difference between the camera s LookAt vector and Eye vector. This difference gives us an idea as to how much we need to rotate the billboard and in what direction. The angle from the viewpoint to the billboard is an arctangent function of the ratio of the Z and X components of the vector. The sign of the X component determines which direction we need to rotate 90 degrees in order to have the billboard facing the camera. If the X component is zero, we need to use the camera heading angle to determine the proper rotation. This method is called by the static Render method prior to rendering the billboards.

Listing 4.25: BillBoard SetupMatrix Method
start example
 private static void SetupMatrix(Camera cam)  {   // Set up a rotation matrix to orient the billboard towards the camera.   Vector3 vDir = Vector3.Subtract(cam.LookAtVector, cam.EyeVector);   if(vDir.X > 0.001f)    m_Matrix = Matrix.RotationY((float)(   Math.Atan(vDir.Z/vDir.X)+Math.PI/2));   else if(vDir.X <   0.001f)    m_Matrix = Matrix.RotationY((float)(   Math.Atan(vDir.Z/vDir.X)   Math.PI/2));   else      if (cam. Heading < 179.0f   cam. Heading > 181.0f)         m_TheMatrix = Matrix.Identity;      else         m_TheMatrix = Matrix.RotationY((float)Math.PI); } 
end example
 

The static Add method (shown in Listing 4 “26) is used to place a billboard into the scene. This method works most efficiently if all billboards that use a common texture image are loaded sequentially. This allows the method to load only one copy of the image to the video card to be shared by all instances of the type of billboard. The method takes the position, size , name, and texture name as arguments. It begins by comparing the texture name with the name of the last texture used. If the two names match, we can clone the last billboard and reuse the texture. The last entry in the list is passed to a copy constructor to create the new billboard. If the texture names do not match, the normal constructor is called.

Listing 4.26: BillBoard Add Method
start example
 public static void Add(float north, float east, float altitude, string sName,   string sTextureName, float fWidth, float fHeight)  {   BillBoard obj;   if (sTextureName.CompareTo(m_sLastTexture) == 0)   {    BillBoard lastObject = (BillBoard)m_List[m_List.Count   1];    obj = new BillBoard (sName,lastObject);   }   else   {    obj = new BillBoard(sName, sTextureName, fWidth, fHeight);   }   m_sLastTexture = sTextureName;   float height = CGameEngine.Ground.TerrainHeight(east, north);   if (altitude < height)   {    altitude = height;   }  obj.m_Pos.X = east;   obj.m_Pos.Y = altitude;   obj.m_Pos.Z = north;   try   {    m_List.Add(obj);    CGameEngine.QuadTree.AddObject((Object3D)obj);   }   catch (DirectXException d3de)   {    Console.AddLine("Unable to add billboard " + sName);    Console.AddLine(d3de.ErrorString);   }   Catch (Exception e)   {    Console.AddLine("Unable to add billboard " + sName);    Console.AddLine(e.Message);   }  } 
end example
 

The next thing to do is set the position of the billboard. The terrain height at the billboard s position is queried to ensure that the billboard is not placed below ground level. You may place the billboard so that it appears to be floating above the ground if you wish. Once the position is saved into the object, the object is placed into the billboard list for future use and added to the Quadtree for culling.

The copy constructor for the class (shown in Listing 4 “27) creates a new billboard that is a complete copy of the supplied billboard except for the name. The name is saved into the new instance and the Copy method is used to perform the member variable data copying.

Listing 4.27: BillBoard Class Copy Constructor
start example
 public BillBoard(string sName, BillBoard other) : base(sName)  {   m_sName = sName;   Copy(other);  } 
end example
 

The normal constructor for the class (shown in Listing 4 “28) creates the billboard at the supplied size and using the supplied texture filename. A radius value of half the width is used as the bounding radius for the object. Setting up the vertices for the billboard quad looks much like the quad we used for the terrain. The difference is that the size of the quad is based on the height and width passed into the constructor. The billboard is considered valid once the texture is successfully loaded.

Listing 4.28: BillBoard Class Constructor
start example
 public BillBoard(string sName, string sTextureName, float fWidth,     float fHeight) : base(sName)  {   m_sName = sName;   m_sTexture = sTextureName;   m_fRadius = fWidth / 2.0f;   // Create the vertices for the box.   m_Corners = new CustomVertex.PositionNormalTextured[4];   m_Corners[0].X = m_fRadius;   // Upper left   m_Corners[0].Y = fHeight;   // Upper left   m_Corners[0].Z = 0.0f;   // Upper left   m_Corners[0].Tu = 1.0f;   m_Corners[0].Tv = 0.0f;   m_Corners[0].Nx = 0.0f;   m_Corners[0].Ny = 0.0f;   m_Corners[0].Nz =   1.0f;   m_Corners[1].X =   m_fRadius;   // Upper right   m_Corners[1].Y = fHeight;   // Upper right   m_Corners[1].Z = 0.0f;   // Upper right   m_Corners[1].Tu = 0.0f;   m_Corners[1].Tv = 0.0f;   m_Corners[1].Nx = 0.0f;   m_Corners[1].Ny = 0.0f;   m_Corners[1].Nz =   1.0f;   m_Corners[2].X = m_fRadius;   // Lower left   m_Corners[2].Y = 0.0f;  // Lower left   m_Corners[2].Z = 0.0f;   // Lower left   m_Corners[2].Tu = 1.0f;   m_Corners[2].Tv = 1.0f;   m_Corners[2].Nx = 0.0f;   m_Corners[2].Ny = 0.0f;   m_Corners[2].Nz =   1.0f;   m_Corners[3].X =   m_fRadius;   // Lower right   m_Corners[3].Y = 0.0f;   // Lower right   m_Corners[3].Z = 0.0f;   // Lower right   m_Corners[3].Tu = 0.0f;   m_Corners[3].Tv = 1.0f;   m_Corners[3].Nx = 0.0f;   m_Corners[3].Ny = 0.0f;   m_Corners[3].Nz =   1.0f;   // Load the texture for the face.   try   {    m_Texture = GraphicsUtility.CreateTexture(CGameEngine.Device3D,      sTextureName);    m_bValid = true;   }   catch   {    Console.AddLine("Unable to create billboard texture for " + sName);   }  } 
end example
 

The Copy method (shown in Listing 4 “29) is used by the copy constructor to duplicate the contents of one instance into another. You might have seen this referred to as a deep copy elsewhere. It does not reload the texture. Instead, it just creates another reference to the existing texture.

Listing 4.29: BillBoard Copy Method
start example
 private void Copy(BillBoard other)  {   m_sTexture = other.m_sTexture;   m_fRadius = other. m_fRadius;   // Create the vertices for the box.   m_Corners = other. m_Corners;   m_bValid = true;  } 
end example
 

The game engine renders the billboards by calling the static method RenderAll shown in Listing 4 “30. By being static, we are ensuring that there is a single point for rendering every billboard. Remember that we have placed references to every billboard in a static array list. The first thing to see is if there are any billboards to draw at all. If not, we can just return. If we are going to be drawing billboards, we need to set up the rendering states required.

Listing 4.30: BillBoard Render All Method
start example
 public static void RenderAll(Camera cam)  {   string currentTexture = "";   if (m_List.Count > 0)   {   CGameEngine.Device3D.RenderState.CullMode =          Microsoft.DirectX.Direct3D.Cull.Clockwise;    // Set diffuse blending for alpha set in vertices.    CGameEngine.Device3D.RenderState.AlphaBlendEnable = true;    CGameEngine.Device3D.RenderState.SourceBlend = Blend.SourceAlpha;    CGameEngine.Device3D.RenderState.DestinationBlend = Blend.InvSourceAlpha;    // Enable alpha testing (skips pixels with less than a certain alpha).    if(CGameEngine.Device3D.DeviceCaps.AlphaCompareCaps.SupportsGreaterEqual)    {     CGameEngine.Device3D.RenderState.AlphaTestEnable = true;     CGameEngine.Device3D.RenderState.ReferenceAlpha = 008;     CGameEngine.Device3D.RenderState.AlphaFunction = Compare.GreaterEqual;    }    SetupMatrix(cam);    CGameEngine.Device3D.VertexFormat =      CustomVertex.PositionNormalTextured.Format;    if (m_VB == null)    {     m_VB = new VertexBuffer(typeof(CustomVertex. PositionColoredTextured),      4, CGameEngine.Device3D, Usage.WriteOnly,      CustomVertex. PositionNormalTextured.Format,      Pool.Default);    }    CGameEngine.Device3D.SetStreamSource(0, m_VB, 0);    foreach (BillBoard obj in m_List)    {     if (currentTexture.CompareTo(obj.m_sTexture) != 0)     {        m_VB.SetData(obj.m_Corners, 0, 0);only copy when texture changes       // Set the texture.       CGameEngine.Device3D.SetTexture(0, obj.m_Texture             currentTexture = obj.m_sTexture;     }     obj.Render(cam);    }   foreach (BillBoard obj in m_List)   {      obj.RenderChildren(cam);   }   }  } 
end example
 

The first render state we will set is the culling mode. Since the vertices for the billboard have been defined in clockwise order, we will set the culling mode to Clockwise . The next set of render states are concerned with the transparent portion of the billboard image. The portions of the billboard image that should be transparent have a value in the alpha channel of zero. An alpha value of zero indicates a fully transparent pixel. A value of 255 in the alpha channel indicates an opaque pixel. Values between the two extremes indicate a range of transparency between the two extremes. Chapter 11 will show how to create images with the alpha data for transparency.

Setting the AlphaBlendEnable state to true enables transparency. The two states on the following lines, SourceBlend and DestinationBlend , define how the alpha values are interpreted. SourceAlpha for the source blending and InvSourceAlpha for the destination provide the normal transparency behavior. Some video cards provide alpha testing capabilities to improve efficiency. If the card supports alpha testing, we configure the testing so that any alpha value greater or equal to the reference value is a pixel that is kept. All other pixels are ignored and therefore completely transparent.

The SetupMatrix method creates the rotation matrix that makes the billboards face the camera. The vertex format is set to the value for our billboard vertices. The vertex buffer for the billboards is a static member of the class. If the buffer has not been initialized yet, we create one that will hold four vertices. We then set this buffer as the source stream for drawing the billboards. At this point, we are ready to begin rendering billboards.

To draw the billboards, we iterate through the static list of billboards. If the texture for the current billboard is different from the last billboard we drew, we need to update the texture and vertex data. Whenever the texture image for a billboard changes, it is likely that the size and shape of the billboard changes as well. The new billboard texture is set as the current texture and the vertices for the billboard are copied into the vertex buffer. The call to the billboard object s Render method comes next. Rendering any child objects of the billboards completes the process.

The object s Render method (shown in Listing 4 “31) performs the actual rendering of an individual billboard. It begins by checking to see of the billboard is both valid and not culled. If this is the case, the method sets the billboard s position in the world transform matrix. The drawing of the billboard is accomplished with the DrawPrimitives method that renders a strip of two triangles for the billboard quad. The method finishes by setting the billboard as culled. This means that the billboard will not be drawn during the next pass unless it is still visible. The RenderChildren method performs the same processing for any child objects that may be attached to the billboards.

Listing 4.31: BillBoard Render and RenderChildren Methods
start example
 public override void Render(Camera cam)     {      if (Visible && m_bValid && !IsCulled)      {     // Translate the billboard into place.      m_ TheMatrix.M41 = m_vPosition.X;      m_ TheMatrix.M42 = m_vPosition.Y;      m_ TheMatrix.M43 = m_vPosition.Z;      CGameEngine.Device3D.Transform.World = m_TheMatrix;      // Render the face.      CGameEngine.Device3D.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);      Culled = true;     }  }  public void RenderChildren(Camera cam)  {      if (Visible && m_bValid && !IsCulled)      {         // Translate the billboard into place.         CGameEngine.Device3D.Transform. World = m_TheMatrix;         Culled = true;         if (m_Children. Count > 0)         {            Object3D obj;            for (int i=0; i<m_Children. Count; i++)             {                obj = (Object3D)m_Children.GetByIndex(i);                obj. Render (cam);             }         }      }  } 
end example
 

The InRect method (shown in Listing 4 “32) is a method required by all 3D objects. It is used by the routine that inserts objects into the Quadtree to determine if an object is within the supplied rectangle. This implementation of the method will not take the size of the billboard into account. We will just look to see if the placement position of the billboard is within the rectangle. If you want to do a more precise test, you could check to see if the circle consisting of the billboard s position and radius intersects the rectangle. We will take advantage of the Rectangle class s Contains method to perform the test.

Listing 4.32: BillBoard Render All Method
start example
 public override bool InRect(Rectangle rect)  {   return rect.Contains((int) vPosition.X, (int) vPosition.Z);  } 
end example
 

The last billboard method is Dispose (shown in Listing 4 “33). As with other object Dispose methods, we will dispose of the texture. We also dispose of the static vertex buffer if it has not been disposed of yet. If the billboard has any children attached, they are also disposed of.

Listing 4.33: BillBoard Render All Method
start example
 public override void Dispose()  {   m_Texture.Dispose();   if (m_VB != null)   {       m_VB.Dispose();       m_VB = null;   }   if (m_Children.Count > 0)   {      Object3D obj;      for (int i=0; i<m_Children.Count; i++)     {      obj = (Object3D)m_Children.GetByIndex(i);          obj.Dispose();     }   }  } 
end example
 

Now that we have our Billboard class, we can dress up the scene a bit. Figure 4 “4 shows the new scene with a number of palm trees and cacti sprinkled across the sand. There are also a number of red and blue colored posts, which will be used in the finished game to mark the racecourse. We can see the difference that the billboards can make. We have a greater sense of the size of the database when we can see small objects off in the distance.

click to expand
Figure 4 “4: Scene with billboards



Introduction to 3D Game Engine Design Using DirectX 9 and C#
Introduction to 3D Game Engine Design Using DirectX 9 and C#
ISBN: 1590590813
EAN: 2147483647
Year: 2005
Pages: 98

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net