52880.fb2
This tutorial builds on the topics and code from the previous tutorial. Make sure you have read Tutorial 1 before you continue.
All 3D shapes are made up from a number of polygons, normally triangles. Triangles are used because they are the most efficient polygons to draw. So, if you wanted to draw a square, it is more efficient to draw two triangles next to each other rather than one square. Therefore, this tutorial will show you how to create one of the building blocks of 3D graphics: a triangular polygon.
What is a vertex? Vertices are points in 3D space. For example, a triangle has three vertices and a square has four vertices. You can describe a triangle by specifying where its three vertices are. To do this you need to know about coordinates.
Below are two diagrams showing how the 2D Cartesian coordinate system works.
In the examples above, we have an x-axis and a y-axis. Along each of these axis are numbers starting from zero (at the origin) and increasing the further along the axis you go. So, to specify a single point, all you need is an x value and a y value (see fig 2.1). It follows then, to represent a triangle you need three of these points that are joined together, each with an x value and a y value (fig 2.2). Notice that when we write a coordinate, it is always in the form: (x, y).
Below are two diagrams showing how the left-handed 3D Cartesian coordinate system works.
Fig 2.3
Fig 2.4
As with the 2D coordinate system we have an x-axis and a y-axis. When we are dealing with 3D shapes and points we need an extra dimension – the z-axis. This axis works in the same way with numbers starting from zero at the origin and increasing the further along the axis you go. Now, with these three axis we can specify any point in 3D space. Notice that when we write coordinates in 3D space they are always in the form: (x, y, z).
What is a 3D primitive? Well, a 3D primitive is a collection of vertices that make up a 3D shape. There are six 3D primitives in Direct3D, you will use these primitives to draw your 3D shapes. Below are diagrams showing examples of these primitives:
Point Lists
A Flexible Vertex Format or FVF is a format for describing attributes of a vertex. We've already seen three attributes: x value, y value and z value. There are other attributes that we can specify for a vertex, such as, colour and shininess. Using FVFs we can configure which attributes we want specify for a vertex. When you specify a polygon in Direct3D, the polygon can be filled based on attributes of the vertices. The fill of the polygon is interpolated (blended) between vertices. In our example below, you will see that the three vertices of our polygon are all different colours: red, green and blue. These colours are blended together across the polygon.
A Vertex Buffer is a memory buffer for storing vertices. A vertex buffer can stored vertices of any format. Once your vertices are stored in a vertex buffer you can perform operations such as: rendering, transforming and clipping.
To represent colours in Direct X, we use the D3DCOLOR_XRGB macro. There are three parameters: Red, Green and Blue. These parameters are integer values between 0 and 255. By specifying different red, green and blue values (mixing colours) you can make any colour you need.
For example:
D3DCOLOR_XRGB(0, 0, 0) is black (no colour)
D3DCOLOR_XRGB(255, 255, 255) is white (full colour)
D3DCOLOR_XRGB(0, 255, 0) is bright green (no red, full green, no blue)
D3DCOLOR_XRGB(100, 20, 100) is dark purple (100 red, 20 green, 100 blue)
Here is the code for this tutorial. It's just the same as the code from the first tutorial, except for a few modifications:
#include <d3d8.h>
LPDIRECT3D8 g_pD3D = NULL;
LPDIRECT3DDEVICE8 g_pD3DDevice = NULL;
LPDIRECT3DVERTEXBUFFER8 g_pVertexBuffer = NULL; // Buffer to hold vertices
struct CUSTOMVERTEX {
FLOAT x, y, z, rhw; // The transformed position for the vertex.
DWORD colour; // The vertex colour.
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
#define SafeRelease(pObject) if(pObject != NULL) {pObject->Release(); pObject=NULL;}
HRESULT InitialiseD3D(HWND hWnd) {
//First of all, create the main D3D object. If it is created successfully we
//should get a pointer to an IDirect3D8 interface.
g_pD3D = Direct3DCreate8(D3D_SDK_VERSION);
if (g_pD3D == NULL) {
return E_FAIL;
}
//Get the current display mode
D3DDISPLAYMODE d3ddm;
if (FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm))) {
return E_FAIL;
}
//Create a structure to hold the settings for our device
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
//Fill the structure.
//We want our program to be windowed, and set the back buffer to a format
//that matches our current display mode
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC;
d3dpp.BackBufferFormat = d3ddm.Format;
//Create a Direct3D device.
if (FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pD3DDevice))) {
return E_FAIL;
}
return S_OK;
}
HRESULT InitialiseVertexBuffer() {
VOID* pVertices;
//Store each point of the triangle together with it's colour
CUSTOMVERTEX cvVertices[] = {
{250.0f, 100.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255, 0, 0),}, //Vertex 1 – Red (250, 100)
{400.0f, 350.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 255, 0),}, //Vertex 2 – Green (400, 350)
{100.0f, 350.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 0, 255),}, //Vertex 3 – Blue (100, 350)
};
//Create the vertex buffer from our device
if (FAILED(g_pD3DDevice->CreateVertexBuffer(3 * sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVertexBuffer))) {
return E_FAIL;
}
//Get a pointer to the vertex buffer vertices and lock the vertex buffer
if (FAILED(g_pVertexBuffer->Lock(0, sizeof(cvVertices), (BYTE**)&pVertices, 0))) {
return E_FAIL;
}
//Copy our stored vertices values into the vertex buffer
memcpy(pVertices, cvVertices, sizeof(cvVertices));
//Unlock the vertex buffer
g_pVertexBuffer->Unlock();
return S_OK;
}
void Render() {
if (g_pD3DDevice == NULL) {
return;
}
//Clear the backbuffer to black
g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
//Begin the scene
g_pD3DDevice->BeginScene();
//Rendering our triangle
g_pD3DDevice->SetStreamSource(0, g_pVertexBuffer, sizeof(CUSTOMVERTEX));
g_pD3DDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX);
g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
//End the scene
g_pD3DDevice->EndScene();
//Filp the back and front buffers so that whatever has been rendered on the back buffer
//will now be visible on screen (front buffer).
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}
void CleanUp() {
SafeRelease(g_pVertexBuffer);
SafeRelease(g_pD3DDevice);
SafeRelease(g_pD3D);
}
void GameLoop() {
//Enter the game loop
MSG msg;
BOOL fMessage;
PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
while (msg.message != WM_QUIT) {
fMessage = PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE);
if (fMessage) {
//Process message
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
//No message to process, so render the current scene
Render();
}
}
}
//The windows message handler
LRESULT WINAPI WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch(msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
case WM_KEYUP:
switch (wParam) {
case VK_ESCAPE:
//User has pressed the escape key, so quit
DestroyWindow(hWnd);
return 0;
break;
}
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
//Application entry point
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, INT) {
//Register the window class
WNDCLASSEX wc = {
sizeof(WNDCLASSEX), CS_CLASSDC, WinProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, "DX Project 2", NULL
};
RegisterClassEx(&wc);
//Create the application's window
HWND hWnd = CreateWindow("DX Project 2", "www.andypike.com: Tutorial 2", WS_OVERLAPPEDWINDOW, 50, 50, 500, 500, GetDesktopWindow(), NULL, wc.hInstance, NULL);
//Initialize Direct3D
if (SUCCEEDED(InitialiseD3D(hWnd))) {
//Show our window
ShowWindow(hWnd, SW_SHOWDEFAULT);
UpdateWindow(hWnd);
//Initialize Vertex Buffer
if (SUCCEEDED(InitialiseVertexBuffer())) {
//Start game running: Enter the game loop
GameLoop();
}
}
CleanUp();
UnregisterClass("DX Project 2", wc.hInstance);
return 0;
}
You should finish up with a window with a black background and a multi-coloured triangle in the middle (shown below).
So, there you have it. The second tutorial complete. We've learnt a lot in this tutorial: Vertices, Coordinated Systems, 3D Primitives, FVF, Vertex Buffers and specifying colours. To take this tutorial a bit further, try adding another triangle. In the next tutorial we will create our first real 3D object.