每日一语:
今天在博客上面看到了一个对中国经济研究很深入的人,在剖析中国的房价,分析得蛮有道理的,但不知道最终是不是会实现。终于,可以出去了,经过1年的积淀,自己发现在这1年中,成长了好多,比之前任何的时候,都要成长的快。继续,我的学习吧。加油!
今天看一下,具体的DirectX3D的基本绘制:主要在前面的基础上面增加了两个函数的实现,主要是Setup,和Dispaly。Setup,完成了顶点缓存和索引缓存的创建以及赋值。Display主要完成整个绘制流程,包含清屏,顶点资源的选择,索引缓存的选择,顶点格式的选择。进行绘制等。
继续看源代码:
DirectX3D.h:
#ifndef __DirectX3DH__
#define __DirectX3DH__
#include <d3dx9.h>
#include <string>
namespace d3d
{
bool InitD3D(
HINSTANCE hInstance, // [in] Application instance.
int width, int height, // [in] Backbuffer dimensions.
bool windowed, // [in] Windowed (true)or full screen (false).
D3DDEVTYPE deviceType, // [in] HAL or REF
IDirect3DDevice9** device);// [out]The created device.
int EnterMsgLoop(
bool (*ptr_display)(float timeDelta));
LRESULT CALLBACK WndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);
template<class T> void Release(T t)
{
if( t )
{
t->Release();
t = 0;
}
}
template<class T> void Delete(T t)
{
if( t )
{
delete t;
t = 0;
}
}
}
#endif
DirectX3D.cpp
#include "DirectX3D.h"
bool d3d::InitD3D(
HINSTANCE hInstance,
int width, int height,
bool windowed,
D3DDEVTYPE deviceType,
IDirect3DDevice9** device)
{
//
// Create the main application window.
//
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC)d3d::WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = "Direct3D9App";
if( !RegisterClass(&wc) )
{
::MessageBox(0, "RegisterClass() - FAILED", 0, 0);
return false;
}
HWND hwnd = 0;
hwnd = ::CreateWindow("Direct3D9App", "Direct3D9App",
WS_EX_TOPMOST,
0, 0, width, height,
0 /*parent hwnd*/, 0 /* menu */, hInstance, 0 /*extra*/);
if( !hwnd )
{
::MessageBox(0, "CreateWindow() - FAILED", 0, 0);
return false;
}
::ShowWindow(hwnd, SW_SHOW);
::UpdateWindow(hwnd);
//
// Init D3D:
//
//第一步
//要初始化IDirect3D 首先必须获取IDirect3D9的指针,使用一个专门的Direct3D函数就可以很容易做到
IDirect3D9 * _d3d9;
//这个对象的主要有两个用途:设备枚举以及创建IDirect3DDevice9类型的对象。设备枚举是指获取系统中可用的的每块图形卡的
//性能,显示模型,格式以及其他信息。这个函数调用失败会返回一个NULL指针。
if(NULL == (_d3d9 = Direct3DCreate9(D3D_SDK_VERSION))){
return FALSE;
}
//第二步
//创建一个代表主显卡的IDirect3DDevice9类型对象时,必须指定使用该对象进行顶点运算的类型。如果可以,我们希望使用硬件顶点运算
//但是由于并非所有的显卡都支持硬件顶点运算,我们必须首先检查图形卡是否支持该类型的运算。
//要进行检查,必须先根据主显卡的性能参数初始化一个IDirect3DDevice9类型的对象。我们使用如下方法来完成初始化:
/*
HRESULT IDirect3D9:GetDeviceCaps(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DCAPS9 * pCaps;
)
Adapter : 指定物理显卡的序号。
DeviceType:指定设备类(例如硬件设备(D3DDEVTYPE_HAL)或软件设备(D3DDEVTYPE_REF));
pCaps 返回已初始化的设备性能结构实例。
*/
D3DCAPS9 caps;
int vp = 0; //代表顶点如何操作
_d3d9->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);
if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT){
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
else
{
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}
//第三步是填充D3DPRESENT_PARAMETER结构
//该结构用于指定所要创建的IDirect3DDevice9类型对象的一些特性,该结构定义如下:
/*
typedef struct _D3DPRESENT_PARAMETERS_{
UINT BackBufferWidth;
UINT BackBufferHeight;
UINT BackBufferFormat;
UINT BackBufferCount;
D3DMULTISAMPLE_TYPE MultiSampleType;
DWORD MultiSampleQuality;
D3DSWAPEFFECT SwapEffect;
HWND hDeviceWindow;
BOOL Windowed;
BOOL EnableAutoDepthStencil;
D3DFORMAT AutoDepthStencilFormat;
DWORD Flags;
UINT FullScreen_RefreshRateInHz;
UINT PresentationInterval;
};
*/
/*
BackBufferWidth: 后台缓存中表面的宽度,单位为像素。
BackBufferHeight:后台缓存中表面的高度,单位为像素。
BackBufferFormat:后台缓存的像素格式(如32位像素格式:D3DFMT_A8R8G8B8);
BackBufferCount: 所需使用的后台缓存的个数,通常指定为1,表明我们仅需要一个后台缓存。
MultiSampleType: 后台缓存所使用的多重采样类型。
MultiSampleQuality:多重采样的质量水平。
SwapEffect:D3DSWAPEFFECT 枚举类型的一个成员。该枚举类型指定了交换链中的缓存的页面置换方式。指定D3DSWAPEFFECT_DISCARD时效率最高。
hDeviceWindow:与设备相关的窗口句柄。指定了所要进行绘制的应用程序窗口。
Windowed:为true时表示窗口模式,false时为全屏模式
EnableAutoDepthStencil:设为true,则Direct3D自动创建并维护深度缓存或模板缓存。
AutoDepthStencilFormat:深度缓存或模板缓存的像素格式(例如,用24位表示深度并将8位保留供模板缓存使用,D3DFMT_D24S8).
Flags:一些附加的特性。可以指定为0,表示无标记,或D3DPRESENTFLAG集合中的一个成员,其中两个成员较常用。
D3DPRESENTFLAG_LOCKABLE_DEPTHBUFFER 指定为可锁定的后台缓存。注意,使用一个可锁定的后台缓存会降低性能。
D3DPRESENTFLAG_DISCARD_DEPTHBUFFER 指定当下一个后台缓存提交时,哪个深度或模块缓存将被丢弃。丢弃的意思是深度或模板缓存存储区
中的内容别丢弃或无效。这样可以提升性能。
FullScreen_RefreshRateInHz: 刷新频率,如果想使用默认的刷新频率,则可将该参数指定为D3DPRESENT_RATE_DEFAULT;
PresentationInterval:D3DPRESENT集合的一个成员,其中有两个比较常用。
D3DPRESENT_INTERVAL_IMMEDIATE 立即提交。
D3DPRESENT_INTERVAL_DEFAULT 由Direct3D来选择提交频率,通常该值等于刷新频率。
*/
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.BackBufferWidth = 800;
d3dpp.BackBufferHeight = 600;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = true;
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
d3dpp.Flags = 0;
d3dpp.FullScreen_RefreshRateInHz = 0;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
//第四步 创建IDirectDevice9类型的对象
/*
HRESULT IDirect3D9::CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9 ** ppReturnedDeviceInterface
);
Adapter:指定我们希望用已创建的IDirect3DDevice9对象代表哪块物理显卡。
DeviceType:指定需要使用的设备类型()如,硬件设备用D3DDEVTYPE_HAL,或D3DDEVTYPE_REF代表软件设备。
hFocusWindow:与设备相关的窗口句柄。通常情况下是指设备所要进行绘制的目标窗口。
为了达到预期的目的,该句柄与D3DPRESENT_PARAMETER结构的数据成员hDeviceWindow应为同一个句柄。
BehaviorFlags:该参数可为D3DCREATE_HARDWARE_VERTEXPROCESSING或D3DCREATE_SOFTWARE_VERTEXPROCESSING.
pPresentationParameters:一个已经完成初始化的D3DPRESENT_PARAMETERS类型的实例,该实例定义了设备的一些特性。
ppReturnedDeviceInterface:返回所创建的设备。
*/
if(FAILED(_d3d9->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd, vp, &d3dpp, device)))
return FALSE;
_d3d9->Release();
return TRUE;
}
int d3d::EnterMsgLoop( bool (*ptr_display)(float timeDelta) )
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG));
static float lastTime = (float)timeGetTime();
while(msg.message != WM_QUIT)
{
if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
float currTime = (float)timeGetTime();
float timeDelta = (currTime - lastTime)*0.001f;
ptr_display(timeDelta);
lastTime = currTime;
}
}
return msg.wParam;
}
看一下WMAIN函数的调用:
#include "DirectX3D.h"
#include <fstream>
#include <vector>
//
// Globals
//
IDirect3DDevice9* Device = 0;
IDirect3DVertexBuffer9 * VB = 0;//顶点缓存的指针
IDirect3DIndexBuffer9 * IB = 0; //索引缓存的指针
ID3DXMesh * mesh = 0;
//初始化两个全局常量,以定义屏幕的分辨率。
const int Width = 800;
const int Height = 600;
//接下来,定义灵活的顶点格式和该结构的灵活顶点格式。该例程中的顶点结构中仅包含位置信息。
struct Vertex{
Vertex(){}
Vertex(float x,float y,float z){
_x = x;
_y = y;
_z = z;
}
float _x,_y,_z;
static const DWORD FVF;
};
const DWORD Vertex::FVF = D3DFVF_XYZ;
//
// Framework Functions
//
//在上一章里,Setup函数没有实现,下面我们在Setup函数里面创建顶点缓存和索引缓存,
//然后对缓存进行锁定,将构成立方体的顶点数据以及构成立方体的三角形单元的索引数据
//分别写入顶点缓存和索引缓存。然后将摄像机沿Z轴负方向平移几个单位,以使绘制
//在世界坐标系原点的立方体处于摄像机的视场内。然后在实施投影变换。最终,将
//填充模式的绘制状态设为线框模式。
bool Setup()
{
// Nothing to setup in this sample.
//创建顶点缓存
Device->CreateVertexBuffer(
8*sizeof(Vertex),
D3DUSAGE_WRITEONLY,
Vertex::FVF,
D3DPOOL_MANAGED,
&VB,
0);
//回忆一下这个函数的参数
/*
第一个参数是缓存空间的大小,这里分配8个顶点结构的字节数。
第二个参数是使用缓存的属性,这里用的只写属性,后面如果读就会有问题。
第三个参数,顶点的灵活顶点格式。
第四个参数,容纳缓存的内存池。
*/
Device->CreateIndexBuffer(
36*sizeof(WORD),
D3DUSAGE_WRITEONLY,
D3DFMT_INDEX16,
D3DPOOL_MANAGED,
&IB,
0);
Vertex * vertices;//定义一个顶点结构的指针,用来装从VB得到的分配的顶点空间。
VB->Lock(0,0,(void**)&vertices,0);
//回忆一下lock的参数,第一个是锁定的VB的偏移,第二个是锁定的字节数,第四个是锁定的方式。
//下面给顶点赋值。
vertices[0] = Vertex(-1.0f,-1.0f,-1.0f);
vertices[1] = Vertex(-1.0f,1.0f,-1.0f);
vertices[2] = Vertex(1.0f,1.0f,-1.0f);
vertices[3] = Vertex(1.0f,-1.0f,-1.0f);
vertices[4] = Vertex(-1.0f,-1.0f,1.0f);
vertices[5] = Vertex(-1.0f,1.0f,1.0f);
vertices[6] = Vertex(1.0f,1.0f,1.0f);
vertices[7] = Vertex(-1.0f,1.0f,-1.0f);
VB->Unlock();
//定义三角形的顶点排列方式
WORD * indices = 0;
IB->Lock(0,0,(void**)&indices,0);
//front side
indices[0] = 0;indices[1] = 1;indices[2] = 2;
indices[3] = 0;indices[4] = 2;indices[5] = 3;
//back side
indices[6] = 4;indices[7] = 6;indices[8] = 5;
indices[9] = 4;indices[10] = 7;indices[11] = 6;
//left side
indices[12] = 4;indices[13] = 5;indices[14] = 1;
indices[15] = 4;indices[16] = 1;indices[17] = 0;
//right side
indices[18] = 3;indices[19] = 2;indices[20] = 6;
indices[21] = 3;indices[22] = 6;indices[23] = 7;
//top
indices[24] = 1;indices[25] = 5;indices[26] = 6;
indices[27] = 1;indices[28] = 6;indices[29] = 2;
//bottom
indices[30] = 4;indices[31] = 0;indices[32] = 3;
indices[33] = 4;indices[34] = 3;indices[35] = 7;
IB->Unlock();
//关于变换,这里我们用到了两种变换,也分别生成了两种变换举证,分别为取景变换矩阵和投影矩阵。
//我们将摄像机变换至世界坐标系的原点,并将其旋转,使摄像机的光轴与世界坐标系Z轴正方向一致。同时,世界空间中的所有
//几何体都随着摄像机一同进行变换,以保证摄像机的视场恒定。这种变换称为取景变换,变换后的坐标系,我们称为观察坐标系。 调整
//摄像头的位置调整
D3DXVECTOR3 position(0.0f,0.0f,-5.0f); // 摄像机在世界坐标系中的位置
D3DXVECTOR3 target(0.0f,0.0f,0.0f); // 世界坐标系中的被观察点
D3DXVECTOR3 up(0.0f,1.0f,0.0f); // 世界坐标系中的向上矢量的表示。
D3DXMATRIX V;
D3DXMatrixLookAtLH(&V,&position,&target,&up); // 得到取景变换矩阵
Device->SetTransform(D3DTS_VIEW,&V); //设置取景变换。
//Set the projection matrix
//在观察坐标系中,从N维空间到N-1维空间的变换,称为投影变换。
D3DXMATRIX proj;
//生成投影矩阵
D3DXMatrixPerspectiveFovLH(
&proj,
D3DX_PI*0.5f,//Y方向的视图,弧度表示。
(float)Width / (float)Height,//纵横比
1.0f,//近裁剪面到坐标原点的距离
500.0f//远裁剪面到坐标原点的距离
);
Device->SetTransform(D3DTS_PROJECTION,&proj); //设置投影变换。
//Switch to wireframe mode
Device->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
// D3DVIEWPORT9 vp = {0,0,320,240,0,1};
// Device->SetViewport(&vp);
return true;
}
//将之前分配的内存进行清理,也就是顶点缓存和索引缓存
void Cleanup()
{
d3d::Release<IDirect3DVertexBuffer9*>(VB);
d3d::Release<IDirect3DIndexBuffer9*>(IB);
mesh->Release();
mesh = 0;
// Nothing to cleanup in this sample.
}
/*
Display方法有两个任务:更新场景和绘制场景。因为我们想让立方体旋转起来,
必须在程序生成的每帧图像中给旋转角一定的增量,从而指定立方体的选择方式。
通过更新每帧图像中立方体的角度,立方体在每帧图像中就被微微地旋转,从而
产生转动的视觉效果。注意:在本例中,我们是用世界变换来指定立方体的方向。
然后,我们调用方法IDirect3DDevice9::DrawIndexedPrimitive来绘制立方体。
*/
bool Display(float timeDelta)
{
if( Device ) // Only use Device methods if we have a valid device.
{
D3DXMATRIX Rx,Ry;
//X轴上旋转45度
D3DXMatrixRotationX(&Rx,3.14f / 4.0f);
//增量Y轴旋转
static float y = 0.0f;
D3DXMatrixRotationY(&Ry,y);
y +=timeDelta;
//重新设置角度到0,当角度到达2*PI.
if(y >= 6.28f)
y = 0.0f;
//混合x轴和y轴旋转角度。
D3DXMATRIX p = Rx * Ry;
D3DXCreateTeapot(Device,&mesh,0);
//从局部坐标系到世界坐标系的变换,称为世界变换。
Device->SetTransform(D3DTS_WORLD,&p);
//清屏
Device -> Clear(0,0,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,0x0ff0ff00,1.0f,0);
//所有的绘制方法都必须在BeginScene和EndScene中进行调用
Device->BeginScene();
/*
//指定输入源
Device->SetStreamSource(0,VB,0,sizeof(Vertex));
//设置索引缓存
Device->SetIndices(IB);
//设置顶点格式
Device->SetFVF(Vertex::FVF);
//进行绘制
Device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,8,0,12);
*/
mesh->DrawSubset(0);
Device->EndScene();
Device->Present(0,0,0,0);
// Swap the back and front buffers.
Device->Present(0, 0, 0, 0);
}
return true;
}
//
// WndProc
//
LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch( msg )
{
case WM_DESTROY:
::PostQuitMessage(0);
break;
case WM_KEYDOWN:
if( wParam == VK_ESCAPE )
::DestroyWindow(hwnd);
break;
}
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}
//
// WinMain
//
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE prevInstance,
PSTR cmdLine,
int showCmd)
{
if(!d3d::InitD3D(hinstance,
640, 480, true, D3DDEVTYPE_HAL, &Device))
{
::MessageBox(0, "InitD3D() - FAILED", 0, 0);
return 0;
}
if(!Setup())
{
::MessageBox(0, "Setup() - FAILED", 0, 0);
return 0;
}
d3d::EnterMsgLoop( Display );
Cleanup();
Device->Release();
return 0;
}
看一下程序运行的截图:
每日总结:
今天主要是了解DirectX3D的绘制流程。
1,顶点数据存放在IDirect3DVertexBuffer9中,类似的索引数据放置在IDirect3DIndexBuffer9中,使用顶点缓存或索引缓存的原因是存储于其中的数据可被保存在显卡中。
2,静态几何体(即不需要在每帧图像中进行更新)的数据应保存在静态顶点缓存或静态索引缓存中,而动态的则相反。
3,绘制状态是图形设备维护的一个状态,该状态将影响几何体的绘制方式。绘制状态从设定起,直到下次修改之前都是有效的。并且绘制状态的当前值会影响几何体的所有后续绘制操作。所有的绘制状态都有初始的默认值。
4,为了绘制顶点缓存和索引缓存中的内容,必须
4.1,调用IDirect3DDevice9::SetStreamSource方法,并建立所要使用的顶点缓存与数据流的链接。
4.2,调用IDirect3DDevice9::SetFVF,设置所要绘制的顶点的格式。
4.3,如果使用了索引缓存,务必调用IDirect3DDevice9::SetIndices方法对索引缓存进行设置。
4.4,在函数对IDirect3DDevice9::BeginScene和IDirect3DDevice::EndScene之间调用IDirect3DDevice9::DrawPrimitive或IDirect3DDevice9::DrawIndexedPrimitive.
5,借助D3DXCreate* 函数,我们可以创建比较复杂的3D物体,比如球体,圆柱体或茶壶。
我们只用这样添加代码就可:
ID3DXMesh * mesh = 0;
D3DXCreateTeapot(Device,&mesh,0);
然后在BeginScene和EndScene中间,添加mesh0->DrawSubset(0);
使用完后,必须释放:
mesh->Release();
mesh = 0;
看一下程序运行的截图: