Drawing a Square with Direct3D

Back to Direct3D Tutorial Index

Welcome to the second in my series of Direct 3D tutorials. Today I am going to cover
the creation of geometric shapes using Direct 3D. By the end of this tutorial, you
will be able to create a square, specifying the places in which the vertices are
drawn and the colour allocated to each vertex.

This tutorial is going to expand upon the knowledge we gained in my previous tutorial.

---------------------------------------------------------------------------------------------------
Section 1: Headers and Global Variables

The header files for this tutorial, along with the library file needed are going to
stay exactly the same.

Our global variables on the other hand, are going to have a few additions.

First off, for use of the Direct 3D Vertex Buffer which handles our geometric points,
we need to set up the Direct 3D Vertex Buffer device. This is done with:
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;

Now that we have a storage class for our Vertex Buffer, we need to set up a struct
which will hold our vertex data. That struct will be called CUSTOMVERTEX and
take the values for the x,y and z positions of the vertices along with the rhw and color
values.

The RHW value, stands for Reciprocal, homogenous vertices divided by w. Which means that
the vertices are transformed in homogeneous space and the x,y and z values are divided by
w. So we set this to 1 and our x,y and z coordinates look like: x/1, y/1, z/1, which keeps
them as they are.

struct CUSTOMVERTEX
{
float x, y, z, rhw;
DWORD color;
};

Our last Global Variable will define our CUSTOMVERTEX and set what type it will be.
I have set mine up like this:
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
Which sets the vertex data to x,y,z,rhw and the DIFFUSE call allows us to set the
color for the vertices.

---------------------------------------------------------------------------------------------------
Section 2: Initializing the Vertex Buffer

To initialize our vertex buffer, I am calling the function initVBuffer which does not
take any parameters.
void initVBuffer (void)
{

For our square, I am creating an array called Square which is a CUSTOMVERTEX type.
CUSTOMVERTEX Square[] =
{

Next I am setting up our four vertices. Unlike OpenGL, Direct 3D works with either
lines or triangles and supports either triangles, triangle strips or triangle fans
depending on what you need them for. I have set mine up to use triangle strips, so
I have arranged my vertices goin from bottom left, to top left, to bottom right, to
top right. So this will create 2 triangles using 4 vertices to create our square.
I have also set my color 2 different ways. The first 3 use a Direct 3D call to convert
our Red, Green and Blue data to hex data which it will then read. The last
show that you can use hex data straight out.
{ 100, 200, 0.5, 1, D3DCOLOR_XRGB(255,0,0), }, RED
{ 100, 100, 0.5, 1, D3DCOLOR_XRGB(0,0,255), }, BLUE

{ 200, 200, 0.5, 1, D3DCOLOR_XRGB(255,255,0), }, YELLOW
{ 200, 100, 0.5, 1, 0xff00ff00, }, GREEN
};

Now I am creating our vertex buffer which will also allocate enough memory for each vertex,
I have four vertices, hence I need to times the size of my CUSTOMVERTEX by 4.
g_pd3dDevice->CreateVertexBuffer( 4*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL );

Next I need to use a temporary array for the memory allocation of my vertices.
This will be called pVertices.
void* pVertices;
After I have my temporary vertex holder, I am going to lock my Vertex Buffer
setting it to the size of my Square array.
g_pVB->Lock( 0, sizeof(Square), (void**)&pVertices, 0 );

I am then copying the needed memory for the Square to my pVertices holder.
memcpy( pVertices, Square, sizeof(Square) );

And finally Unlocking the Vertex Buffer for later use.
g_pVB->Unlock();
}

---------------------------------------------------------------------------------------------------
Section 3: Cleanup

Because we have added a new device to our program, we now need to include it in our
Cleanup function so that we may efficiently exit the program. To do this I have added
the following lines to the top of the function before the other cleanup is done:
if( g_pVB != NULL )
{
g_pVB->Release();
}


---------------------------------------------------------------------------------------------------
Section 4: Rendering
In our render function, we now need to add the code to draw our object.
This code will go between the BeginScene and EndScene calls, and will look like:

This first call will ser our Stream source to our Vertex Buffer, the size of our CUSTOMEVERTEX
struct.
g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof(CUSTOMVERTEX) );

Next I need to set our rendering device to that of the D3DFVF_CUSTOMVERTEX so
that Direct 3D knows to start working with our vertices.
g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );

Then we tell Direct 3D to draw our shape. I have set it here to use Triangle Strips,
starting at our first (0) vertex and drawing 2 shapes. The 2 being the 2 triangles that
make up our square. So this call goes, Type, Starting Vertex, Number of Primitives (shapes).
g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );

---------------------------------------------------------------------------------------------------
Section 5: Calling our initVBuffer function

To call our initVBuffer function, we need to use the line:
initVBuffer();
placed below our init function. Both of which will be inside our WinMain function.

---------------------------------------------------------------------------------------------------
And there we have it. You should be able to run this and see a square with a different color
per vertex.
If you have any questions in regard to this tutorial, please email me at swiftless@gmail.com

#include <dx/d3d9.h>

#pragma comment(lib,"d3d9.lib")

LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;

struct CUSTOMVERTEX
{
float x, y, z, rhw;
DWORD color;
};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)

void init( HWND hWnd )
{

g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );

D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice );

}

void initVBuffer (void)
{
CUSTOMVERTEX Square[] =
{
{ 100, 200, 0.5, 1, D3DCOLOR_XRGB(255,0,0), },
{ 100, 100, 0.5, 1, D3DCOLOR_XRGB(0,0,255), },

{ 200, 200, 0.5, 1, D3DCOLOR_XRGB(255,255,0), },
{ 200, 100, 0.5, 1, 0xff00ff00, },
};

//number * sizeof must be number of vertices
g_pd3dDevice->CreateVertexBuffer( 4*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL );

void* pVertices;
g_pVB->Lock( 0, sizeof(Square), (void**)&pVertices, 0 );

memcpy( pVertices, Square, sizeof(Square) );

g_pVB->Unlock();
}

void Cleanup (void)
{

if( g_pVB != NULL )
{
g_pVB->Release();
}

if( g_pd3dDevice != NULL)
{
g_pd3dDevice->Release();
}

if( g_pD3D != NULL)
{
g_pD3D->Release();
}
}

void Render (void)
{
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1, 0 );

g_pd3dDevice->BeginScene();

g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof(CUSTOMVERTEX) );

g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );

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

g_pd3dDevice->EndScene();

g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}

LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
Cleanup();
PostQuitMessage( 0 );
return 0;

case WM_PAINT:
Render();
ValidateRect( hWnd, NULL );
return 0;
}

return DefWindowProc( hWnd, msg, wParam, lParam );
}

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, "A Basic Window", NULL };
RegisterClassEx( &wc );

HWND hWnd = CreateWindow( "A Basic Window", "A Basic Window", WS_OVERLAPPEDWINDOW, 100, 100, 500, 500, NULL, NULL, wc.hInstance, NULL );

init( hWnd );

initVBuffer();

ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );

MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}

UnregisterClass( "A Basic Window", wc.hInstance );
return 0;
}

Download C++ Source Code for this Tutorial

     

 

Copyright 2008, Donald Urquhart
Proudly supported by http://www.cdadc.com