DirectXTutorial.com
The Ultimate DirectX Tutorial
Lesson 7:  Simple Modeling
Log In
Lesson Overview

So far we have only built single triangles.  Sometimes (such as in the last lesson) we have gotten advanced and drawn two triangles, but they were not connected.

3D models are made out of many triangles connected together to form geometry.  In this lesson, we will cover how to build some simple geometry out of triangles, and how to move them, rotate them and size them as a whole.

Building a Quad

Quad, as we should all know, means four.  In geometry, a quad is a four-sided shape of any kind.  The following are each quads.

Image 7.1 - Various Quads

Image 7.1 - Various Quads

Earlier I said that triangles make up all shapes in a 3D world.  If you look carefully, each of these quads is made up of two triangles placed side by side.

Image 7.2 - Quads Are Dual Triangles

Image 7.2 - Quads Are Dual Triangles

As a matter of fact, this is true of all quads, regardless of shape.  These two triangles combined can make quite a variety of useful shapes in game programming, such as building terrain, walls, boxes, and any other shape with four sides.

The prime question is, how do you make them?  We are already familiar with building triangles, so now let's look at how that code could be changed to draw a square instead.  First, let's look at the init_graphics() function we were using earlier.  This time, it has been changed to represent a quad, rather than a triangle.  Notice there are four points here.  The changes, as usual, are in bold.

// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        { -3.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 3.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { -3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0), },
        { 3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },
    }; 

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(4*sizeof(CUSTOMVERTEX),    // change to 4, instead of 3
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}

Now we have a full square in memory.  Now how do we show it?  Let's take a look at the code that draws them from the render_frame() function.

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

Of course, there is also code to set up the pipeline, but that isn't relevant here.  What is relevant is what is different about the DrawPrimitive() function.  Notice that the first parameter now says D3DPT_TRIANGLESTRIP rather than D3DPT_TRIANGLELIST.  Also notice that the third parameter was changed to a 2.

In case you are foggy on what each of these parameters do, the first parameter says what kind of primitive is being drawn (see Lesson 3), the second parameter says what vertex in the buffer should be started with (0 is the first vertex), and the third parameter says how many primitives should be drawn.  In a triangle strip, the primitives are triangles, so a 2 means show up to 2 triangles, and stop.  Well, that's the end of the vertex buffer anyway, but if we had put a 1 here, it would have stopped at one triangle.  This is illustrated here:

Image 7.3 - Making Triangles

Image 7.3 - Making Triangles

And that's all there is to building a quad!  Now let's combine our quads to make a cube, our first 3D model!

Combining Quads To Make a Cube

Combining quads to make a cube is actually simple.  If you haven't figured it out yet, pause for a moment to think about how you might go about it.

If you still can't think of it, take a look at this code.  If you can think of it, take a look at the code anyway, as building a cube in your head is far more complex than it seems at first glance (unless you're a cyborg, in which case you probably aren't reading a third of what I write anyhow).

This first piece of code is in the init_graphics() function:

// create the vertices using the CUSTOMVERTEX struct
CUSTOMVERTEX t_vert[] =
{
    // side 1
    { -3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 2
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 3
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 4
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 5
    { 3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 6
    { -3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 255), },
};

// create a vertex buffer interface called t_buffer
d3ddev->CreateVertexBuffer(24*sizeof(CUSTOMVERTEX),
                           0,
                           CUSTOMFVF,
                           D3DPOOL_MANAGED,
                           &t_buffer,
                           NULL);

Notice that there is one quad for each side of the cube.  Each set of four vertices represents one quad.

Now how do we draw all this.  It can't be one long triangle strip, because, if you look, there are repeating vertices.  In order to make things much simpler, we split the cube into six quads, meaning that we have to draw them separately (or the triangle strip would draw all over the place instead of in a straight cube, try it out).

Anyway, here's how you draw multiple quads from the same vertex buffer.

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 8, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 12, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 16, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 20, 2);

If you put this into your program, you'll get a nice rotating cube (provided you still have the right transforms there).  Here's a picture of a cube I got with one transform:

Image 7.4 - A Rotating Cube

Image 7.4 - A Rotating Cube

Try building a cube of your own and spinning it in various ways.

More Shapes

Well, I don't know about you, but I'm really excited!  We've built our first 3D object for real!  Let's look at a couple more simple models we can make using quads and triangles.


A Square Pyramid

Let's look at how to build this shape.

Image 7.5 - A Square Pyramid

Image 7.5 - A Square Pyramid

Here is the code for the vertex buffer:

// create the vertices using the CUSTOMVERTEX
struct CUSTOMVERTEX t_vert[] =
{
    // base
    { -3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // triangle 1
    { -3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // triangle 2
    { 3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // triangle 3
    { 3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // triangle 4
    { -3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
};

// create a vertex buffer interface called t_buffer
d3ddev->CreateVertexBuffer(16*sizeof(CUSTOMVERTEX),
                           0,
                           CUSTOMFVF,
                           D3DPOOL_MANAGED,
                           &t_buffer,
                           NULL);

And then to draw the shape:

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);    // base
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 1);    // triangle 1
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 7, 1);    // triangle 2
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 10, 1);    // triangle 3
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 13, 1);    // triangle 4

Take a moment to study how these commands differ from the cube's commands.


The Hypercraft

Now let's do something a little bit more exciting.  We'll take a break from boring geometry and get into some real stuff.  Let's build a "Hypercraft", which is a ship from the first 3D game I ever wrote (and I've used ever since).  Here's what this ship looks like:

Image 7.6 - The Hypercraft

Image 7.6 - The Hypercraft

Here's the code for the vertex buffer:

// create the vertices using the CUSTOMVERTEX
struct CUSTOMVERTEX t_vert[] =
{
    // fuselage
    { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // left gun
    { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

    // right gun
    { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

    // bottom of fuselage
    { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },
    { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },

    // opposite side of left gun
    { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // opposite side of right gun
    { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
};

// create a vertex buffer interface called t_buffer
d3ddev->CreateVertexBuffer(20*sizeof(CUSTOMVERTEX),
                                     0,
                                     CUSTOMFVF,
                                     D3DPOOL_MANAGED,
                                     &t_buffer,
                                     NULL);

And then to draw the shape:

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);    // fuselage
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 1);    // left gun
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 7, 1);    // right gun
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 10, 2);    // bottom of fuselage
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 14, 1);    // other side of left gun
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 17, 1);    // other side of right gun

And there you have it!  We've got a functioning 3D model of a spaceship.  Admittedly it could improve a little on the aerodynamics, but we'll get there for sure.  For now, let's put in a rotation matrix and see what she looks like.

The Finished Program

I chose to use the Hypercraft model for this demo.  It rotates around much like a car advertisement, only it looks somewhat more polygonal.

[Show Code]

Run the code, and see the family vehicle of the future!  Well, maybe the virtual future.  Well, maybe not...

Image 7.7 - The Hypercraft In Action

Image 7.7 - The Hypercraft In Action
Summary

Wow!  Now we are getting somewhere with 3D!  Get good at everything up to this point, and then let's move on and make the Hypercraft look decent with some cool-looking textures.  In the meantime, try doing these exercises:

1.  Make a square
2.  Make a triangular pyramid
3.  Draw 20 copies of your triangular pyramid on the screen at once
4.  Add a tail fin in the back of the Hypercraft
5.  Design and build your own mini-spaceship

Now let's get on to textures!  Let's make this Hypercraft look cool!

Next Lesson:  Adding Textures

GO! GO! GO!