|
|
|
|
Adding some color and the Z-Buffer |
My thanks to Richard Sharpe for porting this chapter to C++!
You might already have a rotating terrain, but it definitely would be better looking filled with some colors instead of just plain white lines. One idea to do this, is to use natural colors, like the ones that we find in the mountains. At the bottom we have blue lakes, then the green trees, the brown mountain and finally snow topped peaks. To keep this tutorial general for every image, you can’t expect every image to have a lake at height 0, and a mountain peak at height 255 (the maximum value for a .bmp pixel). Imagine an image with height values only between 50 and 200, this image would then probably produce a terrain without any lakes or snow topped peaks. To stay general, we first have to detect the minimum and maximum heights in our image. We will store these in the following global variables, which we initialize with the opposite values:
short unsigned int MinimumHeight = 255; short unsigned int MaximumHeight = 0;
When you run your program, you are now going to check if the current pixel’s height is below the current MinimumHeight or above the current MaximumHeight so we can work out what the true Minimum and Maximum values are. We are going to do this in a new function
void SetHeightLimits(void) //new { std::ifstream f_DataFile; short unsigned int dummy; f_DataFile.open("heightmap.bmp", std::ios::binary); if (f_DataFile.is_open()) {
for (int i = 0;i<
(offset);i++) { dummy = f_DataFile.get(); }
for (int x=0;x<
WIDTH;x++) {
for (int y=0; y<
HEIGHT;y++) { int height = f_DataFile.get(); height += f_DataFile.get(); height += f_DataFile.get(); height /= 8;
if (height < MinimumHeight) { MinimumHeight = height; } if (height > MaximumHeight) { MaximumHeight = height; } } } } }
of course we need to call this function so near the beginning of your WinMain function add
SetHeightLimits();
With these variables filled, you can specify the 4 regions of your colors:

Now when you declare your vertices and their colors in the VertexDeclaration method, you are going to define the desired colors to the correct height regions as follows:
if (height < MinimumHeight + (MaximumHeight - MinimumHeight) /4) //new { cv_Vertices[y*WIDTH + x].color = 0xff0000ff; } else if (height < MinimumHeight + (MaximumHeight - MinimumHeight) * 2/4) { cv_Vertices[y*WIDTH + x].color = 0xff228b22; } else if (height < MinimumHeight + (MaximumHeight - MinimumHeight) * 3/4) { cv_Vertices[y*WIDTH + x].color = 0xffa52a2a; } else { cv_Vertices[y*WIDTH + x].color = 0xffffffff; }
When your run this code, you will indeed see a nicely colored network of lines. When we want to see the whole colored terrain, we just have to remove this line :
p_dx_Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
When you execute this, take a few moments to rotate the terrain a couple of times. Pay special attention to the middle area. You will see that sometimes the middle peaks get overdrawn by the ‘invisible’ lake behind it. This is simply because we have not yet defined a ‘Z-buffer’! This is nothing more than an array where your video card keeps track of the depth coordinate of every pixel that should be drawn on your screen (so in our case, a 500x500 matrix!). Every time your card receives a triangle to draw, it checks whether the triangle’s pixels are closer to the screen than the pixels already present in the Z-buffer. If they are closer, the Z-buffer’s contents is updated with these pixels for that region. Of course, this whole process if fully automated. All you have to do is to define the Z-buffer when you create your device. To do this, add these 2 lines to your PresentParameters :
dx_PresParams.EnableAutoDepthStencil = TRUE; dx_PresParams.AutoDepthStencilFormat = D3DFMT_D16;
Here you create a Z-buffer with a precision of 16 bits. What this means in short: Every distance is presented between 0 and 1, with 0 being the near plane (1f in our case) and 1 being the far plane (250f in our case). With 16 bits, you have 2^16 = 65536 possible distances between them. Most cards support 32 bits, but my guess is 16bit precision will be more than enough for our application. The last line activates the previously defined Z-buffer. Run this code. Not as expected, indeed. What could be wrong? We didn’t initialize our buffer. Imagine the whole buffer filled with zeroes. When we would draw a triangle, it would be further away from the viewer than what was previously defined in the Z-buffer, so our triangle is discarded. So in fact, we have to first fill our buffer with ones. To do this automatically every update of our screen, start with it in the device.Clear method :
p_dx_Device->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
Now you’ll see the terrain rotating as expected!
Note: you don't seem to need this next step (I assume it is set to true by default) but it doesn't hurt to add it.
To force enabling of the Zbuffer add the following line to the end of the InitializeDevice function
p_dx_Device->SetRenderState(D3DRS_ZENABLE, true);

Click here to go to the forum on this chapter!
Although now we’ve got some colors in our terrain, it doesn’t really look any better than it did last chapter.. What we need at this moment are shadows. No shadows without lights, so let’s move to the next chapter!
The code for this chapter:
#include<windows.h>
#include<d3d9.h>
#include<d3dx9.h>
#include<dinput.h>
#include<fstream>
int set_offset (void); int set_height (void); int set_width (void);
short unsigned const int offset = set_offset(); short unsigned const int WIDTH = set_width(); short unsigned const int HEIGHT = set_height();
short unsigned int MinimumHeight = 255; //new short unsigned int MaximumHeight = 0; //new
struct OURCUSTOMVERTEX { float x,y,z; DWORD color; };
int int_AppRunning = 1;
float flt_Angle = 0;
int set_offset (void) { std::ifstream f_DataFile; short unsigned int offset; short dummy;
f_DataFile.open("heightmap.bmp", std::ios::binary); for (int i = 0; i < 10; i++) { dummy = f_DataFile.get(); }
offset = f_DataFile.get(); offset += f_DataFile.get()*256; offset += f_DataFile.get()*256*256; offset += f_DataFile.get()*256*256*256;
f_DataFile.close(); return offset; }
int set_height (void) { std::ifstream f_DataFile; short unsigned int HEIGHT; short dummy;
f_DataFile.open("heightmap.bmp", std::ios::binary);
if (f_DataFile.is_open()) { for (int i = 0; i < 22; i++) { dummy = f_DataFile.get(); }
HEIGHT = f_DataFile.get(); HEIGHT += f_DataFile.get()*256; HEIGHT += f_DataFile.get()*256*256; HEIGHT += f_DataFile.get()*256*256*256;
f_DataFile.close(); } return HEIGHT; }
int set_width (void) { std::ifstream f_DataFile; short unsigned int WIDTH; short dummy;
f_DataFile.open("heightmap.bmp", std::ios::binary);
if (f_DataFile.is_open()) {
for (int i = 0; i < 18; i++) { dummy = f_DataFile.get(); }
WIDTH = f_DataFile.get(); WIDTH += f_DataFile.get()*256; WIDTH += f_DataFile.get()*256*256; WIDTH += f_DataFile.get()*256*256*256;
f_DataFile.close(); } return WIDTH; }
LRESULT CALLBACK OurWindowProcedure(HWND han_Wind,UINT uint_Message,WPARAM parameter1,LPARAM parameter2) { return DefWindowProc(han_Wind,uint_Message,parameter1,parameter2); }
HWND NewWindow(LPCTSTR str_Title,int int_XPos, int int_YPos, int int_Width, int int_Height) { WNDCLASSEX wnd_Structure;
wnd_Structure.cbSize = sizeof(WNDCLASSEX); wnd_Structure.style = CS_HREDRAW | CS_VREDRAW; wnd_Structure.lpfnWndProc = OurWindowProcedure; wnd_Structure.cbClsExtra = 0; wnd_Structure.cbWndExtra = 0; wnd_Structure.hInstance = GetModuleHandle(NULL); wnd_Structure.hIcon = NULL; wnd_Structure.hCursor = NULL; wnd_Structure.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); wnd_Structure.lpszMenuName = NULL; wnd_Structure.lpszClassName = "WindowClassName"; wnd_Structure.hIconSm = LoadIcon(NULL,IDI_APPLICATION);
RegisterClassEx(&wnd_Structure);
return CreateWindowEx(WS_EX_CONTROLPARENT, "WindowClassName", str_Title, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE, int_XPos, int_YPos, int_Width, int_Height, NULL, NULL, GetModuleHandle(NULL), NULL); }
LPDIRECT3DDEVICE9 InitializeDevice(HWND han_WindowToBindTo) { LPDIRECT3D9 p_dx_Object; LPDIRECT3DDEVICE9 p_dx_Device;
p_dx_Object = Direct3DCreate9(D3D_SDK_VERSION); if (p_dx_Object == NULL) { MessageBox(han_WindowToBindTo,"DirectX Runtime library not installed!","InitializeDevice()",MB_OK); }
D3DPRESENT_PARAMETERS dx_PresParams; ZeroMemory( &dx_PresParams, sizeof(dx_PresParams) ); dx_PresParams.Windowed = TRUE; dx_PresParams.SwapEffect = D3DSWAPEFFECT_DISCARD; dx_PresParams.BackBufferFormat = D3DFMT_UNKNOWN; dx_PresParams.EnableAutoDepthStencil = TRUE; dx_PresParams.AutoDepthStencilFormat = D3DFMT_D16;
if (FAILED(p_dx_Object->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, han_WindowToBindTo, D3DCREATE_HARDWARE_VERTEXPROCESSING, &dx_PresParams, &p_dx_Device))) { if (FAILED(p_dx_Object->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, han_WindowToBindTo, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &dx_PresParams, &p_dx_Device))) { MessageBox(han_WindowToBindTo,"Failed to create even the reference device!","InitializeDevice()",MB_OK); } }
p_dx_Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); p_dx_Device->SetRenderState(D3DRS_LIGHTING,false); p_dx_Device->SetRenderState(D3DRS_ZENABLE, true);
return p_dx_Device; }
void DrawScene(LPDIRECT3DDEVICE9 p_dx_Device, LPDIRECT3DVERTEXBUFFER9 p_dx_VertexBuffer, LPDIRECT3DINDEXBUFFER9 p_dx_IndexBuffer) { p_dx_Device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0); p_dx_Device->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); p_dx_Device->BeginScene();
p_dx_Device->SetStreamSource( 0, p_dx_VertexBuffer, 0, sizeof(OURCUSTOMVERTEX) ); p_dx_Device->SetFVF(D3DFVF_XYZ|D3DFVF_DIFFUSE); p_dx_Device->SetIndices(p_dx_IndexBuffer);
D3DXMATRIX m_Rotation; D3DXMatrixRotationZ(&m_Rotation, flt_Angle); D3DXMATRIX m_Translation; D3DXMatrixTranslation(&m_Translation,32,-32,0);
D3DXMATRIX m_World; D3DXMatrixMultiply(&m_World, &m_Translation, &m_Rotation); p_dx_Device->SetTransform(D3DTS_WORLD, &m_World);
p_dx_Device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,WIDTH*HEIGHT,0,(WIDTH-1)*(HEIGHT-1)*2);
p_dx_Device->EndScene(); p_dx_Device->Present(NULL, NULL, NULL, NULL); }
LPDIRECT3DVERTEXBUFFER9 FillVertices(HWND han_Window, LPDIRECT3DDEVICE9 p_dx_Device) {
LPDIRECT3DVERTEXBUFFER9 p_dx_VertexBuffer; OURCUSTOMVERTEX *cv_Vertices = new OURCUSTOMVERTEX[WIDTH*WIDTH];
std::ifstream f_DataFile; short unsigned int dummy; f_DataFile.open("heightmap.bmp", std::ios::binary); if (f_DataFile.is_open()) {
for (int i = 0;i<
(offset);i++) { dummy = f_DataFile.get(); }
for (int x=0;x<
WIDTH;x++) {
for (int y=0; y<
HEIGHT;y++) { int height = f_DataFile.get(); height += f_DataFile.get(); height += f_DataFile.get(); height /= 8; cv_Vertices[y*WIDTH + x].x = -x; cv_Vertices[y*WIDTH + x].y = y; cv_Vertices[y*WIDTH + x].z = height;
if (height < MinimumHeight + (MaximumHeight - MinimumHeight) /4) //new { cv_Vertices[y*WIDTH + x].color = 0xff0000ff; } else if (height < MinimumHeight + (MaximumHeight - MinimumHeight) * 2/4) { cv_Vertices[y*WIDTH + x].color = 0xff228b22; } else if (height < MinimumHeight + (MaximumHeight - MinimumHeight) * 3/4) { cv_Vertices[y*WIDTH + x].color = 0xffa52a2a; } else { cv_Vertices[y*WIDTH + x].color = 0xffffffff; } //new fin } } }else{ MessageBox(han_Window,"HeighData file not found!","FillVertices()",MB_OK); }
f_DataFile.close();
if (FAILED(p_dx_Device->CreateVertexBuffer(WIDTH*HEIGHT*sizeof(OURCUSTOMVERTEX), 0, D3DFVF_XYZ|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &p_dx_VertexBuffer, NULL))) { MessageBox(han_Window,"Error while creating VertexBuffer","FillVertices()",MB_OK); }
VOID* p_Vertices; if (FAILED(p_dx_VertexBuffer->Lock(0, WIDTH*HEIGHT*sizeof(OURCUSTOMVERTEX), (void**)&p_Vertices, 0))) { MessageBox(han_Window,"Error trying to lock","FillVertices()",MB_OK); }else{ memcpy(p_Vertices, cv_Vertices, WIDTH*HEIGHT*sizeof(OURCUSTOMVERTEX)); p_dx_VertexBuffer->Unlock(); } delete [] cv_Vertices; return p_dx_VertexBuffer; }
LPDIRECT3DINDEXBUFFER9 FillIndices(HWND han_Window, LPDIRECT3DDEVICE9 p_dx_Device) { LPDIRECT3DINDEXBUFFER9 p_dx_IndexBuffer; short *s_Indices = new short[(WIDTH-1)*(HEIGHT-1)*6];
for (int x=0;x<
WIDTH-1;x++) {
for (int y=0; y<
HEIGHT-1;y++) { s_Indices[(x+y*(WIDTH-1))*6+2] = x+y*WIDTH; s_Indices[(x+y*(WIDTH-1))*6+1] = (x+1)+y*WIDTH; s_Indices[(x+y*(WIDTH-1))*6] = (x+1)+(y+1)*WIDTH;
s_Indices[(x+y*(WIDTH-1))*6+3] = (x+1)+(y+1)*WIDTH; s_Indices[(x+y*(WIDTH-1))*6+4] = x+y*WIDTH; s_Indices[(x+y*(WIDTH-1))*6+5] = x+(y+1)*WIDTH; } }
if (FAILED(p_dx_Device->CreateIndexBuffer((WIDTH-1)*(HEIGHT-1)*6*sizeof(short),D3DUSAGE_WRITEONLY,D3DFMT_INDEX16,D3DPOOL_MANAGED,&p_dx_IndexBuffer,NULL))) { MessageBox(han_Window,"Error while creating IndexBuffer","FillIndices()",MB_OK); }
VOID* p_Indices; if (FAILED(p_dx_IndexBuffer->Lock(0, (WIDTH-1)*(HEIGHT-1)*6*sizeof(short), (void**)&p_Indices, 0))) { MessageBox(han_Window,"Error trying to lock","FillIndices()",MB_OK); }else{ memcpy(p_Indices, s_Indices, (WIDTH-1)*(HEIGHT-1)*6*sizeof(short)); p_dx_IndexBuffer->Unlock(); } delete [] s_Indices; return p_dx_IndexBuffer; }
void SetUpCamera(LPDIRECT3DDEVICE9 p_dx_Device) { D3DXVECTOR3 m_EyePos(80, 0, 120); D3DXVECTOR3 m_TargetPos(-20, 0, 0); D3DXVECTOR3 m_UpVector(0, 0, 1); D3DXMATRIXA16 m_View; D3DXMatrixLookAtLH(&m_View, &m_EyePos, &m_TargetPos, &m_UpVector); p_dx_Device->SetTransform(D3DTS_VIEW, &m_View);
D3DXMATRIX m_Projection; D3DXMatrixPerspectiveFovLH(&m_Projection, D3DX_PI/4, 500/500, 1, 250); p_dx_Device->SetTransform(D3DTS_PROJECTION, &m_Projection); }
LPDIRECTINPUTDEVICE8 InitializeKeyboard(HWND han_Window) { LPDIRECTINPUT8 p_dx_KeybObject; LPDIRECTINPUTDEVICE8 p_dx_KeybDevice;
DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&p_dx_KeybObject, NULL); p_dx_KeybObject->CreateDevice(GUID_SysKeyboard, &p_dx_KeybDevice, NULL);
p_dx_KeybDevice->SetDataFormat(&c_dfDIKeyboard); p_dx_KeybDevice->SetCooperativeLevel(han_Window, DISCL_FOREGROUND|DISCL_NONEXCLUSIVE); p_dx_KeybDevice->Acquire();
return p_dx_KeybDevice; }
void ReadKeyboard(LPDIRECTINPUTDEVICE8 p_Keyb) { char chr_KeybState[256]; p_Keyb->GetDeviceState(sizeof(chr_KeybState),(LPVOID)&chr_KeybState);
if (chr_KeybState[DIK_ESCAPE]/128) { int_AppRunning = 0; }
if (chr_KeybState[DIK_DELETE]/128) { flt_Angle += (float)0.03; }
if (chr_KeybState[DIK_NEXT]/128) { flt_Angle -= (float)0.03; } }
void SetHeightLimits(void) { std::ifstream f_DataFile; short unsigned int dummy; f_DataFile.open("heightmap.bmp", std::ios::binary); if (f_DataFile.is_open()) {
for (int i = 0;i<
(offset);i++) { dummy = f_DataFile.get(); }
for (int x=0;x<
WIDTH;x++) {
for (int y=0; y<
HEIGHT;y++) { int height = f_DataFile.get(); height += f_DataFile.get(); height += f_DataFile.get(); height /= 8;
if (height < MinimumHeight) { MinimumHeight = height; } if (height > MaximumHeight) { MaximumHeight = height; } } } } }
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPreviousInstance,LPSTR lpcmdline,int nCmdShow) { MSG msg_Message; SetHeightLimits();
HWND han_Window = NewWindow("DirectX C++ Tutorial",100,100,500,500); LPDIRECT3DDEVICE9 p_Device = InitializeDevice(han_Window); LPDIRECT3DVERTEXBUFFER9 p_dx_VB = FillVertices(han_Window, p_Device); LPDIRECT3DINDEXBUFFER9 p_dx_IB = FillIndices(han_Window, p_Device); SetUpCamera(p_Device);
LPDIRECTINPUTDEVICE8 p_KeybDevice = InitializeKeyboard(han_Window);
while(int_AppRunning) { if(PeekMessage(&msg_Message,han_Window,0,0,PM_REMOVE)) { DispatchMessage(&msg_Message); }
ReadKeyboard(p_KeybDevice);
DrawScene(p_Device, p_dx_VB, p_dx_IB); }
p_KeybDevice->Release();
p_dx_VB->Release(); p_dx_IB->Release(); p_Device->Release(); DestroyWindow(han_Window);
return 0; }
- Website design & XNA + DirectX code : Riemer Grootjans - ©2003 - 2008 Riemer Grootjans
|
|
|
|
|