Drawing using the INDEX BUFFER in XNA

BEFORE READING, this post explains vertex buffers and how you download vertices to the GPU using .SetData in greater detail

#region using...
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
#endregion

public class Game1 : Microsoft.Xna.Framework.Game
{
  GraphicsDeviceManager graphics;

  ////
  // To draw INDEXED primitives, we'll need 4 basic objects:
  //   1.  A BasicEffect.  Used to DRAW.
  //   2.  A VertexBuffer.  Used to STORE ALL THE VERTICES of
  //        the object we wish to draw.
  //   3.  A VertexDeclaration.  Explains to the GPU what PROPERTIES
  //        each vertex has.  In this example, each vertex will
  //        simply have a POSITION in space, and a COLOR.  Could
  //        also have had texture coordinates, but we're not using those here.
  //   4.  An INDEX BUFFER:  Explains to the GPU what
  //       ORDER the vertices in the VertexBuffer should be visited.
  //       That is what the INDEX BUFFER is for.

  //       Imagine you are a kid again going trick or treating.
  //       This is your neighbourhood:

  //     ^       ^       ^        ^        ^
  //    |_|     |_|     |_|      |_|      |_|
  //    100     102     104      106      108
  //
  //
  //
  //    101     103     105      107      109
  //     ^       ^       ^        ^        ^
  //    |_|     |_|     |_|      |_|      |_|

  // Before leaving your house, you look at this map
  // of houses and you identify the houses you know
  // NEVER have candy.  You mark the ones that
  // DO with an X.

  //     ^       ^       ^        ^        ^
  //    |X|     |_|     |X|      |_|      |X|
  //    100     102     104      106      108
  //
  //
  //
  //    101     103     105      107      109
  //     ^       ^       ^        ^        ^
  //    |_|     |X|     |_|      |X|      |_|

  // OK, so you want to tell everyone in the
  // neighbourhood what order your are visiting
  // these houses.

  // The thing is, each of these houses has
  // real space coordinates right?

  // Vertex space coordinates of the center
  // of each home:

  // House 100:  ( 10, 40, 5 )
  // House 101:  ( 20, 40, 30 )
  // House 102:  ( 30, 40, 5 )
  // House 103:  ( 40, 40, 30 )
  // House 104:  ( 50, 40, 5 )
  // .. and so on ..

  // So you could EITHER tell everyone the
  // physical location (coordinates) of each
  // house you plan to visit:

  // I am visiting:
  // ( 10, 40, 5 ), ( 40, 40, 30 ) ...

  //     ^       ^       ^        ^        ^
  //    |X|     |_|     |X|      |_|      |X|
  //    100     102     104      106      108
  //       \            /  \             /
  //         \        /       \        /
  //            \  /             \  /
  //    101     103     105      107      109
  //     ^       ^       ^        ^        ^
  //    |_|     |X|     |_|      |X|      |_|
  //  

  // Or you could simply list the INDICES
  // ("house numbers") of the homes you
  // would like to hit.

  // Which would be:
  // 100, 103, 104, 107, 108

  // The house numbers are kind of like
  // indices ("indexes").

  // Does that kind of make sense?

  ////////////////////////
  // Lets talk more about this.

  // Now say you're crazy and you're going to fire
  // eggs at each of the houses who are participating
  // in Halloween this year using your patented
  // egg-launcher.

  // So, you have two options to pass the data
  // about the coordinates of the homes to hit
  // for your egg launcher.

  // You could:
  // 1.  Feed the egg launcher the coordinates of
  //      the homes to hit with eggs directly (looks like
  //      ( 20, 40, 30 )... etc)

  // OR 2.  Pass the egg launcher TWO arrays:
  // The array of "positions of each home" (akin to
  // a "vertex array"), and THEN, SEPARATELY, pass it
  // an array of "homes to hit, by index", (INDEX BUFFER).

  // The INDEX BUFFER basically lists the houses to HIT
  // by index, and the machine would know
  // to LOOK UP the physical coordinates of the
  // homes to hit FROM the "positions of each home" array
  // (the vertex buffer).

  // VERTEX BUFFER:
  // Positions of each home, stored in an array:
  // +---------------+----------------+---------------+
  // | ( 10, 40, 5 ) | ( 20, 40, 30 ) | ( 30, 40, 5 ) |
  // +---------------+----------------+---------------+
  //       100              101              102

  // INDEX BUFFER
  // Homes to hit (also stored in an array):
  // +-----+-----+
  // | 101 | 103 |
  // +-----+-----+
  //  

  // As you work through the INDEX BUFFER, firing
  // eggs at each home listed there,
  // to know the physical coordinates of a house
  // at fire time, you simply look it up in the
  // "VERTEX BUFFER",
  // which is that "positions of each home" array.

  // So, when drawing graphics, the VERTEX BUFFER
  // is a list of all the vertices that make up the model.
  // The INDEX BUFFER is the visitation order that the
  // GPU should use to "weave" triangle.

  // So you notice immediately that the INDEX BUFFER
  // way has a really great advantage.  Say you really
  // hate Mr. Smithers and you want his home hit (home 104) with
  // 2 eggs in a row.  Instead of passing the machine
  // data with repeated vertices like this:

  //  ( 20, 40, 30 ), ( 40, 40, 30 ), ( 50, 40, 5 ), ( 50, 40, 5 )

  // Your index buffer could look like this:
  //
  // +-----+-----+-----+-----+
  // | 101 | 103 | 104 | 104 |
  // +-----+-----+-----+-----+

  // So that's a bit more compact notation for
  // when a certain vertex is visited more than once
  // (which happens QUITE often when drawing a model!)

  // Include the fact that each vertex in D3D gets passed with
  // texture coordinates (2 floats) AND normals (3 floats),
  // and its easy to see where the savings comes in.

  ///////////////////////////
  // OK, last thing, then I'll get to the code, I promise.
  // The other thing that this is like is PEGS ON A KNITTING LOOM.
  // If you've seen one of those things, it has these pegs that
  // you weave the yarn about over and over again.  A loom might
  // have 15-20 pegs or so

  // 

  /*
            3 4 5
          2 * * * 6
        1 *       * 7
        *           *
        1 *       *
        3 1 * * * 8
          2 1 1 9
            1 0

    My sweet sweet knitting loom
    with sweet numbered pegs

  */

  // So to knit a sock or glove or something,
  // you might visit pegs in the following order:
  // 4, 10, 5, 11, 6, 12, 7, 13.. 

  // In this example, the actual PHYSICAL PEGS are
  // the VERTEX BUFFER (the pegs have
  // actual physical space coordinates don't they?)

  // The "winding order" or the order that I want you
  // to spin the yarn about the pegs, is NOT specified
  // by physical space coordinates (I'm not giving you the
  // physical distance to travel the yarn from peg 4 to peg 10,
  // I'm not saying "start at 4 cm horizontally from the top (4, 0, 0)
  // and connect with (4, -4, 0)).

  // Same goes for drawing using an index buffer.
  // INSTEAD of directly specifying physical Vertex positions
  // in space while drawing, we specify vertex positions
  // ONCE and store them off in a VERTEX BUFFER.

  // We THEN AFTER THAT create a SECOND array called
  // the INDEX BUFFER which specifies the ORDER TO
  // VISIT THE VERTICES THAT ARE ALREADY STORED IN
  // (and numbered off!) in that VERTEX BUFFER.

  // Each VERTEX actually has a position in space.
  // So we store the vertices as this array:

  // pointList ARRAY:
  //
  // +-----------------+-----------------+-----------------+
  // |   ( 1, 1, 1 )   |   ( 2, 5, 9 )   |   ( 8, 5, 7 )   |
  // +-----------------+-----------------+-----------------+
  //          0                 1                 2

  // This forms the VERTEX BUFFER.
  // (Each triplet in the array represents a vector)

  // So, to DRAW A TRIANGLE, between the points I'd specify
  // 0, 2, 1 in the INDEX BUFFER.

  // INDEX BUFFER ibData:
  //
  // +-------+-------+-------+
  // |   0   |   2   |   1   |
  // +-------+-------+-------+
  //     0       1       2

  // When the GPU goes to draw that
  // it puts the two together (the INDEX BUFFER values, which
  // reference the VERTEX BUFFER values) and so it would draw
  // a triangle between (1,1,1), (8,5,7), (2,5,9), winding it
  // in that EXACT order.

  //
  ////

  // Vars for rotating the tetrahedron when you run the program.
  float rot ;
  Vector3 axis ;

  BasicEffect renderer;

  VertexBuffer vb;
  VertexPositionColor[] pointList ;

  VertexDeclaration vd;

  IndexBuffer ib ;
  short[] ibData ;

  public Game1()
  {
    graphics = new GraphicsDeviceManager( this );
    Content.RootDirectory = "Content";

    this.IsMouseVisible = true ;

    axis = new Vector3( random( 0, 1 ), random( 0, 1 ), random( 0, 1 ) );
    axis.Normalize();
  }

  protected override void LoadContent()
  {
    /////
    // Initialization.
    // This is a really long set of steps we need to do
    // to load vertex data INTO THE GPU.

    // "preload" the GPU up with all the vertices
    // you want it to draw.

    // 1.  Initialize the renderer as a BasicEffect:
    renderer = new BasicEffect( GraphicsDevice, null );
    renderer.VertexColorEnabled = true;

    renderer.View = Matrix.CreateLookAt( -10 * Vector3.UnitZ, Vector3.Zero, Vector3.UnitY );
    renderer.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, 1.0f, 1.0f, 1000.0f );
    //
    //////

    //////
    // 2.  Next, we will create and set up a bunch of vertices
    // that we can draw.

    // Define the vertices of a tetrahedron and put them in
    // the vertex buffer.
    float edgeLength = 1.0f ;
    float a = edgeLength / (float)( 2.0 * Math.Sqrt( 2.0 ) );

    Vector3 topFront0 = v( a, a, a );
    Vector3 bottomFront1 = v( -a, -a, a );

    Vector3 topBack2 = v( -a, a, -a );
    Vector3 bottomBack3 = v( a, -a, -a );

    pointList = new VertexPositionColor[]
    {

      vpc( topFront0, Color.Black ),

      vpc( bottomFront1, Color.Red ),

      vpc( topBack2, Color.GreenYellow ),

      vpc( bottomBack3, Color.AliceBlue )

    };

    // Load verts onto GPU
    vb = new VertexBuffer(
      GraphicsDevice, VertexPositionColor.SizeInBytes * pointList.Length, BufferUsage.None
    );
    vb.SetData<VertexPositionColor>( pointList );

    // 3.  don't forget the vertex declaration
    vd = new VertexDeclaration(

      GraphicsDevice,
      VertexPositionColor.VertexElements

    );

    // And so there we have the 4 vertices of a tetrahedron.

    // But oh!  A tet has 4 vertices, and 4 FACES!  This means
    // we need to draw _4_ triangles between these 4 vertices.

    // Now normally, you might think, "well, ok, to draw these
    // 4 triangles, we'll just issue 4 triangle-draw calls, and
    // simply REPEAT/overlap a few of those vertex."

    // Nah son.  Repeating vertices?  Nah.

    // Instead, we'll use an INDEX BUFFER.  Refer to the
    // Halloween example above for a quick understanding
    // of what we're doing here.. we're basically going to
    // list out the VISITATION ORDER of the vertices that
    // we already specified above.  So we declare the
    // vertices ONE TIME, in ONE PLACE, and then we
    // say the order that these vertices should be
    // visited at draw time.

    // Sure beats having to specify the same vertex several
    // times over, but it means that each vertex can only have
    // ONE COLOR (so coloration will be less customizable).

    // 4.  Index buffer.  First define the order in which the vertices
    // (of the VERTEX BUFFER!) will be visited

    // How did I work out this visitation order?  Using Wikipedia and a pen and paper!

    ibData = new short[]{

      0, 3, 1,
      0, 1, 2,
      0, 2, 3,
      2, 1, 3

    };

    // Now set them down into the GPU.
    ib = new IndexBuffer( GraphicsDevice, typeof(short), ibData.Length, BufferUsage.None );

    ib.SetData<short>( ibData );

    //end setup
  }

  protected override void Update( GameTime gameTime )
  {
    // Esc. to exit.
    if( Keyboard.GetState().IsKeyDown( Keys.Escape ) )
      this.Exit();

    rot += (float)gameTime.ElapsedGameTime.Ticks / TimeSpan.TicksPerSecond ;
    renderer.World = Matrix.CreateFromAxisAngle( axis, rot ) ;

    base.Update( gameTime );
  }

  protected override void Draw( GameTime gameTime )
  {
    GraphicsDevice.Clear( Color.DarkGray );

    renderer.GraphicsDevice.VertexDeclaration = vd;
    renderer.GraphicsDevice.Indices = ib ;

    renderer.Begin(); // start rendering.

    foreach( EffectPass pass in renderer.CurrentTechnique.Passes )
    {
      pass.Begin();

      // Set the size of the points
      renderer.GraphicsDevice.RenderState.PointSize = 8.0f;

      // Tell the GPU what vertex array to pull vertex data from.
      renderer.GraphicsDevice.Vertices[ 0 ].SetSource( vb, 0, VertexPositionColor.SizeInBytes );

      // Actually draw.
      renderer.GraphicsDevice.DrawIndexedPrimitives(

        PrimitiveType.TriangleList,  // draw Tris, every 3 vertices
        // listed in the INDEX BUFFER are going to produce ONE TRIANGLE
        // on the screen.

        0, // ... start at the beginning of the list ...
        0, // lowest vertex index from VB that can be drawn

        pointList.Length,  // draw as many vertices as are specified by the VERTEX BUFFER (_NOT_ the INDEX buffer).
        // This is very important to get right, and it is a bit counter-intuitive.
        // Say we're drawing a tetrahedron (as we are in this case).  That shape
        // has 4 TRIANGLES drawn between 4 POINTS.. a total of 4 triangles
        // are drawn, so essentially that's 12 vertices that are visited.
        // HOWEVER, the value that gets passed here is __4__, NOT 12.
        // Because we visit 4 vertices, over and over again, to draw
        // the 4 triangles.

        0,
        ibData.Length / 3 // how many total triangles are we drawing?
        // we're hitting 12 vertices, to draw 4 triangles, so we
        // are drawing.. uh, 4 triangles.  DUH! :)

      );

      // end the rendering pass.
      pass.End();
    }

    // Stop rendering.
    renderer.End();

    ////
    base.Draw( gameTime );
  }

  #region aux
  /// <summary>Convenience</summary>
  public static Vector3 v( double x, double y, double z )
  {
    return new Vector3( (float)x, (float)y, (float)z );
  }

  /// <summary>Convenience.  After all, Shakespeare did say
  /// that brevity is the soul of with.  He didn't say anything
  /// about efficiency, however.</summary>
  public static VertexPositionColor vpc( Vector3 p, Color c )
  {
    return new VertexPositionColor( p, c );
  }

  public static Color c( double r, double g, double b )
  {
    return new Color( (float)r, (float)g, (float)b );
  }

  static Random rand = new Random();
  public static float random( float low, float high )
  {
    return MathHelper.Lerp( low, high, (float)rand.NextDouble() ) ;
  }
  #endregion

  static void Main( string[] args )
  {
    Game1 game = new Game1();
    game.Run();
  }
}

Post a Comment