欢迎来到EasyLiu的博客!
参考directxtutorial.com DirectX.9.0.3D游戏开发编程基础
在上一节课中我们讲了简单的流水线操作,重点讲了坐标变换,实现了一个三角形的自转。但是存在一个问题:虽然它完美的计算了三维顶点在屏幕上的位置,但是它不显示深度。这一节课我们来解决这个问题!
假设我们要画两个三角形,一个三角形在另外一个三角形的后面,然后从一个(更远处的三角形有一部分在近处三角形后面,就是近处的三角形挡住了远处三角形的一部分)的角度观看。如果我们使用我们掌握的代码来编写程序,会得到如下的结果:
很显然这个结果不是我们想要的,是违背自然规律的。因为远处的东西不应该出现在近处的东西的前面,特别的当近处的东西挡住远处的东西的时候,我们想要的结果应该是如下所示:
那我们怎样解决这个问题呢?
当进行模型渲染的时候,Direct3D会做几个事情。首先,Direct3D会绘制流水线。这个流水线都是整齐的放在内存中的。Direct3D每次拾取这个流水线并且把每个模型转变为2D的图形。
当第一幅图片已经被绘制到屏幕上面以后,下一个模型就被拾取,处理且绘制到后台缓存区。然后,无论这个模型被放在3D空间的哪个位置,第二幅图形总是显示在第一幅图形之上,就得到了之前第一幅图所显示的结果。
幸运的是,Direct3D提供了一个解决方案。这个解决方案叫做Z-Buffer。
什么是Z-Buffer
Z-Buffer也叫作深度缓存,是一个用来存储屏幕上的每一个像素距离摄像头的距离的大的缓存,如下图所示:
上图讲述了Z-Buffer是如何工作的。当一个像素需要被绘制的时候,Direct3D选择距离摄像头最近的像素并且把其绘制到后台缓存区。同时,把深度值存储到Z-Buffer中相应的位置上,当下一次需要绘制东西的时候,Direct3D就能够知道每个像素距离摄像头多近,从而决定哪个物体应该被绘制而哪个物体不应该被绘制。
程序实现Z-Buffer的步骤
1、设定适当的显示参数:
加粗的代码代表在之前的基础上需要添加的代码,设定深度缓存的格式为16bit以及自动运行z-buffer。
// Set up the structure used to create the D3DDevice
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = SCREEN_WIDTH;
d3dpp.BackBufferHeight = SCREEN_HEIGHT;<strong>
d3dpp.EnableAutoDepthStencil = TRUE; // automatically run the z-buffer for us
d3dpp.AutoDepthStencilFormat = D3DFMT_D16; // 16-bit pixel format for the z-buffer</strong>
2、打开Z-Buffer
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE); // turn off the 3D lighting
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); // both sides of the triangles<strong>
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE); // turn on the z-buffer</strong>
3、清除Z-Buffer
每次绘制之前清除Z-Buffer,如下所示,加粗的代表需要添加的代码。
g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET<strong>|D3DCLEAR_ZBUFFER</strong>, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0);
运行结果如下所示:
下面是main.c源代码:
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
#pragma comment(lib,"winmm.lib")
#include <d3d9.h>
#include <d3dx9.h>
#include "Windows.h"
#include <mmsystem.h>
#pragma warning( disable : 4996 ) // disable deprecated warning
#include <strsafe.h>
#pragma warning( default : 4996 )
// define the screen resolution
const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;
LPDIRECT3D9 g_pD3D = NULL; // Used to create the D3DDevice
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices
// A structure for our custom vertex type
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD color; // The vertex color
};
// Our custom FVF, which describes our custom vertex structure
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)
//-----------------------------------------------------------------------------
// Name: InitD3D()
// Desc: Initializes Direct3D
//-----------------------------------------------------------------------------
HRESULT InitD3D(HWND hWnd)
{
// Create the D3D object.
if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)))
return E_FAIL;
//fill D3DCAPS9 struct with the capabilities of the primary display adapter
D3DCAPS9 caps;
g_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
//can we use hardware vertex processing?
DWORD vp = 0;
if (caps.DevCaps&D3DDEVCAPS_HWTRANSFORMANDLIGHT)
{
//yes,support hardware vertex processing
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
else
{
//no,
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}
// Set up the structure used to create the D3DDevice
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = SCREEN_WIDTH;
d3dpp.BackBufferHeight = SCREEN_HEIGHT;
d3dpp.EnableAutoDepthStencil = TRUE; // automatically run the z-buffer for us
d3dpp.AutoDepthStencilFormat = D3DFMT_D16; // 16-bit pixel format for the z-buffer
// Create the D3DDevice
if (FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
vp,
&d3dpp, &g_pd3dDevice)))
{
return E_FAIL;
}
// Device state would normally be set here
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE); // turn off the 3D lighting
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); // both sides of the triangles
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE); // turn on the z-buffer
return S_OK;
}
HRESULT InitVB()
{
// Initialize three vertices for rendering a triangle
CUSTOMVERTEX vertices[] =
{
{ 3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), }, // x, y, z, color
{ 0.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },
{ -3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0), },
};
// Create the vertex buffer. Here we are allocating enough memory
// (from the default pool) to hold all our 3 custom vertices. We also
// specify the FVF, so the vertex buffer knows what data it contains.
if (FAILED(g_pd3dDevice->CreateVertexBuffer(3 * sizeof(CUSTOMVERTEX),
0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL)))
{
return E_FAIL;
}
// Now we fill the vertex buffer. To do this, we need to Lock() the VB to
// gain access to the vertices. This mechanism is required becuase vertex
// buffers may be in device memory.
VOID* pVertices;
if (FAILED(g_pVB->Lock(0, sizeof(vertices), (void**)&pVertices, 0)))
return E_FAIL;
memcpy(pVertices, vertices, sizeof(vertices));
g_pVB->Unlock();
return S_OK;
}
VOID SetupMatrices()
{
D3DXVECTOR3 vEyePt(0.0f, 0.0f, -20.0f);//摄像头位置,决定了场景中物体的大小
D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);//摄像头朝向
D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);//垂直方向为y轴,这个一般是y轴
D3DXMATRIXA16 matView;
D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);
D3DXMATRIXA16 matProj;
D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4, (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, 1.0f, 100.0f);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);
}
VOID Render()
{
// Clear the backbuffer to a blue color
g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0);
// Begin the scene
if (SUCCEEDED(g_pd3dDevice->BeginScene()))
{
SetupMatrices();//设置转换矩阵
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX); //设置顶点格式
D3DXMATRIX matTranslateA; // a matrix to store the translation for triangle A
D3DXMATRIX matTranslateB; // a matrix to store the translation for triangle B
D3DXMATRIX matRotateY; // a matrix to store the rotation for each triangle
UINT iTime = timeGetTime() % 2000;//获取当前时间
FLOAT fAngle = iTime * (2.0f * D3DX_PI) / 2000.0f;//1000ms旋转360度
D3DXMatrixRotationY(&matRotateY, fAngle); // the front side
// build MULTIPLE matrices to translate the model and one to rotate
D3DXMatrixTranslation(&matTranslateA, 0.0f, 0.0f, 2.0f);//往里面平移
D3DXMatrixTranslation(&matTranslateB, 0.0f, 0.0f, -2.0f);//往外面平移
// tell Direct3D about each world transform, and then draw another triangle
g_pd3dDevice->SetTransform(D3DTS_WORLD, &(matTranslateA * matRotateY));
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
g_pd3dDevice->SetTransform(D3DTS_WORLD, &(matTranslateB * matRotateY));
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
// End the scene
g_pd3dDevice->EndScene();
}
// Present the backbuffer contents to the display
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
}
//-----------------------------------------------------------------------------
// Name: Cleanup()
// Desc: Releases all previously initialized objects
//-----------------------------------------------------------------------------
VOID Cleanup()
{
if (g_pVB != NULL)
g_pVB->Release();
if (g_pd3dDevice != NULL)
g_pd3dDevice->Release();
if (g_pD3D != NULL)
g_pD3D->Release();
}
//-----------------------------------------------------------------------------
// Name: MsgProc()
// Desc: The window's message handler
//-----------------------------------------------------------------------------
LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
Cleanup();
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
//-----------------------------------------------------------------------------
// Name: wWinMain()
// Desc: The application's entry point
//-----------------------------------------------------------------------------
INT WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, INT)
{
UNREFERENCED_PARAMETER(hInst);
// Register the window class
WNDCLASSEX wc =
{
sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
L"D3D Tutorial", NULL
};
RegisterClassEx(&wc);
// Create the application's window
HWND hWnd = CreateWindow(L"D3D Tutorial", L"D3D Tutorial 07: ZBuffer",
WS_OVERLAPPEDWINDOW, 100, 100, SCREEN_WIDTH, SCREEN_HEIGHT,
NULL, NULL, wc.hInstance, NULL);
// Initialize Direct3D
if (SUCCEEDED(InitD3D(hWnd)))
{
// Create the vertex buffer
if (SUCCEEDED(InitVB()))
{
// Show the window
ShowWindow(hWnd, SW_SHOWDEFAULT);
UpdateWindow(hWnd);
// Enter the message loop
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
Render();
}
}
}
UnregisterClass(L"D3D Tutorial", wc.hInstance);
return 0;
}