This tutorial assumes you know some about Windows API, directX, and C++. Let's dive in!
Like previous sections we import the needed files and libraries (directX files must be linked or pragma'ed in like below and define what a key up and key down is so the ESC key can exit our program:
// include the basic windows header files and the Direct3D header file #include <windows.h> #include <d3d9.h> #include <d3dx9.h> // define the screen resolution and keyboard macros #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 #define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0) #define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1) // include the Direct3D Library files #pragma comment (lib, "d3d9.lib") #pragma comment (lib, "d3dx9.lib")
Now that is out of the way I will reiterate the basics of the Windows program here in code so that we can move on to the purpose of this tutorial:
We define pointers to the directX object so we can manipulate it as we see fit:
// global declarations LPDIRECT3D9 d3d; // the pointer to our Direct3D interface LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL; // the pointer to the vertex buffer // function prototypes void initD3D(HWND hWnd); // sets up and initializes Direct3D void render_frame(void); // renders a single frame void cleanD3D(void); // closes Direct3D and releases memory void init_graphics(void); // 3D declarations
Remember that we had a vertex buffer for the vertices as well? DirectX has to have a place to hold all of the coordinate data before drawing it to the screen so we create a buffer for that (Again, most everything in directX needs a buffer for something). Also included are the function prototypes we will be using in this segment of code. They are really self explanatory: one sets up all the directX stuff we need, the other is our render frame every tick method and the last one declares all of our graphics we need.
Here is the main loop from the entry of a Windows program. Note it calls the init function then goes into the render frame method every cycle:
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
// wc.hbrBackground = (HBRUSH)COLOR_WINDOW; // not needed any more
wc.lpszClassName = "WClass";
RegisterClassEx(&wc);
hWnd = CreateWindowEx(NULL, "WClass", "Direct3D Program",
WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
// set up and initialize Direct3D
initD3D(hWnd);
// enter the main loop:
MSG msg;
while(TRUE)
{
DWORD starting_point = GetTickCount();
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
render_frame();
// check the 'escape' key
if(KEY_DOWN(VK_ESCAPE))
PostMessage(hWnd, WM_DESTROY, 0, 0);
while ((GetTickCount() - starting_point) < 25);
}
// clean up DirectX
cleanD3D();
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
Basic WinAPI crap that I don't particularly care for since it detracts from our mission. Nevertheless we now have all the frame work we need to do much anything with directX. (People who have read the previous tutorials will notice the lack of all the extra files, i thought it would be best to cut down on the OOP of the file segregation and focus on the code for now).
In order to define our points and matrixes we must first define our own custom vertexs and color schemes:
struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)
DirectX sends coordinates through its pipeline to transform 2D points into a 3D representation of the object.
We must now define our data and initlize directX for this program's use:
// this function initializes Direct3D
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = FALSE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = SCREEN_WIDTH;
d3dpp.BackBufferHeight = SCREEN_HEIGHT;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
// create a device class
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
init_graphics(); // call the function to initialize the triangle
d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE); // turn off the 3D lighting
d3ddev->SetRenderState(D3DRS_ZENABLE, FALSE); // turn on the z-buffer
return;
}
As you can see the pointer is utilized and all major things we need for this code is set up here. Initialization is one of my favorite words
The Z-buffer is the equivalent of a depth reader. Just like with sprites with a Z-order, the Z-buffer tells directX how objects relate to each other in 3D space. It allows objects top rotate around another, behind, in front, etc... (Since we are only drawing one triangle the Z-Buffer will be set to FALSE, but its there in case you want/need it).
Before looking at the render_frame part that does all the cool work let us look at how Direct3D cleans up after itself (with our help of course!) and the init_graphics which define our matrixes we are working with:
// this is the function that cleans up Direct3D
void cleanD3D(void)
{
t_buffer->Release(); // close and release the vertex buffer
d3ddev->Release(); // close and release the 3D device
d3d->Release(); // close and release Direct3D
return;
}
// 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[] =
{
{ 2.5f, -3.0f, 0.0f, D3DCOLOR_XRGB(100, 0, 0), },
{ 0.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 100), },
{ -2.5f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 100, 0), },
};
// create a vertex buffer interface called t_buffer
d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
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;
}
All allocated memory items must be freed to avoid a memory leak and the init graphics set up triangles for use to manipulate in 3D space
Notice that we multiply the size of the custom vertex by '3'--that's how we know we are going to get a triangle (triangles have 3 sides if you haven't gotten it by now). You can play around with the colors, I made them darker cause i'm tired of seeing the same three prong colored triangle all the time
Here we are at the function of the hour---render_frame()! We set up the matrix(es). Now what we haven't discussed is the various things matrixes can do. There is the camera (how you view the 3D world), the rotation, and various transformations. We'll only worry about the camera and rotation in this tutorial.
// single frame render function
void render_frame(void)
{
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
d3ddev->BeginScene();
// select which vertex format we are using
d3ddev->SetFVF(CUSTOMFVF);
// SET UP THE PIPELINE
D3DXMATRIX matRotateY; // a matrix to store the rotation information
static float index = 0.0f; index+=0.05f; // an ever-increasing float value (rotation)
// build a matrix to rotate the model
D3DXMatrixRotationY(&matRotateY, index);
// tell Direct3D about our matrix
d3ddev->SetTransform(D3DTS_WORLD, &matRotateY);
D3DXMATRIX matView; // the view transform matrix
D3DXMatrixLookAtLH(&matView,
&D3DXVECTOR3 (0.0f, 0.0f, 10.0f), // the camera position
&D3DXVECTOR3 (0.0f, 0.0f, 0.0f), // the look-at position
&D3DXVECTOR3 (0.0f, 1.0f, 0.0f)); // the up direction
d3ddev->SetTransform(D3DTS_VIEW, &matView); // set the view transform to matView
D3DXMATRIX matProjection; // the projection transform matrix
D3DXMatrixPerspectiveFovLH(&matProjection,
D3DXToRadian(45), // the horizontal field of view
(FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
1.0f, // the near view-plane
100.0f); // the far view-plane
d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection
// select the vertex buffer to display
d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));
// copy the vertex buffer to the back buffer
d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
d3ddev->EndScene();
d3ddev->Present(NULL, NULL, NULL, NULL);
return;
}
There is a matrix to hold and store data about EVERYTHING! Camera position, look at position, how fast to rotate the triangle, etc... The 3D Math with this can get complicated, but this shows a neat example without too much linear algebra/calculus. Hope you learned a bit about matrixes from this tutorial. I'll do meshes next hopefully. I'm still working on getting them to load correctly (to compile this sample put all the code into one file, or more if you prefer).
Remember to have the DirectX SDK installed and have all the appropriate files linked or pragma'ed in. Happy Coding!



MultiQuote

|