Code:
/* Triangle.cpp
Demonstration code showing how to draw a simple triangle using Direct3D.
See the site for details of each stage.
Written by Keith Ditchburn 2006
*/
#define VC_EXTRALEAN
#include <windows.h>
#include <d3d9.h> // Main Direct3D header
#include <d3dx9.h> // D3DX helper functions
// Forward declarations
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
bool InitialiseDirect3D(HWND hWnd);
void CloseDownDirect3D();
void RenderScene();
bool CreateTriangle();
void DrawTriangle();
void SetMatrices();
// Globals
LPDIRECT3D9 gD3dObject=NULL;
LPDIRECT3DDEVICE9 gD3dDevice=NULL;
LPDIRECT3DVERTEXBUFFER9 gVertexBuffer=NULL;
// My very own vertex structure
struct TMyVertex
{
D3DXVECTOR3 p; // Vertex position
D3DCOLOR c; // Vertex colour
};
// Flag used to describe the above (Flexible Vertex Format) structure to Direct3D
// so Direct3D knows the type of data sent to it per vertex
#define MYVERTEX_FVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)
// The main entry point of our Windows program
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// Create and fill out a window class structure
WNDCLASS wcex;
ZeroMemory(&wcex,sizeof(WNDCLASS));
// Provide the address of the function Windows should call with messages
wcex.lpfnWndProc= (WNDPROC)WndProc;
wcex.style= CS_HREDRAW | CS_VREDRAW;
wcex.hInstance= hInstance;
wcex.hCursor= LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground= (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszClassName= "MyWindowClass";
// Register my new window class with the Windows API
RegisterClass(&wcex);
// Create the window
HWND hWnd = CreateWindow("MyWindowClass", "D3D Triangle Demo",
WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,
NULL, NULL, hInstance, NULL);
if (hWnd==0)
return -1;
// Set up Direct3D
if (!InitialiseDirect3D(hWnd))
return -1;
// Create our triangle
if (!CreateTriangle())
return -1;
// Set our matrices
SetMatrices();
// Show the window for the first time
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// Windows sends some messages directly to your callback function but others are placed in
// a queue. These tend to be keyboard messages or other input that may need translating. We
// enter a loop that checks for queued messages. If one is found it is translated and dispatched
// to our windows procedure. Once the application is closed a WM_QUIT message is received.
// When there are no messages to handle we draw our scene. This makes our program 'badly behaved'
// as we are hogging system resources so I tend to put in a sleep(0) that lets other threads have
// at least some time.
MSG msg;
ZeroMemory( &msg, sizeof(msg) );
while( msg.message!=WM_QUIT )
{
if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
RenderScene();
Sleep(0);
}
}
// Clean up before exiting
CloseDownDirect3D();
return (int)msg.wParam;
}
// Callback message handler function for our window
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
// We do not want to handle this message so pass it back to Windows
// to handle it in a default way
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return 0;
}
// Set up Direct3D
// Requires creation of the Direct3D object and the Direct3D device
bool InitialiseDirect3D(HWND hWnd)
{
// Create the main Direct3D object. This object is used to initialise Direct3D
// and to provide us with a device interface object
gD3dObject=Direct3DCreate9(D3D_SDK_VERSION);
if (gD3dObject==0)
return false;
// Fill out the parameters for our required device
D3DPRESENT_PARAMETERS presParams;
ZeroMemory(&presParams,sizeof(presParams));
presParams.Windowed=TRUE;
presParams.SwapEffect=D3DSWAPEFFECT_DISCARD;
presParams.BackBufferFormat=D3DFMT_UNKNOWN;
presParams.PresentationInterval=D3DPRESENT_INTERVAL_ONE;
// Get the Direct3D object to give us a pointer to a device interface object
// this device represents the graphics capabilities of the PC
HRESULT hr=gD3dObject->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING,&presParams,&gD3dDevice);
if (FAILED(hr))
{
// If we could not get a hardware vertex processing device fallback to a software one
hr=gD3dObject->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,&presParams,&gD3dDevice);
if (FAILED(hr))
return false;
}
// Since we are providing a colour component per vertex we do not
// want Direct3D lighting to be on
gD3dDevice->SetRenderState(D3DRS_LIGHTING,FALSE);
return true;
}
// Release all the Direct3D interfaces in reverse order to their creation
// All Direct3D objects implement a Release interface
void CloseDownDirect3D()
{
if (gVertexBuffer)
{
gVertexBuffer->Release();
gVertexBuffer=NULL;
}
if (gD3dDevice)
{
gD3dDevice->Release();
gD3dDevice=NULL;
}
if (gD3dObject)
{
gD3dObject->Release();
gD3dObject=NULL;
}
}
/* There are 3 matrices using by Direct3D
World Matrix - Transforms 3D data from Model Space into World Space.
You need to set this before rendering every entity in your world.
View Matrix - Transforms from World Space into View Space.
You need to set this each time your camera changes position
Projection Matrix - Transforms from View Space into Screen Space.
You normally set this just once during initialization.
*/
void SetMatrices()
{
// Projection Matrix
D3DXMATRIX matProj;
FLOAT fAspect=((FLOAT)800)/600;
D3DXMatrixPerspectiveFovLH(&matProj,D3DX_PI/4,fAspect,1.0f,100.0f);
gD3dDevice->SetTransform(D3DTS_PROJECTION,&matProj);
// View Matrix
// Set the camera back a bit along the Z axis and looking down Z
D3DXMATRIX matView;
D3DXVECTOR3 vEyePt(0.0f,0.5f,-2.0f );
D3DXVECTOR3 vLookatPt(0.0f,0.5f,0.0f );
D3DXVECTOR3 vUpVec(0.0f,1.0f,0.0f );
D3DXMatrixLookAtLH(&matView,&vEyePt,&vLookatPt,&vUpVec );
gD3dDevice->SetTransform(D3DTS_VIEW,&matView );
// World Matrix
// Just using identity (so triangle is at origin of world)
D3DXMATRIX matWorld;
D3DXMatrixIdentity(&matWorld);
gD3dDevice->SetTransform(D3DTS_WORLD, &matWorld );
}
// In this function we create our triangle by creating and filling a vertex buffer
bool CreateTriangle()
{
HRESULT hr=gD3dDevice->CreateVertexBuffer(3*sizeof(TMyVertex),D3DUSAGE_WRITEONLY, MYVERTEX_FVF,
D3DPOOL_MANAGED, &gVertexBuffer, NULL );
if (FAILED(hr))
return false;
// Lock and fill
TMyVertex* pVertices=NULL;
hr=gVertexBuffer->Lock(0,0,(void**)&pVertices,0);
if (FAILED(hr))
return false;
// Fill in position and colour values for our 3 vertices
// It is essential that the vertices are defined in clockwise order
pVertices[0].p=D3DXVECTOR3(-1.0f,0.0f,0.0f);
pVertices[0].c=D3DCOLOR_XRGB(255,0,0); // Red
pVertices[1].p=D3DXVECTOR3(-1.0f,1.0f,0.0f);
pVertices[1].c=D3DCOLOR_XRGB(0,255,0); // Green
pVertices[2].p=D3DXVECTOR3(+1.0f,0.0f,0.0f);
pVertices[2].c=D3DCOLOR_XRGB(0,0,255); // Blue
gVertexBuffer->Unlock();
return true;
}
// Draw the scene
void RenderScene()
{
// Clear the back buffer to black
gD3dDevice->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);
// Note: all drawing must occur between the BeginScene and EndScene calls
if (SUCCEEDED(gD3dDevice->BeginScene()))
{
// Draw out triangle
DrawTriangle();
// Tell Direct3D that we have finished sending it 3D data
gD3dDevice->EndScene();
}
// Indicate that the scene should be shown
gD3dDevice->Present(0,0,0,0);
}
// Draw the triangle
void DrawTriangle()
{
// To draw we pass the vertices down a stream. We set the source as our vertex buffer
gD3dDevice->SetStreamSource(0,gVertexBuffer,0,sizeof(TMyVertex));
// We then tell Direct3D the make up of the vertices
gD3dDevice->SetFVF(MYVERTEX_FVF);
// Now draw our one triangle
gD3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST,0,1);
}