一、文本显示
在游戏开发中,在游戏界面上现实一些文字信息是很常见的一件事,要学习DX游戏开发,显然了解DX文本如何显示是必不可少的。字符包含诸多的属性,比如颜色,大小,是否加粗,斜体,等等。我们通过DX提供的LPD3DFONT对象来指定这些字体属性,然后再进行渲染。
采用WIN32程序框架实现文本绘制的具体流程如下:
二、基本图元的绘制
无论再复杂的物体,它归根结底都是由基本图元构成的。在CG(Computer graphics)领域,人们普遍使用一组或者多组包围物体表面的多边形近似的表示真实的物体。由于最简答的多边形是三角形,因此DX使用三角形构成其他大多数的多边形,并以三角形网格近似表示复杂的三维物体。
DX定义了6中基本图元:点列表,线段列表,线段条带,三角形,三角形条带,三角形扇。
1.点列表
点列表是一个对每点独立进行渲染的顶点集合。可以在场景中用它模拟星空视野等。
2.线段列表
线段列表是一系列独立线段。可模拟大雨等效果。
3.线段条带
就是互相连接的线段。可以用它产生不封闭的多边形。
4.三角形列表
三角形列表是一系列独立的三角形。
5.三角形条带
三角形条带是一连串相邻接的三角形,这样可以减少顶点数量。3D场景中的大多数物体都是由三角形带组成的,这样在描述复杂物体时,所占用的内存更少,处理时间更少,效率更高。
6.三角形扇
和三角形条带类似,但是所有三角形共用同一顶点。就像扇子一样。
三、什么是顶点缓存?
顶点缓存是D3D用来保存顶点数据的内存缓存,它可以保存任何类型的顶点数据,并可以对其中的顶点数据进行坐标变换、光照处理、裁剪等操作。顶点缓存中的顶点数据表示要输出到屏幕上显示的图形。
四、灵活顶点格式FVF
D3D中,一个顶点缓存中的顶点其实包含了很多的属性,比如顶点坐标、颜色、法线方向、纹理坐标等。使用灵活顶点格式,用户可以自定义顶点包含了哪些信息,不用全部都包括。
代码:
//-----------------------------------------------------------------------------
// File: CreateDevice.cpp
//
// Desc: This is the first tutorial for using Direct3D. In this tutorial, all
// we are doing is creating a Direct3D device and using it to clear the
// window.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#include <d3d9.h>
#pragma warning( disable : 4996 ) // disable deprecated warning
#include <strsafe.h>
#pragma warning( default : 4996 )
#include <d3dx9core.h> //新增
//-----------------------------------------------------------------------------
// Global variables
//-----------------------------------------------------------------------------
LPDIRECT3D9 g_pD3D = NULL; //Direct3d对象
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Direct3D渲染设备对象
LPD3DXFONT g_pFont = 0; //字体对象
WCHAR* strText = L"请输入要显示的图元的代码:\n\
1:点列表\n\
2:线段列表\n\
3:线段条带\n\
4:三角形列表\n\
5:三角形条带\n\
6:三角形扇\n\
ESC:退出";
//"\n是换行用的,然后再加一个"\"是说明该字符串内容没有结束,下一行还是该字符串的内容"
RECT clientRect;
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; //顶点缓存对象
int g_iType = 1; //图元类型,每个图元对应一个整形数值,存放于该变量中,初始化绘制的是整形“1”对应的图元
//定义灵活顶点结构
struct CUSTOMVERTEX
{
FLOAT x,y,z,rhw; //定义了坐标信息
DWORD color;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
//定义函数来完成整个顶点的初始化操作
HRESULT InitVB()
{
//顶点数据
CUSTOMVERTEX vertices[] =
{
{50.0f,250.0f,0.5f,1.0f,0xffff0000,},
{150.0f,50.0f,0.5f,1.0f,0xffff0000,},
{250.0f,250.0f,0.5f,1.0f,0xffff0000,},
{350.0f,50.0f,0.5f,1.0f,0xffff0000,},
{450.0f,250.0f,0.5f,1.0f,0xffff0000,},
{550.0f,50.0f,0.5f,1.0f,0xffff0000,},
};
//创建顶点缓存
if( FAILED (g_pd3dDevice->CreateVertexBuffer(
6 * sizeof(CUSTOMVERTEX),
0,D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT,&g_pVB,NULL
)) )
{
return E_FAIL;
}
//填充顶点缓存
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;
}
/*
说明,一个Direct3D对象可以创建多个Direct3d设备对象,但是D3D对象创建的所有渲染设备对象共享相同的物理资源,
所以会导致严重的性能下降
*/
//-----------------------------------------------------------------------------
// Name: InitD3D()
// Desc: Initializes Direct3D
//-----------------------------------------------------------------------------
HRESULT InitD3D( HWND hWnd )
{
// 创建D3D对象,该对象用于创建D3D设备对象
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
//设置D3DPRESENT_PARAMETERS结构,准备创建D3D设备对象
/*说明 在DX对窗口进行渲染之前,必须先根据渲染需要对窗口进行设置,比如是使用硬件渲染还是软件渲染,渲染是使用单缓冲还是双缓冲
是否需要进行深度缓存,是否需要模板、Alpha缓存等。当你为窗口设置了这些参数之后,就不能改变了,除非销毁再根据需要创建另外的窗口。
窗口的3d特征是一次性设置的,通常就在创建窗口之后,这些设置的集合的名称就是D3DPRESENT_PARAMETERS
*/
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );
d3dpp.Windowed = TRUE; //全屏或窗口
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;//指定表面在交换链中是如何被交换的。D3DSWAPEFFECT_DISCARD:后备缓存的内容被复制到屏幕上后,后备缓存的内容失效,可以丢弃
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;//后备缓冲的格式,若不熟悉,可设置为D3DFMT_UNKNOWN,这时它将使用桌面的格式
/*
当准备工作结束后,就可以创建DX设备对象罗,因为DX对象是整个DX程序的最顶层对象,其他的对象都将通过它的接口函数调用来创建
参数1:指定对象要表示的物理显示设备,即使用哪个显卡来创建设备对象
参数2:设备类型,这里选的是HAL设备。最主要的设备就是硬件抽象层(HAL)设备,它支持硬件光栅化加速和软硬件顶点处理。D3D通过HAL访问硬件。应用程序
如果在支持HAL的计算机上运行,那么通过使用HAL设备将获得最佳性能。另一个是参考设备,它支持所有D3D特性,但是用软件实现,所以对于硬件来说,较慢。
参数3:指定DX程序从前台变换到后台时的提示窗口。
参数4:指定D3D进行3D运算的工作方式,此处参数含义:DX软件进行顶点运算
参数5:制定一个已经初始化的D3DPRESENT_PARAMETERS实例
参数6:返回创建的设备,创建好的设备对象指针存储在该参数中
*/
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
//-------------字体部分-----------------
//创建字体对象
// Device state would normally be set here
if( FAILED(D3DXCreateFont(g_pd3dDevice,0,0,0,0,0,0,0,0,0,L"Arial",&g_pFont)))
return E_FAIL;
//获取窗口客户区
GetClientRect(hWnd,&clientRect);
//--------------顶点部分-----------------
//设置剔除模式为不剔除任何面
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE);
//设置图元填充模式为线框模式
g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
//
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: Cleanup()
// Desc: Releases all previously initialized objects
// 清空之前引用的资源
//-----------------------------------------------------------------------------
/*
D3D对象和D3D设备对象都是系统资源,若只申请不释放,必定造成资源泄露,在整个过程,申请的对象一定要手动释放。
因为D3D设备对象是D3D对象创建的,所以先释放D3D设备对象。最先创建的最后释放,创建、释放的过程和栈结构一样
*/
VOID Cleanup()
{
if( g_pd3dDevice != NULL )
g_pd3dDevice->Release();
if( g_pD3D != NULL )
g_pD3D->Release();
//释放字体
if(g_pFont != NULL)
{
g_pFont->Release();
}
//释放顶点缓存对象
if(g_pVB != NULL)
{
g_pVB->Release();
}
}
//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Draws the scene
//-----------------------------------------------------------------------------
VOID Render()
{
if( NULL == g_pd3dDevice )
return;
// 清空后台缓存
/*
参数1:pRects组中的矩形个数,若第二个参数(*pRects)为空,则此处必须为0,若不为空,则必须是非0值
参数2:将要清除的屏幕矩形数组,是一个指向D3DRECT结构数组的指针,这使我们可以清除屏幕的某一部分
参数3:指定在哪些表面上执行清除表面操作,D3DCLEAR_TARGET表示清空颜色缓存
参数4:使用什么颜色填充清除的表面
参数5:设置深度缓冲的值
参数6:设置模板缓冲的值
*/
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 0, 255 ), 1.0f, 0 );
// 开始在后台缓存绘制图形
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
//在后台缓存绘制图形
g_pFont->DrawText(NULL,strText,-1,&clientRect, DT_NOCLIP | DT_LEFT | DT_TOP,0xffffffff);
g_pd3dDevice->SetStreamSource(0,g_pVB,0,sizeof(CUSTOMVERTEX));
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
switch(g_iType)
{
case 1:
g_pd3dDevice->DrawPrimitive(D3DPT_POINTLIST,0,6);
case 2:
g_pd3dDevice->DrawPrimitive(D3DPT_LINELIST,0,3);
break;
case 3:
g_pd3dDevice->DrawPrimitive(D3DPT_LINESTRIP,0,5);
break;
case 4:
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST,0,2);
break;
case 5:
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,4);
break;
case 6:
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLEFAN,0,4);
break;
}
// End the scene
g_pd3dDevice->EndScene();
}
// 将在后台缓存绘制的图形提交到前台缓存显示
/*
参数1:是你想要显示的后备缓存的一个矩形区域,设为NULL表示要将整个后备缓存的内容显示
参数2:表示一个显示区域,设为NULL表示整个客户显示区域
参数3:可以通过它来将内容显示到不同的窗口中。设为NULL则表示显示到当前主窗口。
参数4:是最小更新区域指针,一般设为NULL
*/
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
//-----------------------------------------------------------------------------
// 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;
//窗口绘制
case WM_PAINT:
Render();
ValidateRect( hWnd, NULL );
return 0;
case WM_KEYUP:
switch(wParam)
{
case VK_ESCAPE:
Cleanup();
PostQuitMessage(0);
break;
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
g_iType = (int)wParam - 48;
break;
}
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
//-----------------------------------------------------------------------------
// Name: wWinMain()
// Desc: 这是整个工程的入口函数,从这里开始理结构,,
/*
注意,InitializeD3D是完成DX整个框架的资源需求,如果该函数不成功,这个程序就不能继续运行,但
如果Windows框架资源没有申请成功,这个程序也不能继续运行。应该先申请Windows框架资源,然后再判断
InitializeD3D是否创建成功,若成功,则进入消息循环
-----------------------------
wWinMain可以处理unicode字符,
而WinMain却会把Unicode字符直接转换成ANSI字符来处理(
如果使用了中文,就可能出错)
第一个参数:应用程序当前句柄
第二个:这一个应用程序实例的前一个句柄(同时运行了一个以上)
第三:不包含应用程序的命令行,可以用CMD向应用程序传入一条字符串指令
第四:制定窗口如何显示的ID
*/
//-----------------------------------------------------------------------------
INT WINAPI wWinMain( HINSTANCE hInst, HINSTANCE, LPWSTR, INT )
{
UNREFERENCED_PARAMETER( hInst );
/*
Win32中的宏UNREFERENCED_PARAMETER,
可以使用它来避免编译器对在函数体中未引用的参数的警告。
虽然从技术上讲是没有必要的,但是努力地构建0个警告的代码是一种良好的编程习惯。
由于这个特殊的宏没有做任何事情,编译器会将对其进行优化。
*/
//定义窗口类
/*
这个窗口类被定义为结构体WNDCLASSEX,它包含Win32窗口的各种属性,
其中包括窗口的图标,菜单,窗口所属的应用实例,光标的外观等等
*/
WNDCLASSEX wc =
{
sizeof( WNDCLASSEX ), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle( NULL ), NULL, NULL, NULL, NULL,
L"DirectX游戏编程一", NULL
};
//注册窗口类
/*
把我们创建的WNDCLASSEX结构发送给函数RegisterClassEx(),向系统注册窗口类。
函数RegisterClassEx()必须在创建窗口前调用,该函数需要窗口类结构的地址作为
参数才能进行注册。如果该函数执行后,返回值是0就表示注册失败,
读者就要检查这个窗口类各成员的值,看看是否有错误,确保它们都是有效值。
*/
RegisterClassEx( &wc );
/*
下一步是创建实际的窗口
CreateWindow接受Unicode字符。如果使用的是Unicode版本,
我们必须始终要把字符串用引号括起来,然后加一个L做前缀,
以表明我们提供的是Unicode字符。
-----
参数:
lpClassName(可选的)——窗口类的名称(与窗口类结构使用相同的名称)。
lpWindowName (可选的)——窗口的标题栏文字。
dwStyle——窗口风格的标志。
X——窗口的水平位置。
Y——窗口的垂直位置。
nWidth——窗口的宽度。
hHeight——窗口的高度。
hWndParent(可选的)——指向父窗口的句柄(比如这个新的窗口是一个弹出窗口或子窗口)。
hMenu (可选的)——窗口菜单资源句柄。
hInstance (可选)——应用程序实例ID(函数wWinMain的第一个参数)
lpParam (optional)——作为窗口回调函数proc的参数,向窗口传递数据
---
函数CreateWindow(A)的返回值是一个非空的句柄
*/
HWND hWnd = CreateWindow( L"DirectX游戏编程一", L"DirectX游戏编程一:初始化D3D设备",
WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,
NULL, NULL, wc.hInstance, NULL );
// Initialize Direct3D 初始化D3D设备
/*如果创建成功,则进入消息循环*/
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
// 展示窗口
if(SUCCEEDED( InitVB() ))
{
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );
// Enter the message loop
/*
MSG是一个Win32结构,用于保存窗口消息,其中一些消息是来自操作系统的,
应用程序将会响应这些消息
*/
MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) )//提取消息
{
/*
我们可以通过调用TranslateMessage函数和DispatchMessage函数对这一消息作出回应
函数TranslateMessage是将虚拟键消息转换为字符消息,
函数DispatchMessage是派发这个消息到窗口程序的回调函数中去,这个将在下一节中讨论
*/
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
UnregisterClass( L"DirectX游戏编程一", wc.hInstance );
return 0;
}
}
效果图: