第一次使用Direct3D

生命不止,奋斗不息,我无法让自己的思想停留下来,无法忍受自己虚度光阴,无所事事,我一直不看好满足现状,不求进取的人,也正因为如此,我一直在使自己变得完美,只要不会,只要有机会学,当然只要有时间,我都会学一些C++相关的技术来提升自己。

      我是一个不玩游戏的人,也是一个讨厌游戏的人,至今,我只玩了两个游戏,一个是金刚、一个类似于反恐的枪击游戏,虽然不爱玩游戏,但是我敬佩那些做游戏的人,他们能将3D游戏做的如此逼真,能够模拟现实所有情节,这点让我感到很惊讶,于是我对3D游戏绚丽的场景迷住了,决定学习一番,以后要是有时间,将自己的家也模拟成3D场景...

当然,这是我一个想法,至于最后能学到什么程度,还要看后续学习计划和时间,不过我一定会往这个目标靠近。

       最近刚学习几天D3D,虽然说D3D是COM接口,但是用法远远没有COM的用法复杂,所以也不是很抽象很难,难得是理解并掌握D3D的整个流程,图形渲染流程,各种坐标的转换,向量与矩阵的运算,在这里我仅仅大致的说明一下D3D初始化的整个流程以及如何绘制一个正方体;

      D3D大致流程如下:

(1)创建D3D对象,通过D3D对象获取屏幕适配器属性(设备能力),以便后续决定如何处理转换和灯光(硬件是否支持,支持则使用硬件,否则使用软件,但仅仅限于测试)

(2)通过D3D对象,创建D3D设备,D3D设备是应用于底层图形设备交互的媒介;

(3)通过D3D设备,我们将所有图形绘制到图形设备中;


三个过程看起来比较简单,在此过程中也伴随着窗口的创建以及消息处理;我们分以下几个步骤解说实现:

(1)窗口的创建

  窗口是图形设备绘制的目的地,在绘制之前我们必须创建指定的窗口,win32中创建的窗口很简单,相应熟悉windows开发的都知道,窗口的注册--->窗口创建-->窗口显示-->窗口刷新;基于这个过程,我将窗口的维护放置到CWindowsBase类中,该类负责窗口创建过程以及销毁;


主要代码如下:

#pragma once


class CWindowBase
{
public:
CWindowBase(void);
virtual ~CWindowBase(void);


public:
bool Create(HINSTANCE hInstance, int nWidth = 1280, int nHeight = 800);
void Destroy();


HWND GetHWnd();
int  GetWidth();
int  GetHeight();


LPCTSTR GetClassName();
LPCTSTR GetWindowName();


protected:
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


protected:
HWND m_hWnd;
int m_nWidth;
int m_nHeight;
};


#include "StdAfx.h"
#include "WindowBase.h"
#include "Resource.h"


CWindowBase::CWindowBase(void)
: m_hWnd(NULL)
, m_nWidth(1280)
, m_nHeight(800)
{
}


CWindowBase::~CWindowBase(void)
{
}


LRESULT CALLBACK CWindowBase::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CLOSE:
{
DestroyWindow(hWnd);
}
break;
case WM_DESTROY:
{
PostQuitMessage(0);
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}


bool CWindowBase::Create(HINSTANCE hInstance, int nWidth, int nHeight)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CUBEDEMON));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wcex.lpszMenuName = /*MAKEINTRESOURCE(IDC_DXDEMON)*/NULL;
wcex.lpszClassName = GetClassName();
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));


if(!RegisterClassEx(&wcex))
return false;


m_hWnd = CreateWindow(GetClassName(), GetWindowName(), WS_OVERLAPPEDWINDOW,
 0, 0, nWidth, nHeight, NULL, 
 NULL, hInstance, NULL);


if (!m_hWnd)
{
return false;
}


ShowWindow(m_hWnd, SW_SHOW);
UpdateWindow(m_hWnd);


m_nWidth = nWidth;
m_nHeight = nHeight;


return true;
}


void CWindowBase::Destroy()
{
if (m_hWnd != NULL)
{
::DestroyWindow(m_hWnd);
m_hWnd = NULL;
}
}


HWND CWindowBase::GetHWnd()
{
return m_hWnd;
}


int  CWindowBase::GetWidth()
{
return m_nWidth;
}


int  CWindowBase::GetHeight()
{
return m_nHeight;
}


LPCTSTR CWindowBase::GetClassName()
{
return _T("WindowBase");
}


LPCTSTR CWindowBase::GetWindowName()
{
return _T("Direct3D Cube");
}

窗口的创建很简单,这里我不一一解释了;

(2)D3D初始化流程

  为了能够使用D3D,我们必须能够在应用程序中初始化D3D设备,为了在后续的图形绘制当中服用D3D初始化流程,这里我将初始化流程封装到D3DBase中:


class CD3DBase
{
public:
CD3DBase(void);
virtual ~CD3DBase(void);


public:
virtual bool Init(HWND hWnd, int nWidth, int nHeight, bool bFullScreen = true);
virtual void UnInit();
virtual void Draw();


protected:
IDirect3DDevice9* m_pD3DDevice;
};


作为D3D初始化接口,我提供了三个接口,一个是初始化接口,一个是反初始化接口,一个是绘制接口,其中初始化和反初始化针对D3D设备资源的创建和销毁,Draw接口针对后续图形的绘制,子类只要实现Draw过程即可,下面我们一一看这几个接口:

A、初始化接口

bool CD3DBase::Init(HWND hWnd, int nWidth, int nHeight, bool bFullScreen/* = true*/)
{
// 创建D3D对象
assert(NULL == m_pD3DDevice);
IDirect3D9* pDirect3d9 = Direct3DCreate9(D3D_SDK_VERSION);
if (NULL == pDirect3d9)
return false;


// 获取屏幕硬件能力,询问是否支持顶点处理光照处理
D3DCAPS9 stCaps;
memset(&stCaps, 0, sizeof(stCaps));
if (FAILED(pDirect3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &stCaps)))
{
pDirect3d9->Release();
return false;
}


// 如果硬件支持则使用硬件的顶点处理,否则使用软处理,一般软处理用于测试
int nVertexProcess = 0;
if (stCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
nVertexProcess = D3DCREATE_HARDWARE_VERTEXPROCESSING;
else
nVertexProcess = D3DCREATE_SOFTWARE_VERTEXPROCESSING;


// 设备描述参数
D3DPRESENT_PARAMETERS d3dpp;
memset(&d3dpp, 0, sizeof(d3dpp));
d3dpp.BackBufferWidth = nWidth; // 交换链的离屏表面宽度
d3dpp.BackBufferHeight = nHeight; // 交换链的离屏表面高度
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; // 离屏表面的颜色格式ARGB
d3dpp.BackBufferCount = 1; // 交换链中离屏表面的数目,一般1个即可
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; // 抗锯齿无
d3dpp.MultiSampleQuality = 0; // 抗锯齿质量-忽略
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // 离屏表面与显示如何交换
d3dpp.hDeviceWindow = hWnd; // 渲染的窗口
d3dpp.Windowed = bFullScreen; // 是否全屏
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; // 刷新频率
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // 当交换链中后台缓存切换到前台缓存表现的时候的最大速率
d3dpp.EnableAutoDepthStencil = true; // 是否使用深度缓存区
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; // 深度缓冲区格式
d3dpp.Flags = 0;


// 创建D3D设备接口
if (FAILED(pDirect3d9->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
nVertexProcess,
&d3dpp,
&m_pD3DDevice)))
{
// 更换16位深度模板后,看是否支持
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
if (FAILED(pDirect3d9->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
nVertexProcess,
&d3dpp,
&m_pD3DDevice)))
{
pDirect3d9->Release();
return false;
}
}


pDirect3d9->Release();


// 设置照相机位置,放置点-对准点-世界坐标上方方向
D3DXVECTOR3 stPosition(0.0f, 0.0f, -12.0f);
D3DXVECTOR3 stTarget(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 stWorldCordUp(0.0f, 1.0f, 0.0f);


D3DXMATRIX stViewMatrix;
D3DXMatrixLookAtLH(&stViewMatrix, &stPosition, &stTarget, &stWorldCordUp);
m_pD3DDevice->SetTransform(D3DTS_VIEW, &stViewMatrix);


// 设置3D透视投影范围
D3DXMATRIX stProjection;
D3DXMatrixPerspectiveFovLH(&stProjection, D3DX_PI*0.5f, (float)nWidth/(float)nHeight, 1.0f, 1000.0f);
m_pD3DDevice->SetTransform(D3DTS_PROJECTION, &stProjection);


// 切换到线框图模式
m_pD3DDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);


return true;
}


首先创建D3D对象,然后查询设备能力,决定后续创建D3D设备的行为,确定使用硬件处理或软件处理顶点,在创建D3D之前确定创建D3D设备具体参数,当然这里要理解离屏表面和交换链的概念,这里简单的说就是,为了让应用更流程,我们往往是将内存中已经渲染好的直接显示出来即可,而不是临时绘制然后显示到前台,后台保存的多个缓存组成了离屏表面组,离屏表面与前台交换的时候就形成一个交换链;


B、反初始化接口

void CD3DBase::UnInit()
{
if (NULL != m_pD3DDevice)
{
m_pD3DDevice->Release();
m_pD3DDevice = NULL;
}
}


这里仅仅是销毁D3D设备

IDirect3DDevice9* m_pD3DDevice;


C、绘制接口

绘制接口为虚函数,具体绘制函数中实现该虚函数即可绘制不同的图形,该项目中,我们使用子类实现具体图形的绘制:


#pragma once
#include "d3dbase.h"


class CD3DCube :
public CD3DBase
{
public:
CD3DCube(void);
virtual ~CD3DCube(void);


public:
virtual bool Init(HWND hWnd, int nWidth, int nHeight, bool bFullScreen = true);
virtual void UnInit();
virtual void Draw();


protected:
IDirect3DVertexBuffer9* m_pVertexBuffer;
IDirect3DIndexBuffer9* m_pIndexBuffer;
};


#include "StdAfx.h"
#include "d3dcube.h"
#include <assert.h>
#include "Vertex.h"


CD3DCube::CD3DCube(void)
: m_pVertexBuffer(NULL)
, m_pIndexBuffer(NULL)
{
}


CD3DCube::~CD3DCube(void)
{
}




bool CD3DCube::Init(HWND hWnd, int nWidth, int nHeight, bool bFullScreen /*= true*/)
{
bool bRet = CD3DBase::Init(hWnd, nWidth, nHeight, bFullScreen);

assert(NULL == m_pIndexBuffer);
assert(NULL == m_pVertexBuffer);


// 创建定点坐标和定点索引
m_pD3DDevice->CreateVertexBuffer(8*sizeof(CVertex), D3DUSAGE_WRITEONLY, CVertex::FVF, D3DPOOL_MANAGED, &m_pVertexBuffer, 0);
m_pD3DDevice->CreateIndexBuffer(12*3*sizeof(WORD), D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &m_pIndexBuffer, 0);


// 初始化顶点
CVertex* pVertes = NULL;
m_pVertexBuffer->Lock(0, 0, (void**)&pVertes, 0);
if (NULL != pVertes)
{
pVertes[0] = CVertex(-1.0f, -1.0f, -1.0f);
pVertes[1] = CVertex(-1.0f, 1.0f, -1.0f);
pVertes[2] = CVertex(1.0f, 1.0f, -1.0f);
pVertes[3] = CVertex(1.0f, -1.0f, -1.0f);
pVertes[4] = CVertex(-1.0f, -1.0f, 1.0f);
pVertes[5] = CVertex(-1.0f, 1.0f, 1.0f);
pVertes[6] = CVertex(1.0f, 1.0f, 1.0f);
pVertes[7] = CVertex(1.0f, -1.0f, 1.0f);
m_pVertexBuffer->Unlock();
}


// 初始化顶点索引
WORD* pIndexs = NULL;
m_pIndexBuffer->Lock(0, 0, (void**)&pIndexs, 0);
if (NULL != m_pIndexBuffer)
{
// 由于有背面拣选,三角形必须为顺时针才是正面,才能被摄像机看到,逆时针为背面永远看不到
// front side
pIndexs[0] = 0;pIndexs[1] = 1;pIndexs[2] = 2;
pIndexs[3] = 0;pIndexs[4] = 2;pIndexs[5] = 3;


// back side
pIndexs[6] = 6;pIndexs[7] = 5;pIndexs[8] = 4;
pIndexs[9] = 6;pIndexs[10] = 4;pIndexs[11] = 7;


// left side
pIndexs[12] = 5;pIndexs[13] = 1;pIndexs[14] = 0;
pIndexs[15] = 5;pIndexs[16] = 0;pIndexs[17] = 4;


// right side
pIndexs[18] = 3;pIndexs[19] = 2;pIndexs[20] = 6;
pIndexs[21] = 3;pIndexs[22] = 6;pIndexs[23] = 7;


// up side
pIndexs[24] = 1;pIndexs[25] = 5;pIndexs[26] = 2;
pIndexs[27] = 5;pIndexs[28] = 6;pIndexs[29] = 2;


// down side
pIndexs[30] = 0;pIndexs[31] = 3;pIndexs[32] = 7;
pIndexs[33] = 0;pIndexs[34] = 7;pIndexs[35] = 4;
m_pIndexBuffer->Unlock();
}


return bRet;
}


void CD3DCube::UnInit()
{
CD3DBase::UnInit();
if (NULL != m_pVertexBuffer)
{
m_pVertexBuffer->Release();
m_pVertexBuffer = NULL;
}
if (NULL != m_pIndexBuffer)
{
m_pIndexBuffer->Release();
m_pIndexBuffer = NULL;
}
}


void CD3DCube::Draw()
{
if(NULL == m_pD3DDevice)
return;


D3DXMATRIX rotationX, rotationY;
ZeroMemory(&rotationX, sizeof(rotationX));
ZeroMemory(&rotationY, sizeof(rotationY));


static float nY = 0.0f;
D3DXMatrixRotationX(&rotationX, D3DX_PI/4.0f);
D3DXMatrixRotationY(&rotationY, nY);


// > 180 degree
nY += 0.001f;
if (nY >= D3DX_PI*2)
{
nY = 0.0f;
}


D3DXMATRIX rotationRet = rotationX*rotationY;
m_pD3DDevice->SetTransform(D3DTS_WORLD, &rotationRet);


m_pD3DDevice->Clear(0, 0, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
m_pD3DDevice->BeginScene();


m_pD3DDevice->SetStreamSource(0, m_pVertexBuffer, 0, sizeof(CVertex));
m_pD3DDevice->SetIndices(m_pIndexBuffer);
m_pD3DDevice->SetFVF(CVertex::FVF);
HRESULT hRet = m_pD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 8, 0, 12); // 8顶点12个三角形


m_pD3DDevice->EndScene();
m_pD3DDevice->Present(0, 0, 0, 0);
}


该类中覆盖了积累的三个虚函数,分别是初始化、反初始化、绘制三个接口,覆盖初始化和反初始化接口的原因是子类有自定义的顶点buffer和顶点索引buffer的初始化和反初始化,覆盖Draw就是具体正方体的绘制实现;

绘制的时候,我们使用线框图模式:也就是点和线条的填充模式(wireframe),一个复杂的物体也可以是多个三角形组成的,绘制之前我们必须将所有的顶点保存起来,这里D3D使用顶点接口,由于保存所有三角形顶点时候很多的顶点重复率非常高,为了节约内存,D3D使用一种简单的方法,所有的顶点使用一次,用另一个数字指定对应顶点的索引组成的三角形的顺序,这样就去掉了重复率,所有绘制正方体需要两个buffer一个是顶点buffer一个是顶点索引buffer,分别对应D3D的 IDirect3DVertexBuffer9和IDirect3DIndexBuffer9,创建设备需要的缓存,我们可以通过D3D设备提供的buffer创建接口:

m_pD3DDevice->CreateVertexBuffer(8*sizeof(CVertex), D3DUSAGE_WRITEONLY, CVertex::FVF, D3DPOOL_MANAGED, &m_pVertexBuffer, 0);
m_pD3DDevice->CreateIndexBuffer(12*3*sizeof(WORD), D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &m_pIndexBuffer, 0);

其中,CreateVertexBuffer为创建顶点buffer,第一个参数为物体的顶点数目,第二个为buffer的行为,第三个为灵活顶点格式FVF(flexible Vertex Format,顶点不仅有位置、顶点的法向量方向、顶点的颜色纹理、顶点的光照)这里我们仅仅使用具体位置表示顶点具体位置即可,顶点为三个坐标float,也就是12直接,另外描述顶点格式,顶点格式描述为static不占用结构体大小,存储在全局数据区;

struct CVertex
{
public:
CVertex(void);
CVertex(float x, float y, float z);
//virtual ~CVertex(){};


public:
float GetXPos();
float GetYPos();
float GetZPos();
DWORD GetFVF();


protected:
float m_fXPos;
float m_fYPos;
float m_fZPos;


public:
static DWORD FVF;
};

注意:这里将虚析构注释掉了,很重要,如果含有virtual关键字,那么默认会包含虚指针表,大小将发生变化,导致后续绘制错误;

第四个参数为缓存的管理方式,我们使用自动管理,这里不再描述,具体看文档。

同理,CreateIndexBuffer参数类似,唯一不同的就是第二个参数,这里描述了顶点使用的索引最大占用字节;


顶点缓存和顶点索引缓存创建后,如果需要修改该缓存,我们使用Lock返回具体地址,然后修改对应坐标和索引值即可,这里需要非常注意的一点就是:


由于D3D做了背面拣选,所有的背面是看不到的,也不会被显示,顺时针顶点组成的面是正面,逆时针组成的面是背面,所以我们给出三角形索引的时候,正面我们给逆时针方向的顺序,背面看不到的我们给逆时针(转过来变为正面后就是顺时针,你想想是不是?);


(3)绘制物体顶点和索引顶点

在线框图中,D3D绘制使用SetStreamSource接口和SetIndies接口设置对应物体的顶点和顶点组成的索引顺序,设置后告诉它灵活顶点格式类型,最后使用DrawindexdPrimitive绘制,绘制的前后,我们使用BeginSence和EndSence表示一个绘制的开始和结束;这里涉及到物体自身坐标、摄像机视图坐标、世界坐标以及视口转换和投影,具体概念好好理解,比较简单,类似于人眼看世界,但看不到3D世界中所有位图,且看到的是一个2D平面,需要投影;



有关矩阵变换也是难点,相信大学学过的不会觉得太难~



资源位置:D3D初始化封装以及如何绘制简单图形      欢迎下载!!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝壳里的沙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值