笔记本摄像头反了-箭头符号
![directx9 0下载](/uploads/image/0641.jpg)
2023年4月7日发(作者:热点资讯怎么卸载)
ndyPike
-1-
DirectX8教程
著AndyPike
译AmanJIANG
第一章:准备就绪
Whatyouwillneed(你需要什么)
•DirectX8.0SDK(可以从/directx下载)
•VC6(SP5)/
•Windows程序设计经验
•通晓C++和OOP
Introduction(序)
(原著的话)
欢迎阅读本DX教程。本教程至少能帮你入门、使你了解怎样用DX8来开发Windows游戏。我写这个教程的原由
有二:首先,当出现DX时,我还是一个初学者。所以,我想边学习边写一个教程来锻炼自己。其次,对初学者来
说,DXSDK并不是很有帮助。而且,网上也没有什么像样的Dx8教程。另外,就像上面我提到的,我也是个初学
者,所以,如果你发现教程中有什么地方不对,请给我写信:**********************。
译者言
我也是一名初学者,所以,有言在先:如果你读英文能如履平地,建议你还是去读原著。此教程很适合入门,等你入
门以后,你会发现,其实一切并没有想象的那样复杂。这是个不错的Dx8教程,我会尽最大努力把它翻译好。注:
我并没有完全按照原著来译,不适之处,请多包涵。嗯,你应该弄到教程附带的源代码,没有那个可不行!可以到
去下载。欢迎指出我的错误,或与我联系,我的Email:************************or
******************,QQ:15852673。
COM
WhatisCOM?COM是什么呢?COM就是ComponentObjectModel,组件对象模型。COM接口和C++的抽
象类相似(但不一样),就像抽象类没有与之相关的实际代码一样,COM描述了一套符号和语法而非实现过程。你
也可以把COM对象就想象成一套为某个主题而设计的一整套库函数。DX就提供了一套完整的设计3D游戏的库。
最棒的就是,使用DX时,你不必去直接接触硬件,而由DX帮你代理了。这使得一些事情变得简单了。
使用COM时应该注意,必须在程序结束前释放所有的COM对象(或接口)。而且,释放它们的顺序应该和创建
它们的顺序相反。就像这样:
interfaceA.
interfaceB.
einterfaceB.
einterfaceA.
调用它们的Release模块来释放它们。
PageFlipping(页翻动)
页翻动又是什么呢?嗯,你知道电影的原理吗?电影通过以每秒钟24幅的速度连续的闪动图像,每幅图像之间的差
别又很小,由于人眼的滞留作用,我们看到的画面就是连续的了。这不难理解。其实,DirectX也是这样工作的。我
们把要显示的物体通通绘制到一个不可见的页上,我们称这个页为“后缓冲区”。绘制完后,快速的把它翻动到可见
的“前缓冲区”上,并重复这个过程。当用户正在观看新绘制的可见页(前缓冲区)时,程序要降下一幅要显示的东
ndyPike
-2-
西绘制到“后缓冲区”上。快速而连续的重复次过程,用户就会看到像电影一样连续的图像了。不过一般情况下,我
们每秒钟能绘制的页数要比电影多很多。
如果我们不是用页翻动技术,那用户看到的屏幕中的物体,将会一个个的被绘制出来,虽然速度可能很快,但效果会
很差,那并不是我们想要的。
所以,我们的游戏需要一个循环,称之为“GameLoop”。每次循环,我们都要清除“后缓冲区”,把该绘制的物
体按照一定的逻辑都绘制到那上面,然后把它翻动到“前缓冲区”上,然后进入下一次循环。这个循环得直到游戏退
出了才能结束。有时我们可能需要好几个这样的“后缓冲区”(多缓冲)来组成一个“交换链”(SwapChain),以
求更好的效果。
Devices(设备)
Whatisadevice?设备是什么?简单的说,就是你的3D卡。你得创建一个接口来代表设备,然后使用那个接口
来绘制东西。
GameLoop(游戏循环)
什么是游戏循环呢?游戏循环是一段代码,在游戏退出之前循环执行的代码。这段代码在每次循环中都要:在屏幕上
绘制物体(或场景人物随便什么)、处理游戏的逻辑过程(如:物体的移动、人工智能等等)、处理Windows的消
息等等。基本上就是这样了。
CreatingYourFirstProject(创建你的第一个项目)
译者:嗯,这部分我就不用译了吧,这可是基础的东西。不过还是说说初学者容易忽略的一点:一定要把你的DX
SDK的Include目录和Lib目录的路径添加到VC的目录设置列表中去,而且不要把Include和Lib的地方放错
了,而且还要放在第一位。还要把添加到项目设置的Lib列表中,否则编译不了。
Okay,that’thestep-by-stepguidebelowtocreateyourfirst
DirectXGraphicsproject.
alC++createanewWin32Application.
>New
eProjectstabselectWin32Application
nameforyourprojectsuchas“DXProject1”
afolderforthelocationofyoursourcecodefiles
ext
theemptyprojectoption.
inish
rethatyourprojectsettingsarecorrect.
t>Settings...
inktab,makesurethat""isinthelistofObject/
isn’tsimplytypeitin.
rethatyoursearchpathsarecorrect.
>Options>DirectoriesTab
"Showdirectoriesfor"drop-down,select"includefiles".
esnotexistalready,addthefollowingpath:
rethatthispathisatthetopofthelistbyclickingontheuparrowbutton(if
needed).
"Showdirectoriesfor"drop-down,select"libraryfiles".
esnotexistalready,addthefollowingpath:
rethatthispathisatthetopofthelistbyclickingontheuparrowbutton(if
needed).
sourcecode.
>New
eFilestab,selectC++SourceFile
filenamesuchas“”
ecodesegmentbelow,andthenpasteitintoyournewfile.
ndRuntheprogram.
7tobuildyourproject
5torun
ndyPike
-3-
下面就是本章的例子了,好好研究吧,不难。
#include
LPDIRECT3D8g_pD3D=NULL;
LPDIRECT3DDEVICE8g_pD3DDevice=NULL;
HRESULTInitialiseD3D(HWNDhWnd)
{
//Firstofall,createdsuccessfullywe
//shouldgetapointertoanIDirect3D8interface.
g_pD3D=Direct3DCreate8(D3D_SDK_VERSION);
if(g_pD3D==NULL)
{
returnE_FAIL;
}
//Getthecurrentdisplaymode
D3DDISPLAYMODEd3ddm;
if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddm)))
{
returnE_FAIL;
}
//Createastructuretoholdthesettingsforourdevice
D3DPRESENT_PARAMETERSd3dpp;
ZeroMemory(&d3dpp,sizeof(d3dpp));
//Fillthestructure.
//Wewantourprogramtobewindowed,andsetthebackbuffertoaformat
//thatmatchesourcurrentdisplaymode
ed=TRUE;
fect=D3DSWAPEFFECT_COPY_VSYNC;
fferFormat=;
//CreateaDirect3Ddevice.
if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp,
&g_pD3DDevice)))
{
returnE_FAIL;
}
returnS_OK;
}
voidRender()
{
if(g_pD3DDevice==NULL)
{
return;
}
//Clearthebackbuffertoagreencolour
g_pD3DDevice->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,255,0),1.0f,0);
//Beginthescene
g_pD3DDevice->BeginScene();
//Renderingofourgameobjectswillgohere
ndyPike
-4-
//Endthescene
g_pD3DDevice->EndScene();
//Filpthebackandfrontbufferssothatwhateverhasbeenrenderedonthe
//backbufferwillnowbevisibleonscreen(frontbuffer).
g_pD3DDevice->Present(NULL,NULL,NULL,NULL);
}
voidCleanUp()
{
if(g_pD3DDevice!=NULL)
{
g_pD3DDevice->Release();
g_pD3DDevice=NULL;
}
if(g_pD3D!=NULL)
{
g_pD3D->Release();
g_pD3D=NULL;
}
}
voidGameLoop()
{
//Enterthegameloop
MSGmsg;
BOOLfMessage;
PeekMessage(&msg,NULL,0U,0U,PM_NOREMOVE);
while(e!=WM_QUIT)
{
fMessage=PeekMessage(&msg,NULL,0U,0U,PM_REMOVE);
if(fMessage)
{
//Processmessage
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//Nomessagetoprocess,sorenderthecurrentscene
Render();
}
}
}
//Thewindowsmessagehandler
LRESULTWINAPIWinProc(HWNDhWnd,UINTmsg,WPARAMwParam,LPARAMlParam)
{
switch(msg)
{
caseWM_DESTROY:
PostQuitMessage(0);
return0;
break;
caseWM_KEYUP:
switch(wParam)
{
caseVK_ESCAPE:
ndyPike
-5-
//Userhaspressedtheescapekey,soquit
DestroyWindow(hWnd);
return0;
break;
}
break;
}
returnDefWindowProc(hWnd,msg,wParam,lParam);
}
//Applicationentrypoint
INTWINAPIWinMain(HINSTANCEhInst,HINSTANCE,LPSTR,INT)
{
//Registerthewindowclass
WNDCLASSEXwc={sizeof(WNDCLASSEX),CS_CLASSDC,WinProc,0L,0L,
GetModuleHandle(NULL),NULL,NULL,NULL,NULL,
"DXProject1",NULL};
RegisterClassEx(&wc);
//Createtheapplication'swindow
HWNDhWnd=CreateWindow("DXProject1",":Tutorial1",
WS_OVERLAPPEDWINDOW,50,50,500,500,
GetDesktopWindow(),NULL,nce,NULL);
//InitializeDirect3D
if(SUCCEEDED(InitialiseD3D(hWnd)))
{
//Showourwindow
ShowWindow(hWnd,SW_SHOWDEFAULT);
UpdateWindow(hWnd);
//Startgamerunning:Enterthegameloop
GameLoop();
}
CleanUp();
UnregisterClass("DXProject1",nce);
return0;
}
你会得到一个绿色背景的窗户(读者:是窗口,白痴),虽然看起来不怎么样,但至少是一个好的开始,不是吗?好
地开头,就是成功地半拉!(读者:……)
ndyPike
-6-
(译者:嗯,那个老外用WinXP?我看还是Win2K好……)
(读者:切~~别在那废话。)
WinMain
此乃Win32程序的入口点,代码会从这里开始执行。这是我们注册、创建、显示窗口的地方。然后,我们要初始化
Direct3D并进入游戏循环(GameLoop)。
WinProc
这是应用程序的消息处理过程。Windows发给我们的程序的消息,都要有它处理。注意我们上面的例子,我们处理
了两种消息:WM_DESTROY(结束程序)和WM_KEYUP(有按键被按下)。其他的消息我们都交给默认消息处
理过程DefWindowProc处理了。
g_pD3D
这是指向IDirect3D8接口的指针,我们得通过它来创建Direct3D设备接口。
g_pD3DDevice
这是IDirect3DDevice8(D3D设备)的接口的指针,它实际上代表了你的硬件3D卡。
InitialiseD3D
我们的这个函数是用来初始化Direct3D的。首先,创建IDirect3D8对象,通过这个对象我们得到了当前屏幕的显
示模式。然后,根据刚刚我们获取的信息(显示模式),创建了兼容的设备。
译者:因为一些显示卡的显示模式并不相同,所以,我们的程序要在每块显卡上都能执行,就要了解这块显卡的显示
模式等等一些信息。然后,根据这些信息,创建我们的D3D设备。
GameLoop
这就是上面提到的“游戏循环”的具体实现了。当初始化结束后,它就开始了。
他会检测有没有Windows的消息,没有的话,就调用Render来绘制屏幕。
Render
首先,我们清除了后缓冲区准备作画。然后,调用了BeginScene函数来告知DX我们要作画了。这时,我们就可
以作画了(教程2)。结束了绘制,我们调用EndScene来告知DX我们画完了。最后,我们调用了Present来完
成关键的一步:翻动后缓冲区到前缓冲区(屏幕)。这时,用户就能看到我们画的东西了。
CleanUp
我们在此做清洁工作:释放所有的被创建的对象。因为程序要推出了。
Summary(摘要)
译者:嗯,好了,这就是我们教程的第一章了。并不难,不是吗?看起来虽然字不多,翻译起来,蛮累呢!
研究明白了的话,看下一章吧!下一章我们就会真正的画一些东西了!呵呵,真是令人期待呢……
也让老外说几句吧:
Ok,that’hatthefinishedprogramwasn’tthemostspectacularthing
intheworld,butjustwaitforthenexttutorialwhenwewillbedrawingsomeshapes!
第二章:绘制三角形
Introduction(序)
所有的3D图形都是由三角形构成的。为什么是三角形而不是别的图形呢?因为三角形有许多优越之处,例如:绘
制效率。(译者:而且我们知道,任意不在同一条直线上的三个点都能构成三角形,这对于在空间中形成某种复杂图
形是很有益处的,我的理解)所以,如果我们想得到一个矩形,最有效率的是绘制两个相同的合并的三角形,这样要
ndyPike
-7-
优于直接绘制一个矩形。所以,本教程下面将告诉你如何绘制复杂物体的最小单元:三角形。(译者:神奇的三角形
啊..)
Vertices(顶点)
顶点(vertex)是什么?顶点就是3D空间中的一个点。例如,三角形有三个顶点,而矩形有四个。在3D空间中,你
可以用三个顶点来指定一个三角形。想做到这些,你需要了解迪卡尔坐标系统。
2DCartesiancoordinatesystem(2D迪卡尔坐标系统)
下面的两幅图演示了2D迪卡尔坐标系统是怎样工作的。
Fig2.1Fig2.2
2D迪卡尔坐标系统是很简单的,用两条轴x、y的值来表示点的位置,进而表现出图形的位置。这是初中时我们就
学习过的。
3DCartesiancoordinatesystem(3D迪卡尔坐标系统)
下面的两幅图演示了左手3D迪卡尔坐标系统是怎样工作的。
Fig2.3Fig2.4
2D坐标系统只有x轴与y轴,因为它是平面的。而在3D空间里,两个轴显然不够用了,所以,有了第三个轴:z
轴。现在,我们用这三个轴就能在3D空间中表示出物体的任意位置了。这其实是很简单的事情,我就不多说了。
3DPrimitives
Primitive为“原始”之意,3Dprimitive就是设备所支持的原始的类型。它包括:点列、线列、线代、三角形
列、三角形带和三角扇形。使用3Dprimitive完成上述的图形是很方便的。以后我们会用3Dprimitive来绘制
图形。下面的一些例子演示了上述的各种方式:
ndyPike
-8-
PointLists
(点列)
Fig2.5
LineLists
(线列)
Fig2.6
LineStrips
(线带)
Fig2.7
TriangleLists
(三角形列)
Fig2.8
ndyPike
-9-
TriangleStrips
(三角形带)
Fig2.9
TriangleFans
(三角扇形)
Fig2.10
FlexibleVertexFormat(FVF)(灵活顶点格式)
Flexible的意思是“灵活的”。这里不译为“灵活的顶点格式”而译为“灵活顶点格式”是有原因的:前者是一个
短语,像是广告词一样;后者才更像是一种名词或术语。所以,我取了后者。灵活顶点格式(FVF)是用来描述顶
点属性的一种格式,而这种格式是可以由我们自定义的,所以称它为“灵活顶点格式”。至此,我们至少知道了顶
点有三种属性:x值、y值和z值。其实顶点还可以有其他属性,例如颜色与亮度。利用灵活顶点格式(FVF)我们
能方便的指定顶点的属性。
如果我们在Direct3D指定了一个多边形,这个多边形将可以被它的各个顶点的属性所填充,带有过渡性的属性填
充。我知道这个不太好理解,没关系,下面我们有例子,它也正是这样做的:在我们下面的例子中,将会有一个三个
顶点所组成的三角形,三角形的每个顶点的颜色都是不同的,他们分别是红、绿、蓝,电脑中的三原色。三角形将会
被这三种颜色混合的、渐变的填充起来。
VertexBuffers(顶点缓冲)
顶点缓冲就是一块用于保存顶点的内存缓冲区。顶点缓冲可以保存任何的顶点类型。当你的一些顶点已经被保存在顶
点缓冲区中,你就可以操作它们了,例如渲染、变换和剪裁。
Colours(颜色)
在DirectX中,如果我们要指定一种颜色,我们可以用D3DCOLOR_XRGB宏。宏中有三个参数,每个参数都是0
到255间的整数值,分别用于描述颜色的红、绿、蓝分量,然后D3DCOLOR_XRGB宏会将它们调和,就像水彩
调色一样。
例如:
D3DCOLOR_XRGB(0,0,0)是黑色(无色)。
D3DCOLOR_XRGB(255,255,255)是纯白色(满色调)。
D3DCOLOR_XRGB(0,255,0)是亮绿色(没有红与蓝,全是绿色的分量)。
D3DCOLOR_XRGB(100,20,100)是暗紫色(100红,20绿,100蓝)。
好了,我们第二章的例子就在这了。它和第一个例子差不多,只是添加了一些代码、做了些修改。
仔细研究吧!It’seasy!
ndyPike
-10-
#include
LPDIRECT3D8g_pD3D=NULL;
LPDIRECT3DDEVICE8g_pD3DDevice=NULL;
LPDIRECT3DVERTEXBUFFER8g_pVertexBuffer=NULL;//Buffertoholdvertices
structCUSTOMVERTEX
{
FLOATx,y,z,rhw;//Thetransformedpositionforthevertex.
DWORDcolour;//Thevertexcolour.
};
#defineD3DFVF_CUSTOMVERTEX(D3DFVF_XYZRHW|D3DFVF_DIFFUSE)
#defineSafeRelease(pObject)if(pObject!=NULL){pObject->Release();pObject=NULL;}
HRESULTInitialiseD3D(HWNDhWnd)
{
//Firstofall,createdsuccessfullywe
//shouldgetapointertoanIDirect3D8interface.
g_pD3D=Direct3DCreate8(D3D_SDK_VERSION);
if(g_pD3D==NULL)
{
returnE_FAIL;
}
//Getthecurrentdisplaymode
D3DDISPLAYMODEd3ddm;
if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddm)))
{
returnE_FAIL;
}
//Createastructuretoholdthesettingsforourdevice
D3DPRESENT_PARAMETERSd3dpp;
ZeroMemory(&d3dpp,sizeof(d3dpp));
//Fillthestructure.
//Wewantourprogramtobewindowed,andsetthebackbuffertoaformat
//thatmatchesourcurrentdisplaymode
ed=TRUE;
fect=D3DSWAPEFFECT_COPY_VSYNC;
fferFormat=;
//CreateaDirect3Ddevice.
if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp,
&g_pD3DDevice)))
{
returnE_FAIL;
}
returnS_OK;
}
HRESULTInitialiseVertexBuffer()
{
VOID*pVertices;
//Storeeachpointofthetriangletogetherwithit'scolour
CUSTOMVERTEXcvVertices[]=
ndyPike
-11-
{
//Vertex1-Red(250,100)
{250.0f,100.0f,0.5f,1.0f,D3DCOLOR_XRGB(255,0,0),},
//Vertex2-Green(400,350)
{400.0f,350.0f,0.5f,1.0f,D3DCOLOR_XRGB(0,255,0),},
//Vertex3-Blue(100,350)
{100.0f,350.0f,0.5f,1.0f,D3DCOLOR_XRGB(0,0,255),},
};
//Createthevertexbufferfromourdevice
if(FAILED(g_pD3DDevice->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
0,D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT,&g_pVertexBuffer)))
{
returnE_FAIL;
}
//Getapointertothevertexbufferverticesandlockthevertexbuffer
if(FAILED(g_pVertexBuffer->Lock(0,sizeof(cvVertices),(BYTE**)&pVertices,0)))
{
returnE_FAIL;
}
//Copyourstoredverticesvaluesintothevertexbuffer
memcpy(pVertices,cvVertices,sizeof(cvVertices));
//Unlockthevertexbuffer
g_pVertexBuffer->Unlock();
returnS_OK;
}
voidRender()
{
if(g_pD3DDevice==NULL)
{
return;
}
//Clearthebackbuffertoblack
g_pD3DDevice->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);
//Beginthescene
g_pD3DDevice->BeginScene();
//Renderingourtriangle
g_pD3DDevice->SetStreamSource(0,g_pVertexBuffer,sizeof(CUSTOMVERTEX));
g_pD3DDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX);
g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST,0,1);
//Endthescene
g_pD3DDevice->EndScene();
//Filpthebackandfrontbufferssothatwhateverhasbeenrenderedonthe
//backbufferwillnowbevisibleonscreen(frontbuffer).
g_pD3DDevice->Present(NULL,NULL,NULL,NULL);
}
voidCleanUp()
ndyPike
-12-
{
SafeRelease(g_pVertexBuffer);
SafeRelease(g_pD3DDevice);
SafeRelease(g_pD3D);
}
voidGameLoop()
{
//Enterthegameloop
MSGmsg;
BOOLfMessage;
PeekMessage(&msg,NULL,0U,0U,PM_NOREMOVE);
while(e!=WM_QUIT)
{
fMessage=PeekMessage(&msg,NULL,0U,0U,PM_REMOVE);
if(fMessage)
{
//Processmessage
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//Nomessagetoprocess,sorenderthecurrentscene
Render();
}
}
}
//Thewindowsmessagehandler
LRESULTWINAPIWinProc(HWNDhWnd,UINTmsg,WPARAMwParam,LPARAMlParam)
{
switch(msg)
{
caseWM_DESTROY:
PostQuitMessage(0);
return0;
break;
caseWM_KEYUP:
switch(wParam)
{
caseVK_ESCAPE:
//Userhaspressedtheescapekey,soquit
DestroyWindow(hWnd);
return0;
break;
}
break;
}
returnDefWindowProc(hWnd,msg,wParam,lParam);
}
//Applicationentrypoint
INTWINAPIWinMain(HINSTANCEhInst,HINSTANCE,LPSTR,INT)
{
//Registerthewindowclass
WNDCLASSEXwc={sizeof(WNDCLASSEX),CS_CLASSDC,WinProc,0L,0L,
ndyPike
-13-
GetModuleHandle(NULL),NULL,NULL,NULL,NULL,
"DXProject2",NULL};
RegisterClassEx(&wc);
//Createtheapplication'swindow
HWNDhWnd=CreateWindow("DXProject2",":Tutorial2",
WS_OVERLAPPEDWINDOW,50,50,500,500,
GetDesktopWindow(),NULL,nce,NULL);
//InitializeDirect3D
if(SUCCEEDED(InitialiseD3D(hWnd)))
{
//Showourwindow
ShowWindow(hWnd,SW_SHOWDEFAULT);
UpdateWindow(hWnd);
//InitializeVertexBuffer
if(SUCCEEDED(InitialiseVertexBuffer()))
{
//Startgamerunning:Enterthegameloop
GameLoop();
}
}
CleanUp();
UnregisterClass("DXProject2",nce);
return0;
}
运行程序,你会得到一个背景为黑色的窗口。窗口的中央是一个三角形,被三原色过渡填充的三角形,很漂亮,不是
吗?真高兴,现在我们已经可以绘制一些真正的图形了。看图来说,上面的一些东西就不难理解了。好了,下面有一
幅程序运行时的截图:
嘿嘿,添加了几行代码,得到了如此漂亮的图形,不是很复杂,对吧?
那末,我们都做了什么改动呢?
WinMain
WinMain函数作了细微的调整,就是初始化Direct3D后又调用了InitialiseVertexBuffer函数。
ndyPike
-14-
g_pVertexBuffer
这是顶点缓冲的指针。注意它是全局的,所以可以被所有的函数使用。这就是我们存储三角形的顶点的地方了,顶点
缓冲,对,就是那个。
CUSTOMVERTEX
这是我们自定义的顶点格式,为什么能自定义呢?对!因为Direct3D支持的灵活顶点格式(FVF)。其中我们都指定
了x,y,z,rhw和颜色属性。
D3DFVF_CUSTOMVERTEX
上面的CUSTOMVERTEX乃是我们自定义的顶点格式,但自定义也不能随便定义,我们需要按照一定的规则,而
且也需要通知DX我们的格式知怎样定义的。所以,我们定义了这个标识D3DFVF_CUSTOMVERTEX,它运用
了DX原有的两个标识D3DFVF_XYZRHW和D3DFVF_DIFFUSE(已转换顶点和扩散颜色)定义了我们自己的格
式,而且,跟上面我们定义的CUSTOMVERTEX格式是相吻合的。明白了吗?也就是说:我们要按照DX支持的
各种顶点属性和它们的标识来组合出我们自己需要的顶点格式和标识,而标识是用来通知DX的。
InitialiseVertexBuffer
这是一个新的函数,你已经猜到了:它是用来初始化定点缓冲的。在函数中,首先定义了一个CUSTOMVERTEX
型的数组cvVertices,并按照前面我们所定义的格式输入了三个不同的顶点,位置不同颜色也不同。注意:在本例
中,顶点的z值应该在0到1之间,因为现在的程序只能支持一些2D的图形,这样做是为了让程序最简单且容易
理解。不用急,以后我们就会创建真正的3D图形了。
然后我们利用设备借口调用了CreateVertexBuffer函数来初始化我们的顶点缓冲。注意第一个参数我们输入了顶
点缓冲的大小,因为我们输入了三个顶点,所以是3个CUSTOMVERTEX的大小。此函数将给我们一个指向顶点
缓冲的指针(g_pVertexBuffer)。
这时我们的顶点缓冲已经创建好了,我们应该把我们指定好的顶点传送给它:锁定顶点缓冲-〉传送已经被保存的顶
点-〉解除锁定。到这里,我们的顶点缓冲算是真正的准备完毕了。
Render
我们也细微地改动了这个函数。首先我们捆绑了顶点缓冲到设备数据流,然后设置了顶点着色方法,最后,调用了
DrawPrimitive来完成绘制工作。DrawPrimitive的第一个参数是绘图方法(就是在文章上面的3DPrimitive中
我们提到的方法),我们用了三角形列(D3DPT_TRIANGLELIST)。下个参数是起始顶点,最后是需要画的图形
的数目,这里我们设置为1,因为只有一个三角形要绘制。嗯,我们还用了D3DCOLOR_XRGB宏使背景成为了黑
色(0,0,0)。
SafeRelease
我们还创建了这个SafeRelease宏。此宏能安全而简便地释放COM对象。
Summary(摘要)
啊…,第二个教程完成了,你一定有所收获吧?我相信是的。不过我挺累,我已经尽力翻译好了,怎么样?
在此,我们学习了很多理论性的东西,不过,应该很容易,对吧?啊对,你可以随意改动本文的例子,绘制更多的三
角形、更漂亮的图案,译者本人(我)就是这样做的,很有意思。下个教程,我们就会创建真正的3D图形了!怎么
样?激动吧?呵呵呵呵….
总是我在说,也的给原著点面子吧!
So,'velearntalotinthistutorial:
Vertices,CoordinatedSystems,3DPrimitives,FVF,
thistutorialabitfurther,exttutorialwewillcreateourfirstreal
3Dobject.
ndyPike
-15-
第三章:旋转的立方体
Introduction(序)
上一章,我们绘制了一个2D的三角形,很漂亮。这回呢,我们要创建我们的第一个3D对象:立方体。为了要显示
它是真正的3D对象,呵呵,我们要旋转它,这样,你将会看到它的各个面。记住:所有的3D对象都是由三角形构
成的(译者:神奇的三角形啊…读者:又来了),所以,我们的立方体是由12个三角形构成的(6个面,每个面需
要2个三角形,二六一十二,乘法口诀,会不?)。
3DWorldSpace(3D世界空间)
上一章我们见到了左手3D迪卡尔坐标系统是怎样工作的,此坐标系统从现在开始将被我们应用。
DirectX的默认是左手坐标系统,以后我们就用左手坐标系统。至于左手坐标系统和右手坐标系统的差别,在此就不
多说了,伸出手来就知道了。
BackfaceCulling(背面拣选)
什么是背面拣选?这是其实是一个很简单的概念。就是:所有的三角形面,面向我们的面将会被渲染出来(可
见),否则将不被渲染(不可见,被拣选出来了)。多么简练的一句话,如果你没明白,我举个例子:假如空间中
有一个正方形面(要想象成一块方形纸片),我们把正面涂成红色,背面涂成蓝色,你把它拿在手里,如果你没有弄
弯它,那你始终只会看见面向你的那个面,而不会看见它背面(有透视眼者另当别论)。那么这个“背面拣选”在
Direct3D中有什么用呢?假如我们创建了一个封闭的立方体(要想象成一个方纸盒),那末里面的六个面将不会被
我们看见,所以,也不用被渲染。“背面拣选”将使渲染更有效率。
那么怎么知道那个面被渲染而哪个面被拣选(不渲染)呢?一切都在你定义的顶点的顺序上。下面的两幅图演示了如
何定义“顺时针”的三角形。如果你定义的三角形是顺时针的,将会被渲染出来;但如果你把它翻过来,那它就变成
逆时针的了,就不会被渲染出来。嗯,其实你可以指定哪个面将被拣选,顺时针或逆时针,但是DX默认是拣选逆时
针的三角形。
Fig3.1Fig3.2
Howtomakeacube(怎样创建立方体)
下面的两幅图演示了我们的立方体将被怎样创建出来。这里我们用了三个三角带:一个是顶,一个是四个连起来的
面,一个是底。下面的图显示了每个三角形带及它们的顶点排列方法。顶点被排列成0到17,也会按照这个顺序被
放到顶点缓冲中。还有一幅图显示了我们的立方体(Fig3.4)。请注意图中哪些顶点重合了,还有就是除了立方体的
底面之外的其它面都是顺时针的顶点排列。这是因为我们激活了上面提到的“背面拣选”。
ndyPike
-16-
Fig3.3
Fig3.4
ndyPike
-17-
Matrices(矩阵)
矩阵是什么呢?它其实是一个数学主题,所以,在此我只能简要地概括一下。矩阵就像是一个表格,有一定数目的行
与列;每个格子中都一个数字或表达式。通过特定的矩阵,我们可以对3D对象中的顶点坐标进行运算,来实现类似
移动、旋转、缩放这样的操作。详细内容请参考线性代数学。在DirectX中,矩阵都是4X4的表格。矩阵的类型有
三种:WorldMatrix(世界矩阵)、ViewMatrix(视图矩阵)和ProjectionMatrix(投影矩阵)。
WorldMatrix(世界矩阵)
你可以利用世界矩阵在3D空间(世界空间)里通过修改对象的坐标来完成它们的旋转、缩放或平移操作。所有这些
操作(变换)都是关于原点(0,0,0)的。单一的变幻(例如平移或缩放等)可以组合起来,形成更复杂的变幻;
但要注意各种变幻的顺序很重要,矩阵1X矩阵2和矩阵2X矩阵1是不同的。还有,当你完成了一项世界变幻的矩
阵操作时,矩阵跟着将转变所有的顶点。要旋转两个对象,一个关于x轴,一个关于y轴,你应该先完成x轴的变
幻,然后渲染第一个对象;再完成y轴的变幻,然后渲染第二个对象。
ViewMatrix(视图矩阵)
视图矩阵就是摄像机(或眼睛)。摄像机在世界空间中有一个位置,还有一个观察点。例如,你可以把摄像机悬置于
某个对象的上面(摄像机位置),把镜头对准那个对象的中心(观察点)。你也可以指定哪面是上面,在我们下面的
例子中,我们指定了y轴的正方向是上面。
ProjectionMatrix(投影矩阵)
投影矩阵可以被想象成摄像机的镜头,它指定了视界(fieldofview)和前、后裁剪平面。目前我们会在例子中保持一
致的设置。
好了,第三章的源代码就在这了,又做了一些新的变化。
#include
LPDIRECT3D8g_pD3D=NULL;
LPDIRECT3DDEVICE8g_pD3DDevice=NULL;
LPDIRECT3DVERTEXBUFFER8g_pVertexBuffer=NULL;//Buffertoholdvertices
structCUSTOMVERTEX
{
FLOATx,y,z;
DWORDcolour;
};
#defineD3DFVF_CUSTOMVERTEX(D3DFVF_XYZ|D3DFVF_DIFFUSE)
#defineSafeRelease(pObject)if(pObject!=NULL){pObject->Release();pObject=NULL;}
HRESULTInitialiseD3D(HWNDhWnd)
{
//Firstofall,createdsuccessfullywe
//shouldgetapointertoanIDirect3D8interface.
g_pD3D=Direct3DCreate8(D3D_SDK_VERSION);
if(g_pD3D==NULL)
{
returnE_FAIL;
}
//Getthecurrentdisplaymode
D3DDISPLAYMODEd3ddm;
if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,&d3ddm)))
{
returnE_FAIL;
}
ndyPike
-18-
//Createastructuretoholdthesettingsforourdevice
D3DPRESENT_PARAMETERSd3dpp;
ZeroMemory(&d3dpp,sizeof(d3dpp));
//Fillthestructure.
//Wewantourprogramtobewindowed,andsetthebackbuffertoaformat
//thatmatchesourcurrentdisplaymode
ed=TRUE;
fect=D3DSWAPEFFECT_COPY_VSYNC;
fferFormat=;
//CreateaDirect3Ddevice.
if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp,
&g_pD3DDevice)))
{
returnE_FAIL;
}
//Turnonbackfaceculling.
//Thisisbecausewewanttohidethebackofourpolygons
g_pD3DDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW);
//Turnofflightingbecausewearespecifyingthatourverticeshavecolour
g_pD3DDevice->SetRenderState(D3DRS_LIGHTING,FALSE);
returnS_OK;
}
HRESULTInitialiseVertexBuffer()
{
VOID*pVertices;
//Storeeachpointofthecubetogetherwithit'scolour
//Makesurethatthepointsofapolygonarespecifiedinaclockwisedirection,
//thisisbecauseanti-clockwisefaceswillbeculled
//Wewilluseathreetrianglestripstorenderthesepolygons
//(Top,Sides,Bottom).
CUSTOMVERTEXcvVertices[]=
{
//TopFace
{-5.0f,5.0f,-5.0f,D3DCOLOR_XRGB(0,0,255),},//Vertex0-Blue
{-5.0f,5.0f,5.0f,D3DCOLOR_XRGB(255,0,0),},//Vertex1-Red
{5.0f,5.0f,-5.0f,D3DCOLOR_XRGB(255,0,0),},//Vertex2-Red
{5.0f,5.0f,5.0f,D3DCOLOR_XRGB(0,255,0),},//Vertex3-Green
//Face1
{-5.0f,-5.0f,-5.0f,D3DCOLOR_XRGB(255,0,0),},//Vertex4-Red
{-5.0f,5.0f,-5.0f,D3DCOLOR_XRGB(0,0,255),},//Vertex5-Blue
{5.0f,-5.0f,-5.0f,D3DCOLOR_XRGB(0,255,0),},//Vertex6-Green
{5.0f,5.0f,-5.0f,D3DCOLOR_XRGB(255,0,0),},//Vertex7-Red
//Face2
{5.0f,-5.0f,5.0f,D3DCOLOR_XRGB(0,0,255),},//Vertex8-Blue
{5.0f,5.0f,5.0f,D3DCOLOR_XRGB(0,255,0),},//Vertex9-Green
//Face3
{-5.0f,-5.0f,5.0f,D3DCOLOR_XRGB(0,255,0),},//Vertex10-Green
{-5.0f,5.0f,5.0f,D3DCOLOR_XRGB(255,0,0),},//Vertex11-Red
//Face4
{-5.0f,-5.0f,-5.0f,D3DCOLOR_XRGB(255,0,0),},//Vertex12-Red
{-5.0f,5.0f,-5.0f,D3DCOLOR_XRGB(0,0,255),},//Vertex13-Blue
ndyPike
-19-
//BottomFace
{5.0f,-5.0f,-5.0f,D3DCOLOR_XRGB(0,255,0),},//Vertex14-Green
{5.0f,-5.0f,5.0f,D3DCOLOR_XRGB(0,0,255),},//Vertex15-Blue
{-5.0f,-5.0f,-5.0f,D3DCOLOR_XRGB(255,0,0),},//Vertex16-Red
{-5.0f,-5.0f,5.0f,D3DCOLOR_XRGB(0,255,0),},//Vertex17-Green
};
//Createthevertexbufferfromourdevice.
if(FAILED(g_pD3DDevice->CreateVertexBuffer(18*sizeof(CUSTOMVERTEX),
0,D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT,&g_pVertexBuffer)))
{
returnE_FAIL;
}
//Getapointertothevertexbufferverticesandlockthevertexbuffer
if(FAILED(g_pVertexBuffer->Lock(0,sizeof(cvVertices),(BYTE**)&pVertices,0)))
{
returnE_FAIL;
}
//Copyourstoredverticesvaluesintothevertexbuffer
memcpy(pVertices,cvVertices,sizeof(cvVertices));
//Unlockthevertexbuffer
g_pVertexBuffer->Unlock();
returnS_OK;
}
voidSetupRotation()
{
//Herewewillrotateourworldaroundthex,yandzaxis.
D3DXMATRIXmatWorld,matWorldX,matWorldY,matWorldZ;
//Createthetransformationmatrices
D3DXMatrixRotationX(&matWorldX,timeGetTime()/400.0f);
D3DXMatrixRotationY(&matWorldY,timeGetTime()/400.0f);
D3DXMatrixRotationZ(&matWorldZ,timeGetTime()/400.0f);
//Combinethetransformationsbymultiplyingthemtogether
D3DXMatrixMultiply(&matWorld,&matWorldX,&matWorldY);
D3DXMatrixMultiply(&matWorld,&matWorld,&matWorldZ);
//Applythetansformation
g_pD3DDevice->SetTransform(D3DTS_WORLD,&matWorld);
}
voidSetupCamera()
{
//Herewewillsetupthecamera.
//Thecamerahasthreesettings:"CameraPosition","LookatPosition"and
//"UpDirection"
//Wehavesetthefollowing:
//CameraPosition:(0,0,-30)
//LookatPosition:(0,0,0)
//Updirection:Y-Axis.
D3DXMATRIXmatView;
D3DXMatrixLookAtLH(&matView,&D3DXVECTOR3(0.0f,0.0f,-30.0f),//CameraPosition
&D3DXVECTOR3(0.0f,0.0f,0.0f),//LookAtPosition
ndyPike
-20-
&D3DXVECTOR3(0.0f,1.0f,0.0f));//UpDirection
g_pD3DDevice->SetTransform(D3DTS_VIEW,&matView);
}
voidSetupPerspective()
{
//Herewespecifythefieldofview,aspectrationandnearand
//farclippingplanes.
D3DXMATRIXmatProj;
D3DXMatrixPerspectiveFovLH(&matProj,D3DX_PI/4,1.0f,1.0f,500.0f);
g_pD3DDevice->SetTransform(D3DTS_PROJECTION,&matProj);
}
voidRender()
{
if(g_pD3DDevice==NULL)
{
return;
}
//Clearthebackbuffertoblack
g_pD3DDevice->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);
//Beginthescene
g_pD3DDevice->BeginScene();
//Setuptherotation,camera,andperspectivematrices
SetupRotation();
SetupCamera();
SetupPerspective();
//Renderingourobjects
g_pD3DDevice->SetStreamSource(0,g_pVertexBuffer,sizeof(CUSTOMVERTEX));
g_pD3DDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX);
g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,2);//Top
g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,4,8);//Sides
g_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,14,2);//Bottom
//Endthescene
g_pD3DDevice->EndScene();
//Filpthebackandfrontbufferssothatwhateverhasbeenrenderedonthe
//backbufferwillnowbevisibleonscreen(frontbuffer).
g_pD3DDevice->Present(NULL,NULL,NULL,NULL);
}
voidCleanUp()
{
SafeRelease(g_pVertexBuffer);
SafeRelease(g_pD3DDevice);
SafeRelease(g_pD3D);
}
voidGameLoop()
{
//Enterthegameloop
MSGmsg;
BOOLfMessage;
PeekMessage(&msg,NULL,0U,0U,PM_NOREMOVE);
ndyPike
-21-
while(e!=WM_QUIT)
{
fMessage=PeekMessage(&msg,NULL,0U,0U,PM_REMOVE);
if(fMessage)
{
//Processmessage
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//Nomessagetoprocess,sorenderthecurrentscene
Render();
}
}
}
//Thewindowsmessagehandler
LRESULTWINAPIWinProc(HWNDhWnd,UINTmsg,WPARAMwParam,LPARAMlParam)
{
switch(msg)
{
caseWM_DESTROY:
PostQuitMessage(0);
return0;
break;
caseWM_KEYUP:
switch(wParam)
{
caseVK_ESCAPE:
//Userhaspressedtheescapekey,soquit
DestroyWindow(hWnd);
return0;
break;
}
break;
}
returnDefWindowProc(hWnd,msg,wParam,lParam);
}
//Applicationentrypoint
INTWINAPIWinMain(HINSTANCEhInst,HINSTANCE,LPSTR,INT)
{
//Registerthewindowclass
WNDCLASSEXwc={sizeof(WNDCLASSEX),CS_CLASSDC,WinProc,0L,0L,
GetModuleHandle(NULL),NULL,NULL,NULL,NULL,
"DXProject3",NULL};
RegisterClassEx(&wc);
//Createtheapplication'swindow
HWNDhWnd=CreateWindow("DXProject3",":Tutorial3",
WS_OVERLAPPEDWINDOW,50,50,500,500,
GetDesktopWindow(),NULL,nce,NULL);
//InitializeDirect3D
if(SUCCEEDED(InitialiseD3D(hWnd)))
{
//Showourwindow
ShowWindow(hWnd,SW_SHOWDEFAULT);
ndyPike
-22-
UpdateWindow(hWnd);
//InitializeVertexBuffer
if(SUCCEEDED(InitialiseVertexBuffer()))
{
//Startgamerunning:Enterthegameloop
GameLoop();
}
}
CleanUp();
UnregisterClass("DXProject3",nce);
return0;
}
运行例子,你将会得到下面这样的屏幕:
怎么样?一个旋转的、多彩的立方体!
那末,我们都改动了什么呢?
Includeandlibfiles
我们用新的头文件“d3dx8.h”代替了原来的“d3d8.h”。
项目设置中我们要加入两个Lib文件,“”和“”。
CUSTOMVERTEX
我们自定义的顶点格式,还记得吗?这回我们改动了它:只包含x,y,z和颜色值。这可以使我们在3D空间中指
定一个顶点和它的颜色。
D3DFVF_CUSTOMVERTEX
为了和上面我们自定义的顶点各式相匹配,我们当然还要改动我们的FVF。这回我们用了两个标识:
D3DFVF_XYZ和D3DFVF_DIFFUSE。
ndyPike
-23-
InitaliseD3D
在此,我们用SetRenderState函数激活了背面拣选。记住我们用D3DCULL_CCW符号指定了DirectX拣选逆时
针的面。
我们又用了SetRenderState函数去除了灯光,因为我们为每个顶点都设置了颜色(灯光)。
InitaliseVertexBuffer
在此我们为我们的18个顶点设了值。每个顶点都有注解和标号,和上面的图中是一样的。立方体的中心是(0,0,
0),它的长宽高都是10。
SetupRotation
一个新的函数,在此我们调用了D3DXMatrixRotationX、D3DXMatrixRotationY和D3DXMatrixRotationZ函
数产生了3个矩阵而且分别将它们保存在了3个D3DXMATRIX结构中;然后我们将它们相乘后形成了世界矩阵;
我们又调用了SetTransform函数为我们的顶点应用了此变换。
SetupCamera
又一个新的函数,在这里我们设置摄像机。我们将摄像机放在了(0,0,-30)处,然后将观察点设置为(0,0,
0)。(我们已经把立方体放在原点了)我们还设置了y轴的正方向为“上面”。我们用D3DXMatrixLookAtLH生
成了视图矩阵然后调用了SetTransform应用了变换。
SetupPerspective
又一个新函数。在这里我们设置摄像机的镜头:我们确定了视界为PI/4(正常)而横纵比为1。我们还确定了前、
后裁剪平面分别为1和500,这意味着范围之外的三角形将会被裁剪掉。
Render
在此渲染函数里,我们调用了三个新的函数:SetupRotation,SetupCamera和SetupPerspective,这些函数会
在我们渲染三角形之前被调用。
还记得吗?我们用了三个三角形带:一个是顶,一个是四个连起来的面,一个是底。
Summary(摘要)
让老外说先:
That'tutorialwelearntaboutBackfaceCulling,Matrices,3DWorld
exttutorial,wewillarrangeour
codesofarintoclassesandmakeourapplicationfullscreen.
第三章的教程就到这了,真是累死我了!老外写的东西毕竟不像是直接读中文那样好理解,就像是编译执行与解释执
行那样差别很大!不过,我已经把它编译好了,你可以直接运行了!:)我可是尽力翻译好了,不知道对你有没有帮
助?什么?有的地方弄错了?欢迎与我联系:E-mail:************************QQ:15852673,Aman就
是我了。嗯,此章中,我们了解了背面拣选、矩阵、3D世界空间还有怎样用三角形制作立方体,你一定已经学会了
吧?呵呵,下一张有更精彩的内容等着你!
第四章:全屏幕模式和深度缓冲
Introduction(序)
在此章中,我们把以往单个的文件()转换成了两个类:CGame和CCuboid。CGame包含了主要的代
码,例如游戏的初始化和循环、渲染工作都有它来完成。CCuboid用来创建立方体对象:你可以指定位置与尺寸。
CCuboid也包含一个渲染函数,将会在CGame的渲染函数中被调用。其实程序并没有作什么太大的改动,你会发
现它会像以往一样容易理解。我们改动了程序使它成为了全屏幕的模式,这在某方面这要比窗口式的强多了;我们还
会接触一下深度缓冲。从现在起,程序代码不会像以往一样全部放在教程中了(因为很长,放进来也不容易看);取
而代之,我会只放进来一些有意义的代码片断,其实这样也好。
DepthBuffers(深度缓冲)
嗯,深度缓冲,听起来挺神秘的,是吧?但我要告诉你,这又是一个简单的概念。深度缓冲(又称Z-buffer)的作用
是确保多边形能够正确地显示在它们本来的深度(相对于摄像机)上。例如在你的场景中有两个矩形:一个是蓝色
ndyPike
-24-
的而另一个是绿色的;蓝色的Z值为10,绿色的Z值为20(摄像机在原点);这就意味着蓝色的在绿色的前面
(看下面的图示)。深度缓冲能确定哪个对象在另一个对象的前面,正确的将被渲染。DirectX会测试对象在屏幕上
的像素点到摄像机的远近,并把得出的值保存在深度缓冲中;接着,DirectX会测试同一位置另一对象的像素点,并
和刚才的像素进行比较:如果更近,就刷新刚才的纪录,否则就不理睬(有东西在它前面挡着它)。这会决定此位置
像素点的颜色到底是蓝色的还是绿色的。下面是图示:
Fig4.1
要在程序中使用深度缓冲是很简单的事情,所有要做的只有:在初始化D3D的函数中选择正确的格式、激活深度缓
冲和确保在渲染函数中清空深度缓冲。在此章的源代码中,CGame的InitialiseD3D模块里,我增加了一些选择
深度缓冲格式的源代码。D3DPRESENT_PARAMETERS结构中,有两个参数要设置:
pthStencilFormat=D3DFMT_D16;
AutoDepthStencil=TRUE;
要激活深度缓冲,需要在你的InitialiseD3D模块中增加此行代码:
m_pD3DDevice->SetRenderState(D3DRS_ZENABLE,D3DZB_TRUE);
然后确保在CGame的Render模块中增加了D3DCLEAR_ZBUFFER标识使设备清除函数(Clear)能正确地清空
深度缓冲:
m_pD3DDevice->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,
0),1.0f,0);
FullScreen(全屏幕)
要使用全屏幕模式,我们需要调整一下InitialiseD3D模块:把D3DPRESENT_PARAMETERS结构的
“Windowed”参数设置为FALSE,通知D3D我们要使用全屏幕模式;然后,我们还需要设置
D3DPRESENT_PARAMETERS结构中的两个全屏幕参数:FullScreen_RefreshRateInHz用于设置屏幕刷新率,
FullScreen_PresentationInterval用于设置页翻动的最大速度,我们选择了D3DPRESENT_INTERVAL_ONE
标识来限定仅当屏幕刷新时完成页翻动。
译者注:上面的后几句是按照原意翻译的,但我想这样可能不太好懂,我要在此大概地说明一下:当电子束从上至下
完成了屏幕上的每一行水平回扫后,会被暂时关闭,回到屏幕的左上角;此过程所需的时间被称为垂直回扫周期或
屏幕空白周期。有时,程序能完成的刷新速率(页翻动速度)可能要比屏幕刷新率还高,所以我们要使它在限定的
垂直回扫周期内完成刷新工作,否则将产生“图像撕裂”。原文上述的设定为至少等待一个垂直回扫周期(一次的屏
幕刷新完成后)来完成页翻动操作。
ndyPike
-25-
ed=FALSE;
reen_RefreshRateInHz=D3DPRESENT_RATE_DEFAULT;
reen_PresentationInterval=D3DPRESENT_INTERVAL_ONE;
嗯,我们还应该选择正确的后缓冲区格式。我写了一个模块CheckDisplayMode用来测试正确的格式,此模块会在
InitialiseD3D模块中被调用。
D3DFORMATCGame::CheckDisplayMode(UINTnWidth,UINTnHeight,UINTnDepth)
{
UINTx;
D3DDISPLAYMODEd3ddm;
for(x=0;x
{
m_pD3D->EnumAdapterModes(0,x,&d3ddm);
if(==nWidth)
{
if(==nHeight)
{
if((==D3DFMT_R5G6B5)||
(==D3DFMT_X1R5G5B5)||
(==D3DFMT_X4R4G4B4))
{
if(nDepth==16)
{
;
}
}
elseif((==D3DFMT_R8G8B8)||
(==D3DFMT_X8R8G8B8))
{
if(nDepth==32)
{
;
}
}
}
}
}
returnD3DFMT_UNKNOWN;
}
还有一件要做的事就是我们修改了WinMain函数中的创建窗口部分,我们用了GetSystemMetrics函数得到了当
前屏幕的宽与高,然后将我们的窗口设置成了与屏幕一样大。
Logging(实时记录)
我们的程序还有了写纪录的功能,这在调试时是很有用的。我为CGame添加了EnableLogging和WriteToLog
模块,而且在WinMain刚刚完成CGame的初始化时就调用了EnableLogging,它会清理当前的纪录然后激活纪
录功能。一旦记录功能被激活了,WriteToLog就能单纯地向记录文件中写记录了。当程序关闭后,记录工作就会完
成。你要记住:程序的显示速率不会高于屏幕刷新率,那是因为我们设置了FullScreen_PresentationInterval为
D3DPRESENT_INTERVAL_ONE。程序运行后记录文件会存在于你的程序目录中,名为“”。
运行程序会得到下面的画面:六个不同大小和位置的立方体,怎么样?
ndyPike
-26-
Summary(摘要)
老外说:
Wehaven'treallyaddedalotofcodeinthistutorial,
sometimetofamiliariseyourselfwiththenewstructure,you'llseethatmostofthefunctionsare
reenrenderinganddepthbuffersareprettystraightforward;nowthatwe've
createdthemwedon'exttutorialwewilllook
hedontheminthelasttutorial,butthistimewewillgoalittle
furtheranddemonstratethepowerofmatrixtransformations.
俺说:此章中,我们学习了全屏幕模式与深度缓冲,这是令人兴奋的。其实慢慢学来,一切并非想象的那样难。你认
为呢?好了,又翻译了一章,还真有点累,去歇一会儿了先…..啊,对了,下一章我们要学的是:神奇的------------
-------矩阵变换。(读者:切~拉长音卖关子)
第五章:矩阵变换
Introduction(序)
啊哈,新的一章又开始了!这一章我们要加深一下对矩阵的了解。正如以前提到过的,使用矩阵,你可以旋转、缩放
或移动顶点(进而对整个对象也能做这些变换)。此章我们会看到怎样按照不同的方法来旋转五个立方体:绕x
轴、绕y轴、绕z轴、绕自定义轴和绕所有轴。
Whatarematricesandhowdotheywork?(矩阵是什么和它们怎样工作)
矩阵其实是一个高级的数学主题(参看线性代数),所以,在此我只能尽力地、简要地概括一下。矩阵就像是一个表
格,有一定数目的行与列;每个格子中都一个数字或表达式。通过特定的矩阵,我们可以对3D对象中的顶点坐标进
行运算,来实现类似移动、旋转、缩放这样的操作。在DirectX中,矩阵就是4X4的数表。下面的这幅图是一个矩
阵的例子,这个矩阵能使一个对象(由它的所有顶点)缩放到原来的五倍。
ndyPike
-27-
Fig5.1
OK,矩阵是怎样改变顶点的位置的呢?要改变一个顶点的x,y与z值,你应该把它们与某个矩阵相乘。下面的图
例演示了顶点是怎样与矩阵相乘的。
Fig5.2
这其实是一种简单的计算,你只需要将x、y和z值分别与每列上的数相乘再相加,每列上得出的数都是新顶点的一
个坐标值,这在上图中是显而易见的。你可能已经注意到我们上面的图例中在当前顶点(currentvertex)的z值
后面还有一个“1”,那是为了给矩阵的运算提供可行性和方便性的(最好参看一下相关的数学资料)。你需要同样
数表来完成矩阵变换。
所以,你只需操作矩阵就能完成这些类似旋转、缩放或移动的变幻。幸运的是,DirectX给我们提供了一些函数能方
便地生成一些通常的矩阵。那末,怎样完成即缩放又旋转(复合变换)的变幻呢?嗯,首先,我们需要两个矩阵:一
个用来旋转的一个用来缩放的;然后,我们把两个矩阵相乘,得出一个新的复合矩阵,即缩放又旋转的矩阵;然后利
用这个新的矩阵来变幻顶点。应该注意的是,矩阵相乘并不是普通的乘法,而且,也不满足交换率:矩阵AX矩阵
B和矩阵BX矩阵A是不相等的。下面的图表演示了怎样把两个矩阵相乘:
ndyPike
-28-
Fig5.3
要把两个矩阵相乘,你需要把第一个矩阵的每一行和第二个矩阵的每一列都相乘。在上面的例子中,我们把第一个矩
阵的第一行的每个元素与第二个矩阵的第一列的对应元素相乘,然后把得出的四个结果相加,按此方法,我们得出了
新矩阵的第一行的四个元素(Column1-4),其它元素的计算方法依此类推。这可能看起来有些复杂,不过,就像
上面我提到的,DirectX提供了一些矩阵操作的函数,所以不要对此太担心。
HowdoIusetransformationmatricesinDirectX?
(在DirectX中怎样使用变换矩阵)
在教程此章的例子中,我们有5个不同位置、做不同旋转的立方体。下面是我们的代码预排,让我们一步步地来:
Step1:Createobjects(第一步,创建对象)
第一件要做的事就是创建定义我们的五个立方体。我们像定义变量那样定义了五个立方体,而且修改了CGame的
InitialiseGame来创建和设置它们,不过我们没有改变它们的尺寸。InitialiseGame模块现在是这样的:
boolCGame::InitialiseGame()
{
//Setupgamesobjectshere
m_pCube1=newCCuboid(m_pD3DDevice);
m_pCube1->SetPosition(-27.0,0.0,0.0);
m_pCube2=newCCuboid(m_pD3DDevice);
m_pCube2->SetPosition(-9.0,0.0,0.0);
m_pCube3=newCCuboid(m_pD3DDevice);
m_pCube3->SetPosition(9.0,0.0,0.0);
m_pCube4=newCCuboid(m_pD3DDevice);
m_pCube4->SetPosition(27.0,0.0,0.0);
m_pCube5=newCCuboid(m_pD3DDevice);
m_pCube5->SetPosition(0.0,15.0,0.0);
returntrue;
}
ndyPike
-29-
下图演示了五个立方体的初始位置。我们通常可能喜欢在原点上创建它们,然后再调动它们。但是按照教程的意图,
我们将像下图那样创建它们。
Fig5.4
Step2:CreateTransformationMatrices(第二步,创建变换矩阵)
第二步是要创建变换矩阵。我们想要每个立方体做不同的旋转,就需要给每个立方体不同的变换矩阵。立方体1、
2、3将分别绕x、y、z轴旋转;立方体4将按我们自定义的轴旋转;而立方体5将绕x、y、z轴旋转,并且会被
放大。
要创建绕x、y、z轴的旋转矩阵,我们将分别用到DX的DXD3DXMatrixRotationX、D3DXMatrixRotationY和
D3DXMatrixRotationZ函数:第一个参数是指向D3DXMATRIX结构变量的指针,用于保存矩阵;第二个参数是
要旋转的弧度。下面是来自我们的新的Render模块的片断:
//Createtherotationtransformationmatricesaroundthex,yandzaxis
D3DXMatrixRotationX(&matRotationX,timeGetTime()/400.0f);
D3DXMatrixRotationY(&matRotationY,timeGetTime()/400.0f);
D3DXMatrixRotationZ(&matRotationZ,timeGetTime()/400.0f);
然后要做的就是创建我们自定义的旋转轴。这时我们需要用到DX的D3DXMatrixRotationAxis函数:第一个参数
还是指向D3DXMATRIX结构变量的指针,用于保存矩阵;第二个参数是一个指向D3DXVECTOR3结构变量的指
针,用于定义我们自己的轴。我们想要一个在x、y轴之间成45度角的轴,所以我们用了一个向量(1,1,0);第
三个参数是要旋转的弧度。看下面的片断和图示:
//Createtherotationtransformationmatricesaroundouruserdefinedaxis
D3DXMatrixRotationAxis(&matRotationUser1,&D3DXVECTOR3(1.0f,1.0f,0.0f),
timeGetTime()/400.0f);
ndyPike
-30-
Fig5.5
我们还需要创建一些矩阵,例如缩放立方体5的矩阵。还有就是一些能将立方体移动到原点然后再移回来的矩阵
(稍候解释)。下面是代码:
//Createthetranslation(move)matrices
D3DXMatrixTranslation(&matMoveRight27,27.0,0.0,0.0);
D3DXMatrixTranslation(&matMoveLeft27,-27.0,0.0,0.0);
D3DXMatrixTranslation(&matMoveRight9,9.0,0.0,0.0);
D3DXMatrixTranslation(&matMoveLeft9,-9.0,0.0,0.0);
D3DXMatrixTranslation(&matMoveDown15,0.0,-15.0,0.0);
D3DXMatrixTranslation(&matMoveUp15,0.0,15.0,0.0);
//Createascaletransformation
D3DXMatrixScaling(&matScaleUp1p5,1.5,1.5,1.5);
Step3:MultiplyMatrices(第三步,把矩阵相乘)
现在,我们有了各种变幻矩阵。接下来我们需要为每个立方体创建一个复合的变幻矩阵,这需要DX所提供的
D3DXMatrixMultiply函数。立方体1简单,我们要让它绕x轴旋转,而它就在x轴上,这意味着我们不需要为它
做矩阵相乘的运算,因为我们已经有了x轴的旋转矩阵。立方体2-5有些不同,因为它们不在我们想让它们旋转的
轴上。所以,我们需要把它们移动到旋转轴(我们想要的,x、y或z)上,然后旋转他们,再把它们移回到原来的
位置(下图5.7)。如果我们直接对他们做旋转操作而不移动,那他们将绕所给轴做摆动(下图5.6)。下面是图
示:
ndyPike
-31-
Fig5.6
Fig5.7
我们有了立方体1的变幻矩阵,下面我们将创建立方体2-5的变幻矩阵。代码如下。要注意的是矩阵相乘的顺序,
要不然会得出不同的结果。
//Combinethematricestoform4transformationmatrices
D3DXMatrixMultiply(&matTransformation2,&matMoveRight9,&matRotationY);
D3DXMatrixMultiply(&matTransformation2,&matTransformation2,&matMoveLeft9);
D3DXMatrixMultiply(&matTransformation3,&matMoveLeft9,&matRotationZ);
D3DXMatrixMultiply(&matTransformation3,&matTransformation3,&matMoveRight9);
D3DXMatrixMultiply(&matTransformation4,&matMoveLeft27,&matRotationUser1);
D3DXMatrixMultiply(&matTransformation4,&matTransformation4,&matMoveRight27);
D3DXMatrixMultiply(&matTransformation5,&matMoveDown15,&matRotationY);
ndyPike
-32-
D3DXMatrixMultiply(&matTransformation5,&matTransformation5,&matRotationX);
D3DXMatrixMultiply(&matTransformation5,&matTransformation5,&matRotationZ);
D3DXMatrixMultiply(&matTransformation5,&matTransformation5,&matMoveUp15);
D3DXMatrixMultiply(&matTransformation5,&matTransformation5,&matScaleUp1p5);
Step4:ApplyingtheTransformations(应用变换)
嗯,现在我们每个立方体都有了自己的变换矩阵,我们需要为它们应用这些变换并且渲染他们。要应用一个变换矩
阵,需要调用SetTransform模块。当应用了一种变换之后,以后所有的对象都将按此变换被渲染。所以,我们需
要为每个立方体都调用一次SetTransform(用它们自己的变换矩阵),然后渲染它们。下面是代码:
//Applythetransformationsandrenderourobjects
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matRotationX);
m_pCube1->Render();
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matTransformation2);
m_pCube2->Render();
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matTransformation3);
m_pCube3->Render();
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matTransformation4);
m_pCube4->Render();
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matTransformation5);
m_pCube5->Render();
崭新的Render模块这儿了。有件事要注意,我把SetupPerspective()的代码都移动到SetupCamera()中了,为
了代码简洁。
voidCGame::Render()
{
D3DXMATRIXmatRotationX,matRotationY,matRotationZ,matRotationUser1;
D3DXMATRIXmatMoveRight27,matMoveLeft27,matMoveRight9;
D3DXMATRIXmatMoveLeft9,matMoveDown15,matMoveUp15;
D3DXMATRIXmatTransformation2,matTransformation3;
D3DXMATRIXmatTransformation4,matTransformation5;
D3DXMATRIXmatScaleUp1p5;
if(m_pD3DDevice==NULL)
{
return;
}
//Clearthebackbufferanddepthbuffer
m_pD3DDevice->Clear(0,NULL,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0,0,0),1.0f,0);
//Beginthescene
m_pD3DDevice->BeginScene();
//Setupcameraandperspective
SetupCamera();
//Createtherotationtransformationmatricesaroundthex,yandzaxis
D3DXMatrixRotationX(&matRotationX,timeGetTime()/400.0f);
D3DXMatrixRotationY(&matRotationY,timeGetTime()/400.0f);
D3DXMatrixRotationZ(&matRotationZ,timeGetTime()/400.0f);
//Createtherotationtransformationmatricesaroundouruserdefinedaxis
ndyPike
-33-
D3DXMatrixRotationAxis(&matRotationUser1,&D3DXVECTOR3(1.0f,1.0f,0.0f),
timeGetTime()/400.0f);
//Createthetranslation(move)matrices
D3DXMatrixTranslation(&matMoveRight27,27.0,0.0,0.0);
D3DXMatrixTranslation(&matMoveLeft27,-27.0,0.0,0.0);
D3DXMatrixTranslation(&matMoveRight9,9.0,0.0,0.0);
D3DXMatrixTranslation(&matMoveLeft9,-9.0,0.0,0.0);
D3DXMatrixTranslation(&matMoveDown15,0.0,-15.0,0.0);
D3DXMatrixTranslation(&matMoveUp15,0.0,15.0,0.0);
//Createascaletransformation
D3DXMatrixScaling(&matScaleUp1p5,1.5,1.5,1.5);
//Combinethematricestoform4transformationmatrices
D3DXMatrixMultiply(&matTransformation2,&matMoveRight9,&matRotationY);
D3DXMatrixMultiply(&matTransformation2,&matTransformation2,&matMoveLeft9);
D3DXMatrixMultiply(&matTransformation3,&matMoveLeft9,&matRotationZ);
D3DXMatrixMultiply(&matTransformation3,&matTransformation3,&matMoveRight9);
D3DXMatrixMultiply(&matTransformation4,&matMoveLeft27,&matRotationUser1);
D3DXMatrixMultiply(&matTransformation4,&matTransformation4,&matMoveRight27);
D3DXMatrixMultiply(&matTransformation5,&matMoveDown15,&matRotationY);
D3DXMatrixMultiply(&matTransformation5,&matTransformation5,&matRotationX);
D3DXMatrixMultiply(&matTransformation5,&matTransformation5,&matRotationZ);
D3DXMatrixMultiply(&matTransformation5,&matTransformation5,&matMoveUp15);
D3DXMatrixMultiply(&matTransformation5,&matTransformation5,&matScaleUp1p5);
//Applythetransformationsandrenderourobjects
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matRotationX);
m_pCube1->Render();
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matTransformation2);
m_pCube2->Render();
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matTransformation3);
m_pCube3->Render();
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matTransformation4);
m_pCube4->Render();
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matTransformation5);
m_pCube5->Render();
//Endthescene
m_pD3DDevice->EndScene();
//Filpthebackandfrontbufferssothatwhateverhasbeenrenderedonthe
//backbufferwillnowbevisibleonscreen(frontbuffer).
m_pD3DDevice->Present(NULL,NULL,NULL,NULL);
//CountFrames
m_dwFrames++;
}
当作了这些变动之后,我们将得到下面这样的五个立方体。
ndyPike
-34-
Summary(摘要)
口干舌又燥,让老外说先:
Inthistutorialwelearntwhatamatrixis,howmatricesworkandhowtomultiplythemtogether.
exttutorialwe'll
takealookatextures.
这一章可真够长的了!可把我累坏了……怎么样?都学会了吗?其实,都很简单,对吗?矩阵,这章我们主要就学
了这个玩意。其实是线性代数的内容,你应该学习(复习)一下,有好处。下一章我们要学的是------纹理,呵呵,
我们的立方体要穿衣服了(读者:什么?原来它们一直裸体啊?……)。
第六章:纹理
Introduction(序)
这一章我们将学习什么是纹理和怎样使用纹理。纹理能使3D场景(对象)带有真实感。在本章的例子中,我们将使
上一章的五个立方体拥有不同的纹理:用不同的颜色为它们标号。
WhatisaTexture?(纹理是什么)
纹理就是3D图形中的2D贴图(位图),能应用到三角形(或一定数目的三角形)上,用来增加真实感。举个例
子:假如你想在你的场景中放置一座砖墙,你可能会在摄像机前创建一个正方形然后把它涂成红色。嗯,但是它看起
来只是一个红色的正方形而不像一座砖墙,有许多砖块甚至还带有一个窗子的那种。这时,纹理就有了用武之地。所
需的只是一个对象(你的正方形)和一个墙的纹理。你可以用任何绘画工具(例如Photoshop)来绘制纹理,甚至
是Windows自带的“画图”工具,只要它能支持位图。
TextureSize(纹理的尺寸)
理论上你可以创建任何尺寸的纹理,但是为了提高效率,纹理最好是正方形(长和宽一样)的而且它的边长最好是2
的n次幂的形式,例如16x16,32x32,64x64,128x128,256x256。256x256是最有效率的尺寸,
如果合适你用的话。记住:纹理越小越好。
ndyPike
-35-
TextureCoordinates(纹理坐标)
纹理坐标的作用是在纹理上指定一个点,因为纹理是2D的,所以我们仅需要两个值:U和V。U是横轴的值,而V
是纵轴的值,U和V的取值应该在0和1之间(取其它值用于生成特效,以后再说),左上角是(0,0)右下角是
(1,1)。下面的图例(6.1)演示了一个纹理和它的几个点。
Fig6.1
TextureMapping(纹理映射)
现在让我们了解纹理坐标。在场景中,我们可以把纹理应用到对象上,这个过程就叫做“纹理映射”。在此过程中,
纹理坐标会被映射到顶点上,所以,顶点将额外增加两个值:U和V。下面的图表(6.2)是一个纹理映射到立方体
的例子,例子中的立方体的顶点序号和第三章相同。
下面的表演示了每个顶点的纹理坐标(顶点序号=(U,V)):
0=(0,1)9=(0,0)
1=(0,0)10=(1,1)
2=(1,1)11=(1,0)
3=(1,0)12=(0,1)
4=(0,1)13=(0,0)
5=(0,0)14=(0,1)
6=(1,1)15=(0,0)
7=(1,0)16=(1,1)
8=(0,1)17=(1,0)
Fig6.2
ndyPike
-36-
UsingTexturesWithDirectX(在DirectX中使用纹理)
Step1:ModifyFVFandCustomVertex(第一步,修改FVF和自定义顶点)
第一件要做的事:我们需要修改自定义的顶点结构来保存纹理坐标U和V,它们是两个新的FLOAT型变量tu和
tv。我们还需要修改FVF,增加D3DFVF_TEX1标识与之相匹配。
//DefineaFVFforourcuboids
#defineCUBIOD_D3DFVF_CUSTOMVERTEX(D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
//Defineacustomvertexforourcuboids
structCUBIOD_CUSTOMVERTEX
{
FLOATx,y,z;
DWORDcolour;
FLOATtu,tv;
};
Step2:LoadTexture(读取纹理)
我们还需要给CCuboid类增加一个新的指针,用来保存被读取的纹理:它的名字是m_pTexture,
LPDIRECT3DTEXTURE8型变量。我们还需要一个用来从BMP文件中读取纹理的模块。
LPDIRECT3DTEXTURE8m_pTexture;
boolCCuboid::SetTexture(constchar*szTextureFilePath)
{
if(FAILED(D3DXCreateTextureFromFile(m_pD3DDevice,szTextureFilePath,
&m_pTexture)))
{
returnfalse;
}
returntrue;
}
Step3:SetTextureCoordinates(设置纹理坐标)
在我们的CCudoid类的UpdateVertices模块中,我们得为每个顶点设置纹理坐标。注意颜色值后面的两个新的纹
理坐标值U和V。顶点与纹理坐标的对应关系请参看表6.2。
boolCCuboid::UpdateVertices()
{
VOID*pVertices;
//Storeeachpointofthecubetogetherwithit'scolourandtexturecoordinates
//Makesurethatthepointsofapolygonarespecifiedinaclockwisedirection,
//thisisbecauseanti-clockwisefaceswillbeculled
//Wewilluseathreetrianglestripstorenderthesepolygons(Top,Sides,
//Bottom).
CUBIOD_CUSTOMVERTEXcvVertices[]=
{
//TopFace
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(0,0,255),0.0f,1.0f,},//Vertex0-Blue
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
D3DCOLOR_XRGB(255,0,0),0.0f,0.0f,},//Vertex1-Red
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(255,0,0),1.0f,1.0f,},//Vertex2-Red
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
D3DCOLOR_XRGB(0,255,0),1.0f,0.0f,},//Vertex3-Green
ndyPike
-37-
//Face1
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(255,0,0),0.0f,1.0f,},//Vertex4-Red
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(0,0,255),0.0f,0.0f,},//Vertex5-Blue
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(0,255,0),1.0f,1.0f,},//Vertex6-Green
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(255,0,0),1.0f,0.0f,},//Vertex7-Red
//Face2
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
D3DCOLOR_XRGB(0,0,255),0.0f,1.0f,},//Vertex8-Blue
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
D3DCOLOR_XRGB(0,255,0),0.0f,0.0f,},//Vertex9-Green
//Face3
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
D3DCOLOR_XRGB(0,255,0),1.0f,1.0f,},//Vertex10-Green
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
D3DCOLOR_XRGB(255,0,0),1.0f,0.0f,},//Vertex11-Red
//Face4
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(255,0,0),0.0f,1.0f,},//Vertex12-Red
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(0,0,255),0.0f,0.0f,},//Vertex13-Blue
//BottomFace
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(0,255,0),0.0f,1.0f,},//Vertex14-Green
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
D3DCOLOR_XRGB(0,0,255),0.0f,0.0f,},//Vertex15-Blue
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
D3DCOLOR_XRGB(255,0,0),1.0f,1.0f,},//Vertex16-Red
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
D3DCOLOR_XRGB(0,255,0),1.0f,0.0f,},//Vertex17-Green
};
//Getapointertothevertexbufferverticesandlockthevertexbuffer
if(FAILED(m_pVertexBuffer->Lock(0,sizeof(cvVertices),(BYTE**)&pVertices,0)))
{
returnfalse;
}
//Copyourstoredverticesvaluesintothevertexbuffer
memcpy(pVertices,cvVertices,sizeof(cvVertices));
//Unlockthevertexbuffer
m_pVertexBuffer->Unlock();
returntrue;
}
Step4:Rendering(渲染)
最后是CCuboid类的Render模块:我们用SetTexture来设置要渲染的纹理,用SetTextureStageState来设
置怎样渲染纹理。在此我们选择了不用任何混合或类似的效果的渲染方法,你可以依靠那些标识参数来混合纹理与纹
理(或顶点颜色)。
//Sethowthetextureshouldberendered.
ndyPike
-38-
if(m_pTexture!=NULL)
{
//'twanttoblendourtexturewith
//thecoloursofourvertices,souseD3DTOP_SELECTARG1
m_pD3DDevice->SetTexture(0,m_pTexture);
m_pD3DDevice->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1);
}
else
{
//lldisabletexturerendering.
m_pD3DDevice->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_DISABLE);
}
呵呵,运行程序会得到这样的屏幕:
Summary(摘要)
Inthistutorialwe'smoretotexturesinDirectXthanthat,and
we'exttutorial,we'lllookintolighting.
啊,又学完了一章。此章我们大概地学习了纹理,关于纹理的其它,我们以后会学到。下一章,我们要学习,呵呵,
灯光。
第七章:灯光与材质
Introduction(序)
新的一章又开始了,这一章,我们将学习DirectX中的灯光和材质。在例程中,我们将在原点附近创建四个旋转的
立方体,在它们的中间(原点附近),我们会放置一个光源,你将会看到光照的效果是怎样形成的。
rldLighting(DX光照对真实世界光照)
ndyPike
-39-
在DX中,我们能创建不同类型的光,使场景看起来更真实。但要记住,DX中的光只是在近似地模拟自然界的光。
在自然界中,光由光源(如灯泡)发出,然后延直线传播,直到消耗完毕或传入眼睛;光在遇到物体时会发生反射,
每次反射都会有能量的消耗;实际上,光可以在物体间反射千百万次,而且光滑表面会比不光滑的表面反射更多的
光。这一切如果全算进来的话,计算量是巨大的,所以,DX只是在近似地模仿。
Attributesofalight(光的属性):
想要不同的光可以指定不同的属性,不是所有类型的光都拥有下面的这些属性:
Position(位置)
这是在3D空间中光源的位置,例如(0,10,0)。
Direction(方向)
这是一个向量(例如(0,1,0)),表示从光源发射出来的光的方向。
Range(范围)
这是光从光源发出后所能到达的最大距离,范围以外的物体将不会接受到此光源的光。
Attenuation(衰减)
在光源到它的范围之间,光会逐渐衰减,这是很正常的。如果想要,你也可以指定让光不衰减,或反而越来越亮。
DiffuseLight(漫反射)
光的漫反射颜色。漫射光是分散的,但仍然具有方向,不像环境光没有方向。
AmbientLight(环境光)
这是环境光的颜色,它是通用的背景光,它是分散的且没有方向和光源,它充满整个场景。
SpecularLight(镜面反射)
镜面反射的的颜色。它是相对于漫射光来说的,它根本不会分散,你可以用它在对象上创建高亮。
Typesoflighting(光的类型):
在场景中共有四中类型的光可以被你创建,每种都有不同的行为与属性。
AmbientLight(环境光)
可以为场景指定通用的环境光,与其它灯光互不影响,用红、绿、蓝三个分量来设置颜色。
PointLight(点光源)
电光源的一个很好的例子就是灯泡,它没有方向(因为向全部方向发出),但是有颜色、范围和衰减。下面是图示
(7.1)。
Fig7.1
ndyPike
-40-
DirectionalLight(直射光)
直射光具有颜色,没有位置。举个例子应该是太阳,场景里的所有对象都会从同样的方向接收到同样的光。直射光也
没有范围和衰减。下面是图示(7.2)。
Fig7.2
Spotlight(聚光灯)
手电筒是聚光灯的好例子,它具有位置、方向、范围、发光内径和发光外径属性,光照强度还会随距离而衰减。下图
(7.3)中,Theta是内径,Phi是外径。
Fig7.3
所有的类型的光都会增加程序的负担,从小到大作负担排序:环境光、直射光、点光源、聚光灯。要记住在做程序时
需要考虑这些。
Materials(材质)
材质用于描述对象(三角形)的反光性能,使它看起来有没有光泽和带有什么颜色。下面是描述材质的一些设置:
ndyPike
-41-
DiffuseReflection(漫反射)
物体的漫反射颜色,能使物体看起来是什么颜色。
AmbientReflection(环境反射)
这也是一个颜色值,用于描述对象反射环境光的数量。你甚至可以指定它根本不反射环境光,这意味着它将不可见,
除非接收到其它类型的光。
SpecularReflectionandPower(镜面反射与高光强度)
镜面反射的值,可以用镜面反射和高光强度设置对象,使它看起来发亮。
Emission(放射)
使物体自发光,但不会影响到场景内的其它物体。
Normals(法线)
法线是垂直于三角形面的一个向量,它的方向是由三角形的顶点定义顺序所决定的。下图(7.4)演示了一个顺时针
定义的三角形的及它的法线。顶点的法线一般均分它周围的三角形法线。下图(7.5)演示了4个三角形的横截面和
它们的法线(亮红),同样也演示了3个顶点的法线(红)。本教程只把法线用于描述阴影(译者:还有其他用
途)。
Fig7.4Fig7.5
ImplementinglightsinDirectX(在DX中实现灯光)
在此章的代码中,我们设置了一个点光源,而且我们把环境光设置的很暗以突出它。我们还设置了能使立方体棱角
分明的法线,而且使材质没有高光。
Step1:ModifyFVFandCustomVertex(修改FVF和自定义定点)
第一件有做的就是修改FVF和自定义定点结构。在FVF中我们移除了D3DFVF_DIFFUSE而替换为
D3DFVF_NORMAL,然后在自定义定点结构中去掉了颜色属性而添加了法线属性。记住,最后还有纹理坐标属性。
//DefineaFVFforourcuboids
#defineCUBOID_D3DFVF_CUSTOMVERTEX(D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1)
//Defineacustomvertexforourcuboids
structCUBOID_CUSTOMVERTEX
{
FLOATx,y,z;//Positionofvertexin3Dspace
FLOATnx,ny,nz;//LightingNormal
FLOATtu,tv;//Texturecoordinates
};
Step2:Creatingthelights(创建灯光)
我们给CGame增加了一个新的模块InitialiseLights,我们把所有的灯光设置代码都放在这了。首先,我们为场景
设置了一个点光源。我们需要一个D3DLIGHT8类型的结构变量,然后为它设置正确的值。要指定它是一个点光源
(而不是别的),我们用D3DLIGHT_POINT常量。如果你想要直射光那就是D3DLIGHT_DIRECTIONAL,想要
聚光灯就是D3DLIGHT_SPOT。然后,我们要决定光源发出什么样的光,所以我们要指定Diffuse(漫射)、
ndyPike
-42-
Ambient(环境)和Specular(镜面)的RGB值,记住它们的值应该是在0和1之间的,0就是没有,1就是
满。然后还有它的位置值x,y和z,最后是范围和衰减。把衰减设置为值1意味着它不会衰减。
嗯,我们已经设置好灯光了,接下来要把它应用到场景中然后激活它。首先我们得给它分配一个灯光索引号,我们用
SetLight,它的第一个参数就是了,这是一个从0开始的索引列表,所以这第一个灯光我们设置为0。然后,激活
灯光(turniton),用LightEnable。第一个参数是灯光索引号,第二个参数是开关(TRUE开,FALSE关)。
然后我们有调用了SetRenderState以允许灯光,最后再次调用了SetRenderState为场景设置了环境光。
D3DLIGHT8d3dLight;
//Initializethelightstructure.
ZeroMemory(&d3dLight,sizeof(D3DLIGHT8));
//Setupawhitepointlightat(0,0,-10).
=D3DLIGHT_POINT;
e.r=1.0f;
e.g=1.0f;
e.b=1.0f;
t.r=0.0f;
t.g=0.0f;
t.b=0.0f;
ar.r=0.0f;
ar.g=0.0f;
ar.b=0.0f;
on.x=0.0f;
on.y=0.0f;
on.z=-10.0f;
ation0=1.0f;
ation1=0.0f;
ation2=0.0f;
=100.0f;
//Assignthepointlighttoourdeviceinpoisition(index)0
m_pD3DDevice->SetLight(0,&d3dLight);
//Enableourpointlightinposition(index)0
m_pD3DDevice->LightEnable(0,TRUE);
//Turnonlighting
m_pD3DDevice->SetRenderState(D3DRS_LIGHTING,TRUE);
//Setambientlightlevel
m_pD3DDevice->SetRenderState(D3DRS_AMBIENT,D3DCOLOR_XRGB(32,32,32));
Step3:SetupMaterial(设置材质)
我们又给CCuboid增加了一个新的模块SetMaterial,用于给立方体设置材质。我们还给CCuboid增添了一个新
的D3DMATERIAL8型变量m_matMaterial,然后为它设置属性值。我们要设置漫反射(Diffuse)、环境反射
(Ambient)和镜面反射(Specular)的RGBA值,然后是放射(Emissive)的RGBA值。记住要初始化这些值
(尤其是Emissive),否则将不能正确工作。
boolCCuboid::SetMaterial(D3DCOLORVALUErgbaDiffuse,D3DCOLORVALUErgbaAmbient,
D3DCOLORVALUErgbaSpecular,D3DCOLORVALUErgbaEmissive,floatrPower)
{
//SettheRGBAfordiffuselightreflectedfromthismaterial.
m_e=rgbaDiffuse;
ndyPike
-43-
//SettheRGBAforambientlightreflectedfromthismaterial.
m_t=rgbaAmbient;
//Setthecolorandsharpnessofspecularhighlightsforthematerial.
m_ar=rgbaSpecular;
m_=rPower;
//SettheRGBAforlightemittedfromthismaterial.
m_ve=rgbaEmissive;
returntrue;
}
在CCuboid中我们调用了SetMaterial,来给我们的立方体设置默认的材质。
//Setmaterialdefaultvalues(R,G,B,A)
D3DCOLORVALUErgbaDiffuse={1.0,1.0,1.0,0.0,};
D3DCOLORVALUErgbaAmbient={1.0,1.0,1.0,0.0,};
D3DCOLORVALUErgbaSpecular={0.0,0.0,0.0,0.0,};
D3DCOLORVALUErgbaEmissive={0.0,0.0,0.0,0.0,};
SetMaterial(rgbaDiffuse,rgbaAmbient,rgbaSpecular,rgbaEmissive,0);
最后,在CCuboid的Render模块中,我们调用SetMaterial来告知DX我们想要为后续的顶点应用我们的材质。
m_pD3DDevice->SetMaterial(&m_matMaterial);
Step4:GenerateNormals(生成法线)
我们改变了原来的3个三角带的立方体构造方式,现在我们用有12个三角形、36个顶点的一个三角带来构造立方
体。我们还像以往那样定义顶点,只不过这回去掉了颜色值增添了法线向量(初始化为0)。然后我们有一个循环,
它会调用GetTriangeNormal来计算每个三角形的法线向量,然后这个三角形的三个顶点的法线向量将会被设置为
与三角形相同。下面的代码片断是GetTriangeNormal模块和那个循环。
我们之所以把三角形的顶点的法线设置的与三角形相同,是因为我们想要立方体更棱角分明,而不是看起来有平滑的
圆角。如果我们需要某些对象有平滑的外形(例如球体),那么我们需要把顶点的法线设置为平分周围的三角形法
线。
D3DVECTORCCuboid::GetTriangeNormal(D3DXVECTOR3*vVertex1,D3DXVECTOR3*vVertex2,
D3DXVECTOR3*vVertex3)
{
D3DXVECTOR3vNormal;
D3DXVECTOR3v1;
D3DXVECTOR3v2;
D3DXVec3Subtract(&v1,vVertex2,vVertex1);
D3DXVec3Subtract(&v2,vVertex3,vVertex1);
D3DXVec3Cross(&vNormal,&v1,&v2);
D3DXVec3Normalize(&vNormal,&vNormal);
returnvNormal;
}
//Setallvertexnormals
inti;
ndyPike
-44-
for(i=0;i<36;i+=3)
{
vNormal=GetTriangeNormal(&D3DXVECTOR3(cvVertices[i].x,
cvVertices[i].y,
cvVertices[i].z),
&D3DXVECTOR3(cvVertices[i+1].x,
cvVertices[i+1].y,
cvVertices[i+1].z),
&D3DXVECTOR3(cvVertices[i+2].x,
cvVertices[i+2].y,
cvVertices[i+2].z));
cvVertices[i].nx=vNormal.x;
cvVertices[i].ny=vNormal.y;
cvVertices[i].nz=vNormal.z;
cvVertices[i+1].nx=vNormal.x;
cvVertices[i+1].ny=vNormal.y;
cvVertices[i+1].nz=vNormal.z;
cvVertices[i+2].nx=vNormal.x;
cvVertices[i+2].ny=vNormal.y;
cvVertices[i+2].nz=vNormal.z;
}
下图(7.6)显示了立方体的三个面上的(顶点的)法线,其它未显示的法线也是雷同的。如果我们平分顶点法线,
那末立方体看起来将是边缘平滑的,那不是我们想要的。当我们真的想要平滑的边缘时,我们才需要平分顶点法线,
以后的章节中我们会学习到这个。
Fig7.6
最后,在CCuboid的Render模块中,我们要用D3DPT_TRIANGLELIST替代原来的
D3DPT_TRIANGLESTRIP:
m_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST,0,12);
好了,这样我们就会得到像下图这样的4旋转的个立方体。它们分别都有不同的纹理,在它们的中间有一个点光
源,这样你会看到光效的变幻。
ndyPike
-45-
Summary(摘要)
这一章就到此结束了。在此章中,我们学习了灯光的各个类型和纹理对物体外观的影响。呵呵,我们的场景会更漂亮
了。下一章我们要学的是——索引缓冲。
Inthistutorialwe''velearntallaboutthedifferenttypesoflightsand
'
nexttutorialwe'lltakealookatIndexBuffers.
第八章:索引缓冲
Introduction(序)
这一章我们将学习一下关于索引缓冲的内容。首先,我们要改进一下CCuboid使它能支持索引缓冲。然后我们会创
建一个类CTerrain,一个很简单的地形发生器。当我们的地形生成后,我们还会赋予它草的纹理,使它看起来更真
实。
WhatisanIndexBuffer?(什么是索引缓冲)
索引缓冲就是内存中的一块用于索引顶点的区域。不明白?接着看。当渲染场景时,DX会对每个顶点都进行许多计
算,例如灯光、变幻等等,这样,运算量会很大。但是我们总是希望让DX做最少的运算以增加程序的效率,因此,
我们需要把顶点的数目减到最少。这样,索引缓冲就派上用场了。举个例子能更好地理解:假如我们想要绘制一个正
方形,那末它需要由两个三角形来构成,总共6个顶点(用三角列)。但实际上4顶点就应当能确定一个正方形了
(每角一个),用6个顶点是因为其中有2个顶点被重合(相同的值)了。因为我们有顶点重合了,所以用索引缓
冲是一个好主意。它是这样工作的:首先我们在顶点缓冲中只定义4个顶点(每角一个),然后在索引缓冲中定义
6个索引,每个索引都会映射顶点缓冲中的一个顶点。然后我们用索引缓冲中的索引来渲染三角形,那末实际上我们
只用了4个顶点。下图(8.1)演示了所举的这个例子:
ndyPike
-46-
Fig8.1
在上面的例子中,我们只用了4个顶点就定义了一个正方形,实际节约了2个顶点。注意索引缓冲中的顺序是顺时
针的,这是顶点被渲染的顺序。然后我们会像以前那样渲染场景,只不过这次除了顶点缓冲之外我们还用了索引缓
冲。那末如果我们把这种方法应用到我们的立方体上面,我们就会节约12个顶点。不要说那并不多,想象一个有
100个立方体的场景,我们在每一帧上都会节约1200个顶点呢!
ImplementinganIndexBufferinDirectX(在DX中实现索引缓冲)
我们需要给CCuboid添加一个索引缓冲,要实现这个,我们需要添加如下代码:
Step1:CreatingtheIndexBuffer(创建索引缓冲)
首先,我们需要两个新的成员变量:一个LPDIRECT3DINDEXBUFFER8型的m_pIndexBuffer,索引缓冲指
针;一个DWORD变量m_dwNumOfIndices,对象(顶点)索引数目,在此为36(6个面X每面2个三角形X
每个三角形3个顶点),构造函数会这样设置。
我们还有一个新的模块CreateIndexBuffer,在构造函数中,我们会在设置顶点值之前调用它。使用索引缓冲的方
法与使用顶点缓冲是类似的:首先我们创建索引缓冲并且得到它的指针(m_pIndexBuffer),然后我们临时地把
36个顶点索引都保存到一个数组当中,然后锁定索引缓冲,最后把数组复制给它并解锁。
LPDIRECT3DINDEXBUFFER8m_pIndexBuffer;
DWORDm_dwNumOfIndices;
boolCCuboid::CreateIndexBuffer()
{
VOID*pBufferIndices;
//Createtheindexbufferfromourdevice
if(FAILED(m_pD3DDevice->CreateIndexBuffer(m_dwNumOfIndices*sizeof(WORD),
0,D3DFMT_INDEX16,D3DPOOL_MANAGED,
&m_pIndexBuffer)))
{
returnfalse;
}
//Setvaluesfortheindexbuffer
WORDpIndices[]={0,1,2,3,2,1,//Top
4,5,6,7,6,5,//Face1
8,9,10,11,10,9,//Face2
12,13,14,15,14,13,//Face3
16,17,18,19,18,17,//Face4
ndyPike
-47-
20,21,22,23,22,21};//Bottom
//Getapointertotheindexbufferindicesandlocktheindexbuffer
m_pIndexBuffer->Lock(0,m_dwNumOfIndices*sizeof(WORD),
(BYTE**)&pBufferIndices,0);
//Copyourstoredindicesvaluesintotheindexbuffer
memcpy(pBufferIndices,pIndices,m_dwNumOfIndices*sizeof(WORD));
//Unlocktheindexbuffer
m_pIndexBuffer->Unlock();
returntrue;
}
Step2:ModifytheVertexBuffer(修改顶点缓冲)
我们需要修改一下UpdateVertices模块。首先也是最主要的就是这回我们只需要24个顶点,而不是36个。然后
我们会像上一章那样初始化法线向量为0。
此章的另一点不同就是我们将计算出三角形的共享顶点的均分法线(上一章我们只是把三角形的法线简单地相加,而
没有均分),实际上我们并不真的需要对这个立方体这样做,目的只是想充当一个例子。在UpdateVertices中我
们增添了两个新的指针pNumOfSharedPolygons和pSumVertexNormal,它们分别指向两个数组:一个
WORD型的用于描述所有顶点周围的三角形数目(也就是几个三角形共享那个顶点);另一个D3DVECTOR型的
用于保存所有顶点的均分法线向量。接下来我们将计算出每个顶点的均分法线向量。
在UpdateVertices中当我们初始化所有顶点之后,我们会有一个循环,索引缓冲中的所有的顶点索引都会通过
它。循环将计算出每个三角形的法线、累加被引用的顶点的计数(保存在pNumOfSharedPolygons中,被引用的
次数也就是周围的三角形数),还把得出的三角形法线向量加到它周围的三个顶点的法线向量上(由
pSumVertexNormal保存,这样整个循环结束后就能得出所有顶点的法线向量了,只不过还没有均分)。
然后,我们会根据每个顶点的引用计数(也就是几个三角形共享那个顶点)来计算出所有顶点的均分法线向量,接着
我们会将它规格化,确保向量的x、y和z都能在0和1之间。最后,我们还得把得出的每个顶点均分法线向量应用
到对应的顶点上,然后像以往一样复制到顶点缓冲中。
boolCCuboid::UpdateVertices()
{
DWORDi;
VOID*pVertices;
WORD*pBufferIndices;
D3DXVECTOR3vNormal;
DWORDdwVertex1;
DWORDdwVertex2;
DWORDdwVertex3;
//Arrayholdshowmanytimesthisvertexisshared
WORD*pNumOfSharedPolygons=newWORD[m_dwNumOfVertices];
//Arrayholdssumofallfacenormalsforsharedvertex
D3DVECTOR*pSumVertexNormal=newD3DVECTOR[m_dwNumOfVertices];
//Clearmemory
for(i=0;i
{
pNumOfSharedPolygons[i]=0;
pSumVertexNormal[i]=D3DXVECTOR3(0,0,0);
}
CUBOID_CUSTOMVERTEXcvVertices[]=
{
//TopFace
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
ndyPike
-48-
0.0f,0.0f,0.0f,0.0f,1.0f,},//Vertex0
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,0.0f,},//Vertex1
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,1.0f,},//Vertex2
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,0.0f,},//Vertex3
//Face1
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,1.0f,},//Vertex4
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,0.0f,},//Vertex5
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,1.0f,},//Vertex6
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,0.0f,},//Vertex7
//Face2
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,1.0f,},//Vertex8
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,0.0f,},//Vertex9
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,1.0f,},//Vertex10
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,0.0f,},//Vertex11
//Face3
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,1.0f,},//Vertex12
{m_rX+(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,0.0f,},//Vertex13
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,1.0f,},//Vertex14
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,0.0f,},//Vertex15
//Face4
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,1.0f,},//Vertex16
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,0.0f,},//Vertex17
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,1.0f,},//Vertex18
{m_rX-(m_rWidth/2),m_rY+(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,0.0f,},//Vertex19
//BottomFace
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,1.0f,},//Vertex20
{m_rX+(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,0.0f,0.0f,},//Vertex21
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ-(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,1.0f,},//Vertex22
{m_rX-(m_rWidth/2),m_rY-(m_rHeight/2),m_rZ+(m_rDepth/2),
0.0f,0.0f,0.0f,1.0f,0.0f,},//Vertex23
};
//Getapointertotheindexbufferindicesandlocktheindexbuffer
m_pIndexBuffer->Lock(0,m_dwNumOfIndices*sizeof(WORD),
(BYTE**)&pBufferIndices,D3DLOCK_READONLY);
ndyPike
-49-
//Foreachtriangle,countthenumberoftimeseachvertexisusedand
//addtogetherthenormalsoffacesthatshareavertex
for(i=0;i
{
dwVertex1=pBufferIndices[i];
dwVertex2=pBufferIndices[i+1];
dwVertex3=pBufferIndices[i+2];
vNormal=GetTriangeNormal(&D3DXVECTOR3(cvVertices[dwVertex1].x,
cvVertices[dwVertex1].y,
cvVertices[dwVertex1].z),
&D3DXVECTOR3(cvVertices[dwVertex2].x,
cvVertices[dwVertex2].y,
cvVertices[dwVertex2].z),
&D3DXVECTOR3(cvVertices[dwVertex3].x,
cvVertices[dwVertex3].y,
cvVertices[dwVertex3].z));
pNumOfSharedPolygons[dwVertex1]++;
pNumOfSharedPolygons[dwVertex2]++;
pNumOfSharedPolygons[dwVertex3]++;
pSumVertexNormal[dwVertex1].x+=vNormal.x;
pSumVertexNormal[dwVertex1].y+=vNormal.y;
pSumVertexNormal[dwVertex1].z+=vNormal.z;
pSumVertexNormal[dwVertex2].x+=vNormal.x;
pSumVertexNormal[dwVertex2].y+=vNormal.y;
pSumVertexNormal[dwVertex2].z+=vNormal.z;
pSumVertexNormal[dwVertex3].x+=vNormal.x;
pSumVertexNormal[dwVertex3].y+=vNormal.y;
pSumVertexNormal[dwVertex3].z+=vNormal.z;
}
//Unlocktheindexbuffer
m_pIndexBuffer->Unlock();
//Foreachvertex,calculateandsettheaveragenormal
for(i=0;i
{
vNormal.x=pSumVertexNormal[i].x/pNumOfSharedPolygons[i];
vNormal.y=pSumVertexNormal[i].y/pNumOfSharedPolygons[i];
vNormal.z=pSumVertexNormal[i].z/pNumOfSharedPolygons[i];
D3DXVec3Normalize(&vNormal,&vNormal);
cvVertices[i].nx=vNormal.x;
cvVertices[i].ny=vNormal.y;
cvVertices[i].nz=vNormal.z;
}
//Getapointertothevertexbufferverticesandlockthevertexbuffer
if(FAILED(m_pVertexBuffer->Lock(0,sizeof(cvVertices),(BYTE**)&pVertices,0)))
{
returnfalse;
}
//Copyourstoredverticesvaluesintothevertexbuffer
ndyPike
-50-
memcpy(pVertices,cvVertices,sizeof(cvVertices));
//Unlockthevertexbuffer
m_pVertexBuffer->Unlock();
//Cleanup
deletepNumOfSharedPolygons;
deletepSumVertexNormal;
pNumOfSharedPolygons=NULL;
pSumVertexNormal=NULL;
returntrue;
}
Step3:Render(渲染)
Render模块也相应地做了改动:首先,我们要通知DX我们要用索引缓冲来渲染三角形,所以我们得调用
SetIndices并且把索引缓冲的指针给它;其次,我们要用DrawIndexedPrimitive代替原来的DrawPrimitive以
支持用索引缓冲渲染三角形。
//Selectindexbuffer
m_pD3DDevice->SetIndices(m_pIndexBuffer,0);
//Renderpolygonsfromindexbuffer
m_pD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,m_dwNumOfVertices,0,
m_dwNumOfPolygons);
CreatingaTerrain(创建地形)
我们会使用与创建立方体类似的方法来创建我们的地形,它将由一个20X20方格阵来构成(每个方格由两个三角形
构成)。我们也会使用索引缓冲,它能使要渲染的顶点数目由2400减至441。这里就不放代码片断了,你可以直
接去研究附带的源代码。下图(8.2)显示了我们平坦的地形。你可以用"wireframe"模式来渲染场景,通过设置填
充状态:m_pD3DDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);可以设置的值共
有D3DFILL_POINT、D3DFILL_WIREFRAME和D3DFILL_SOLID。
Fig8.2
ndyPike
-51-
现在我们的地形看起来只是一些平整的格子,我们可以通过把它的各个顶点的y坐标值取随机数来使它变成一个简
单的地形。这一点我们已经做了,除了它的边缘之外。如果你想要更细致的地形,到
/看一下应该会有帮助。下图(8.3)演示了我们的立方体和简
易的随机地形。
Fig8.3
最后我们还要赋予它一个草的纹理,这样看起来好多了。我们会得到下图这样的屏幕:
ndyPike
-52-
Summary(摘要)
Inthistutorialwe'velookatindexbuffersandhowtheycanmakeamassivesavingonhowmany
exttutorialwewilllookathowtocreateothershapessuch
asspheres,cylindersandcones.
这一章我们见识了索引缓冲和它的优越性。下一章我们将学习怎样创建其他的几何体,例如球,圆柱和圆锥。
(能省就省,草草收笔之…)
第九章:有纹理的球体、圆柱体和锥体
Introduction(序)
这一章我们将学习怎样创建有纹理的球体、圆柱体和锥体,我们也会为它们设置法线,这样它们就能产生正确的阴影
了。而且,我们将会使所有的类都从一个基类CBase派生,这是一个能以HTML格式来生成记录文件的和有计算法
线向量功能的类。我们这一章的例子将会含有两个圆柱体、两个圆锥体和一个简单的绕太阳旋转的地月模型。
Howtomakeacylinder(怎样生成圆柱体)
我们的圆柱体将会含有两个三角扇形,一个做顶,一个做底;它的周身将由一个三角带来完成。下图(9.1)演示了
它的构成,顶点和三角形的分布;并且也演示了它的一个段上的几个顶点的法线是什么样子的。这里我们只用顶点缓
冲而不用索引缓冲,原因是我们想要它的顶、底与周身相拼接的地方的一些重合顶点拥有不同的法线,如果用索引缓
冲就不行了(因为它的实现是把几个重合的顶点看作一个)。
Fig9.1
要创建一个圆柱体我们需要指定它的分段数,在上面的图示中为8。当然,分段数越多,看起来就会越平滑(越接近
圆)。我们还需要指定它的高和半径,这样它的顶点、法线、纹理坐标就都出来了。下面的代码片断能告诉你这一切
是怎样完成的。
boolCCylinder::UpdateVertices()
{
CYLINDER_CUSTOMVERTEX*pVertex;
WORDwVertexIndex=0;
ndyPike
-53-
intnCurrentSegment;
//Lockthevertexbuffer
if(FAILED(m_pVertexBuffer->Lock(0,0,(BYTE**)&pVertex,0)))
{
LogError("
returnfalse;
}
floatrDeltaSegAngle=(2.0f*D3DX_PI/m_nSegments);
floatrSegmentLength=1.0f/(float)m_nSegments;
//Createthesidestrianglestrip
for(nCurrentSegment=0;nCurrentSegment<=m_nSegments;nCurrentSegment++)
{
floatx0=m_rRadius*sinf(nCurrentSegment*rDeltaSegAngle);
floatz0=m_rRadius*cosf(nCurrentSegment*rDeltaSegAngle);
pVertex->x=x0;
pVertex->y=0.0f+(m_rHeight/2.0f);
pVertex->z=z0;
pVertex->nx=x0;
pVertex->ny=0.0f;
pVertex->nz=z0;
pVertex->tu=1.0f-(rSegmentLength*(float)nCurrentSegment);
pVertex->tv=0.0f;
pVertex++;
pVertex->x=x0;
pVertex->y=0.0f-(m_rHeight/2.0f);
pVertex->z=z0;
pVertex->nx=x0;
pVertex->ny=0.0f;
pVertex->nz=z0;
pVertex->tu=1.0f-(rSegmentLength*(float)nCurrentSegment);
pVertex->tv=1.0f;
pVertex++;
}
//Createthetoptrianglefan:Center
pVertex->x=0.0f;
pVertex->y=0.0f+(m_rHeight/2.0f);
pVertex->z=0.0f;
pVertex->nx=0.0f;
pVertex->ny=1.0f;
pVertex->nz=0.0f;
pVertex->tu=0.5f;
pVertex->tv=0.5f;
pVertex++;
//Createthetoptrianglefan:Edges
for(nCurrentSegment=0;nCurrentSegment<=m_nSegments;nCurrentSegment++)
{
floatx0=m_rRadius*sinf(nCurrentSegment*rDeltaSegAngle);
floatz0=m_rRadius*cosf(nCurrentSegment*rDeltaSegAngle);
pVertex->x=x0;
pVertex->y=0.0f+(m_rHeight/2.0f);
pVertex->z=z0;
pVertex->nx=0.0f;
pVertex->ny=1.0f;
pVertex->nz=0.0f;
ndyPike
-54-
floattu0=(0.5f*sinf(nCurrentSegment*rDeltaSegAngle))+0.5f;
floattv0=(0.5f*cosf(nCurrentSegment*rDeltaSegAngle))+0.5f;
pVertex->tu=tu0;
pVertex->tv=tv0;
pVertex++;
}
//Createthebottomtrianglefan:Center
pVertex->x=0.0f;
pVertex->y=0.0f-(m_rHeight/2.0f);
pVertex->z=0.0f;
pVertex->nx=0.0f;
pVertex->ny=-1.0f;
pVertex->nz=0.0f;
pVertex->tu=0.5f;
pVertex->tv=0.5f;
pVertex++;
//Createthebottomtrianglefan:Edges
for(nCurrentSegment=m_nSegments;nCurrentSegment>=0;nCurrentSegment--)
{
floatx0=m_rRadius*sinf(nCurrentSegment*rDeltaSegAngle);
floatz0=m_rRadius*cosf(nCurrentSegment*rDeltaSegAngle);
pVertex->x=x0;
pVertex->y=0.0f-(m_rHeight/2.0f);
pVertex->z=z0;
pVertex->nx=0.0f;
pVertex->ny=-1.0f;
pVertex->nz=0.0f;
floattu0=(0.5f*sinf(nCurrentSegment*rDeltaSegAngle))+0.5f;
floattv0=(0.5f*cosf(nCurrentSegment*rDeltaSegAngle))+0.5f;
pVertex->tu=tu0;
pVertex->tv=tv0;
pVertex++;
}
if(FAILED(m_pVertexBuffer->Unlock()))
{
LogError("
returnfalse;
}
returntrue;
}
那末然后呢?锁定顶点缓冲之后我们需要计算每个段的角度,也就是用分段数来平分2XPI个弧度,它将会被保存在
rDeltaSegAngle中,在以后定义顶点位置时将会被用到。我们为它的周身设置纹理坐标时还得计算每段的长度,
也就是用分段数来平分纹理坐标的最大值1.0。
然后我们还得定义它的周身。我们有一个循环,它会顺序的处理每一个分段,计算分段的顶和底的顶点的x和z
值,它的y值很好计算,正的半个高度和负的半个高度就是了。我们用段的长度来计算当前段的纹理坐标。
然后,我们就能定义顶和底的三角扇形了。首先我们定义它们的中心点(圆心),然后用相同的计算方法得出边缘的
顶点。完成后,顶点缓冲就应该满了,我们解锁它准备渲染。
ndyPike
-55-
Howtomakeacone(怎样生成圆锥体)
我们的锥体将由一个三角扇形来构成底,一个三角列来构成周身。像圆柱体一样,我们需要指定它的段数、高和半
径。下图(9.2)演示了它的构成方法,图中的锥体为8个段,红线演示了其中一个段的法线。我们将同时使用索引
缓冲和顶点缓冲。同时,我们也设置了正确的法线,得到了正确的阴影。
Fig9.2
我们需要指定锥体的段数,上图中的段数为8,当然,段数越多效果越好。我们还需要指定它的高和半径,这样它的
顶点、法线、纹理坐标就都出来了。下面的代码片断能告诉你这一切是怎样完成的。
boolCCone::UpdateVertices()
{
CONE_CUSTOMVERTEX*pVertex;
WORD*pIndices;
WORDwVertexIndex=0;
intnCurrentSegment;
//Lockthevertexbuffer
if(FAILED(m_pVertexBuffer->Lock(0,0,(BYTE**)&pVertex,0)))
{
LogError("
returnfalse;
}
//Locktheindexbuffer
if(FAILED(m_pIndexBuffer->Lock(0,m_dwNumOfIndices,(BYTE**)&pIndices,0)))
{
LogError("
returnfalse;
}
floatrDeltaSegAngle=(2.0f*D3DX_PI/m_nSegments);
floatrSegmentLength=1.0f/(float)m_nSegments;
floatny0=(90.0f-(float)D3DXToDegree(atan(m_rHeight/m_rRadius)))/90.0f;
//Foreachsegment,addatriangletothesidestrianglelist
for(nCurrentSegment=0;nCurrentSegment
{
ndyPike
-56-
floatx0=m_rRadius*sinf(nCurrentSegment*rDeltaSegAngle);
floatz0=m_rRadius*cosf(nCurrentSegment*rDeltaSegAngle);
pVertex->x=0.0f;
pVertex->y=0.0f+(m_rHeight/2.0f);
pVertex->z=0.0f;
pVertex->nx=x0;
pVertex->ny=ny0;
pVertex->nz=z0;
pVertex->tu=1.0f-(rSegmentLength*(float)nCurrentSegment);
pVertex->tv=0.0f;
pVertex++;
pVertex->x=x0;
pVertex->y=0.0f-(m_rHeight/2.0f);
pVertex->z=z0;
pVertex->nx=x0;
pVertex->ny=ny0;
pVertex->nz=z0;
pVertex->tu=1.0f-(rSegmentLength*(float)nCurrentSegment);
pVertex->tv=1.0f;
pVertex++;
//Setthreeindices(1triangle)persegment
*pIndices=wVertexIndex;
pIndices++;
wVertexIndex++;
*pIndices=wVertexIndex;
pIndices++;
wVertexIndex+=2;
if(nCurrentSegment==m_nSegments-1)
{
*pIndices=1;
pIndices++;
wVertexIndex--;
}
else
{
*pIndices=wVertexIndex;
pIndices++;
wVertexIndex--;
}
}
//Createthebottomtrianglefan:Centervertex
pVertex->x=0.0f;
pVertex->y=0.0f-(m_rHeight/2.0f);
pVertex->z=0.0f;
pVertex->nx=0.0f;
pVertex->ny=-1.0f;
pVertex->nz=0.0f;
pVertex->tu=0.5f;
pVertex->tv=0.5f;
pVertex++;
//Createthebottomtrianglefan:Edgevertices
for(nCurrentSegment=m_nSegments;nCurrentSegment>=0;nCurrentSegment--)
ndyPike
-57-
{
floatx0=m_rRadius*sinf(nCurrentSegment*rDeltaSegAngle);
floatz0=m_rRadius*cosf(nCurrentSegment*rDeltaSegAngle);
pVertex->x=x0;
pVertex->y=0.0f-(m_rHeight/2.0f);
pVertex->z=z0;
pVertex->nx=0.0f;
pVertex->ny=-1.0f;
pVertex->nz=0.0f;
floattu0=(0.5f*sinf(nCurrentSegment*rDeltaSegAngle))+0.5f;
floattv0=(0.5f*cosf(nCurrentSegment*rDeltaSegAngle))+0.5f;
pVertex->tu=tu0;
pVertex->tv=tv0;
pVertex++;
}
if(FAILED(m_pVertexBuffer->Unlock()))
{
LogError("
returnfalse;
}
if(FAILED(m_pIndexBuffer->Unlock()))
{
LogError("
returnfalse;
}
returntrue;
}
生成圆锥体的方法与生成圆柱体的方法是相似的,具体的细节你可以直接去读源代码,这里就不再罗嗦了。
Howtomakeasphere(怎样生成球体)
我们的球体是由一个三角列构成的。我们需要指定它的环数和段数,当然,环数与段数越多,它看起来就越光滑。我
们将会使用索引缓冲和顶点缓冲。下图(9.3)演示了它的构成方法。
ndyPike
-58-
Fig9.3
要创建球体我们可以使用下面的代码片断,它出自的DX论坛,是“Laurent”贴的一个例子的改
编。在此我要感谢他,允许我在本DirectX教程中使用他的这段代码。如果你想看那个帖子的全部内容,请到
/community/forums/?topic_id=85779。
boolCSphere::UpdateVertices()
{
//Codeadaptedfromasampleby"Laurent"ectXforum
///community/forums/?topic_id=85779
WORD*pIndices;
SPHERE_CUSTOMVERTEX*pVertex;
WORDwVertexIndex=0;
intnCurrentRing;
intnCurrentSegment;
D3DXVECTOR3vNormal;
//Lockthevertexbuffer
if(FAILED(m_pVertexBuffer->Lock(0,0,(BYTE**)&pVertex,0)))
{
LogError("
returnfalse;
}
//Locktheindexbuffer
if(FAILED(m_pIndexBuffer->Lock(0,m_dwNumOfIndices,(BYTE**)&pIndices,0)))
{
LogError("
returnfalse;
}
//Establishconstantsusedinspheregeneration
FLOATrDeltaRingAngle=(D3DX_PI/m_nRings);
FLOATrDeltaSegAngle=(2.0f*D3DX_PI/m_nSegments);
//Generatethegroupofringsforthesphere
for(nCurrentRing=0;nCurrentRing
{
FLOATr0=sinf(nCurrentRing*rDeltaRingAngle);
FLOATy0=cosf(nCurrentRing*rDeltaRingAngle);
ndyPike
-59-
//Generatethegroupofsegmentsforthecurrentring
for(nCurrentSegment=0;nCurrentSegment
nCurrentSegment++)
{
FLOATx0=r0*sinf(nCurrentSegment*rDeltaSegAngle);
FLOATz0=r0*cosf(nCurrentSegment*rDeltaSegAngle);
vNormal.x=x0;
vNormal.y=y0;
vNormal.z=z0;
D3DXVec3Normalize(&vNormal,&vNormal);
//Addonevertextothestripwhichmakesupthesphere
pVertex->x=x0;
pVertex->y=y0;
pVertex->z=z0;
pVertex->nx=vNormal.x;
pVertex->ny=vNormal.y;
pVertex->nz=vNormal.z;
pVertex->tu=1.0f-((FLOAT)nCurrentSegment/(FLOAT)m_nSegments);
pVertex->tv=(FLOAT)nCurrentRing/(FLOAT)m_nRings;
pVertex++;
//Addtwoindicesexceptforthelastring
if(nCurrentRing!=m_nRings)
{
*pIndices=wVertexIndex;
pIndices++;
*pIndices=wVertexIndex+(WORD)(m_nSegments+1);
pIndices++;
wVertexIndex++;
}
}
}
if(FAILED(m_pIndexBuffer->Unlock()))
{
LogError("
returnfalse;
}
if(FAILED(m_pVertexBuffer->Unlock()))
{
LogError("
returnfalse;
}
returntrue;
}
嗯,首先我们得锁定索引缓冲和顶点缓冲。然后我们用和前面一样的方法计算出了段的角度,用类似的方法计算出了
环的角度。计算环的角度时我们只需要平分一个半圆(PI弧度)就行了。
当我们确定了环角和段角之后,我们会有一个循环,它能一环一环的生成球上的所有的顶点,索引缓冲会把它们连结
成三角形列。我们还为每个顶点都设置了法线。球的半径是按照1来计算的,然后我们再通过缩放比例矩阵把它调
整为想要的半径。最后,解锁顶点和索引缓冲,准备渲染。
译者:注意,程序中球体的“环角”与地球的纬度的划分方法并不相同,不要混淆。
ndyPike
-60-
最后我们会得到类似这样的屏幕:
HTMLLogging(HTML格式的程序日志)
这回我们程序的LOG文件已改用HTML格式的记录方式了,运行一下程序就知道了。程序还增加了一些新的函数,
自己去看源代码吧,这里就不多说了。
Summary(摘要)
Nowwecancreatequiteafewshapes:acube,asphere,acone,acylinderandevenasimple
hapesareprettybasic,butcanbeveryusefulwhenyoustartcreatingyourown
exttutorial,we'lllookatcreatingmorecomplexobjectslikealiens,gunsand
vehicles.
现在我们可以创建一些像立方体、球体这样的基本对象了,它们在构建场景的时候是很有用的。下一章我们会学习模
型的载入方法。
第十章:载入模型
Introduction(序)
这一章我们将会看到怎样创建模型和怎样在DX中应用模型。至今为止,我们已经可以创建一些简单的对象了,像立
方体、球体、圆柱体等等。但是一个游戏需要的可远远不只这些,它需要比如枪、人物、建筑物或是宇宙飞船等等这
样的模型。你可能会在一张纸上创建出这些并且计算出了所有顶点的准确位置,或者你会用一种类似“3D
modeller”的软件,但是我的建议是使用立体建模软件(如3DSMAX、Maya),它更方便。
3DModellingSoftware(立体建模软件)
你需要用立体建模软件来创建你的游戏中的对象,下面是一个建模软件列表(按价格排序),应该会有你喜欢的。立
体建模软件其实有很多种,但是根据我的调查,这些是更受欢迎的。
ndyPike
-61-
想要配合DX,软件得能导出模型为.x文件。这得需要一个有此功能的插件或是转化程序。
PackageWebsitePrice*
$3495
$1999
$1695
$595
$34.95
$20
e
*列表中的价格为发布价格,想要了解准确的当前的价格请到它的主页上。你可以到来把价格
换算成你的本国货币单位。
你会选择哪一个呢?那是你的事了。我选择了3DCanvas(trialversion)、MilkShape3D(trialversion)和
OpenFX(fullversion),因为他们很便宜,对于业余爱好者很适用。
译者:下面的几段原著简要地介绍了他选择的三个小软件,接着又稍微重点地介绍了一下MilkShape3D。我认为
我们能使用这些软件的机率很小,所以偷懒不翻译了。
reusingWindowsXP
orWindows2000(likeme)itcanrebootyourmachinewithoutwarning!Thehelpadvisedmethat
updatingmydisplaydrivertothelatestversionwouldfixtheproblem...butIhavethelatestversion
already!So,I'mafraid3DCanvasisnogoodforme.
ostofthefeatures
thatyouneedtocreatea3Dmodel,reloadsoftutorialsonthe
webandaforumontheMilkShapesitethatisreallyuseful.
n
ywayIfoundwastoexporttoa.3ds
salsoverylimited
,OpenFXisn'ttherighttoolforme.
So,IhavedecidedtouseMilkShape3Dbecauseitischeap,(viaafree
plug-in)anditiseasytouse.
ithMilkShape3D(用MilkShape3D创建.X文件)
Onceyouhaveyour3Dmodellingpackage,stutorial
10.1below,showsascreenshotofMilkShape3Dwithmycompletedspaceshipmodel.
ndyPike
-62-
Fig10.1
Onceyourmodeliscomplete,iswithMilkShape,download
andinstallthe"DirectX8.1Exporter"
openyourmodelinMilkShapeandgoto"File">"Export">"DirectX(JT)...".Selectalocationfor
,thenselecttheoptionsyourequire(normallythedefaults)andpress"OK".Youarenow
readytoloadyourmodelintoyourDirectXprogram.
译者:其实商业游戏软件是不用.x这种文件格式的,不信的话你可以随便找来一款,到它的目录下看一看。一般来
说,商业游戏无论用什么软件来建模,最终都会把模型文件转换成自己的格式,而且一般都会将文件打包,以节省空
间。因为现在是学习阶段,所以我们暂时就用.x这种格式。
ntoyourprogram(在程序中载入.x文件)
这一章我们会增加一个新的类CMesh,它的功能是渲染从.x文件中读取的网格(Mesh)。下面有CMesh类的源
代码,它的构造函数将展示出怎样从.x文件中读取网格(Mesh)并且将它们保存至内存,它的析构函数将展示出怎
样释放网格所占有的内存。它的渲染模块会告诉你怎样渲染网格(Mesh)。
CMesh类的构造函数有两个输入参数:第一个是DX设备,我们早就接触过了;第二个是要读取的.x文件的文件
名,这个很好理解。我们用D3DXLoadMeshFromX函数来完成把网格载入内存的工作。然后,我们会创建两个数
组,分别用于保存模型的材质和纹理。然后我们会有一个循环,其实它的功能就是从文件(由.x文件给定)中读取
纹理并保存到数组中。最后,我们克隆了网格并且调用了D3DXComputeNormals函数来设置法线。
(译者:其实原著这里说的也不是很详细,不明白就研究源代码吧)
在析构函数中我们释放了网格和纹理占用的内存,还有前面创建的两个数组,都通通释放了。
渲染模块是很简单的,我们会循环的处理网格的每个子集,并且用适当的纹理和材质渲染它们。
CMesh::CMesh(LPDIRECT3DDEVICE8pD3DDevice,LPSTRpFilename)
{
LPD3DXBUFFERpMaterialsBuffer=NULL;
LPD3DXMESHpMesh=NULL;
m_pD3DDevice=pD3DDevice;
if(FAILED(D3DXLoadMeshFromX(pFilename,D3DXMESH_SYSTEMMEM,m_pD3DDevice,NULL,
ndyPike
-63-
&pMaterialsBuffer,&m_dwNumMaterials,&pMesh)))
{
m_pMesh=NULL;
m_pMeshMaterials=NULL;
m_pMeshTextures=NULL;
LogError("
return;
}
D3DXMATERIAL*matMaterials=(D3DXMATERIAL*)pMaterialsBuffer->GetBufferPointer();
//oldthematerialsandonlytoholdthetextures
m_pMeshMaterials=newD3DMATERIAL8[m_dwNumMaterials];
m_pMeshTextures=newLPDIRECT3DTEXTURE8[m_dwNumMaterials];
for(DWORDi=0;i
{
//Copythematerial
m_pMeshMaterials[i]=matMaterials[i].MatD3D;
//Settheambientcolorforthematerial(D3DXdoesnotdothis)
m_pMeshMaterials[i].Ambient=m_pMeshMaterials[i].Diffuse;
//Createthetexture
if(FAILED(D3DXCreateTextureFromFile(m_pD3DDevice,
matMaterials[i].pTextureFilename,
&m_pMeshTextures[i])))
{
m_pMeshTextures[i]=NULL;
}
}
//We'vefinishedwiththematerialbuffer,soreleaseit
SafeRelease(pMaterialsBuffer);
//Makesurethatthenormalsaresetupforourmesh
pMesh->CloneMeshFVF(D3DXMESH_MANAGED,MESH_D3DFVF_CUSTOMVERTEX,
m_pD3DDevice,&m_pMesh);
SafeRelease(pMesh);
D3DXComputeNormals(m_pMesh);
LogInfo("
}
CMesh::~CMesh()
{
SafeDelete(m_pMeshMaterials);
if(m_pMeshTextures!=NULL)
{
for(DWORDi=0;i
{
if(m_pMeshTextures[i])
{
SafeRelease(m_pMeshTextures[i]);
}
}
}
ndyPike
-64-
SafeDelete(m_pMeshTextures);
SafeRelease(m_pMesh);
LogInfo("
}
DWORDCMesh::Render()
{
if(m_pMesh!=NULL)
{
for(DWORDi=0;i
{
m_pD3DDevice->SetMaterial(&m_pMeshMaterials[i]);
m_pD3DDevice->SetTexture(0,m_pMeshTextures[i]);
m_pMesh->DrawSubset(i);
}
returnm_pMesh->GetNumFaces();
}
else
{
return0;
}
}
Scalingmakesyourobjectdarker?(缩放使你的对象变暗了?)
还有一点需要注意的是,如果你缩放了网格,同时你也缩放了法线。这可能会使你的对象看起来发暗,解决的办法是
激活D3DRS_NORMALIZENORMALS渲染状态:
m_pD3DDevice->SetRenderState(D3DRS_NORMALIZENORMALS,TRUE);
这一章我们的例子是创建三个太空船(译者:即使看起来不太像),它们会做不同的旋转。运行例子你会得到像下面
这样的屏幕:
ndyPike
-65-
Summary(摘要)
Sonowwecancreateanyobjectwelike,er,
whenyoucreateyourmodels,epolygons
exttutorialwewilllookatadding2Delements
usefulwhenitcomestocreatingscoresandenergybars.
现在我们可以创建自己喜欢的对象了,再也不会局限于立方体和球体了。但要记住:创建模型的时候,在允许的情况
下,我们应该使用尽可能少的三角形,以减少程序负担、提高运行速度。下一章我们会接触一下3D场景中的2D元
素。
译者废话:如果遇到不了解的名词、术语或者问题,应该到网上广泛地搜索资料、参加论述以提高之。
第十一章:3D中的2D
Introduction(序)
这一章,我们会向3D场景中增加一些2D的元素。这是很有用的,例如我们可以用它来创建类似生命格、能量格、
雷达等等这样的东西。我们还会学习到怎样使它们透明(以实现原来的Colorkey效果),这样能使它们更好地融入
场景中。(译者:请运行例子并观察左下角的2D元素,明明是一张128X128的方形BMP,为什么会没有留下方
边呢?)
AddingText(添加文字)
首先也是最简单的就是往场景里添加一些2D文字(请看例子的左上角)。为此,我们创建了一个简易的类
CFont,下面是它的源代码:
CFont::CFont(LPDIRECT3DDEVICE8pD3DDevice,LPSTRpFontFace,intnHeight,boolfBold,
boolfItalic,boolfUnderlined)
{
HFONThFont;
m_pD3DDevice=pD3DDevice;
intnWeight=FW_NORMAL;
DWORDdwItalic=0;
DWORDdwUnderlined=0;
if(fBold)
{
nWeight=FW_BOLD;
}
if(fItalic)
{
dwItalic=1;
}
if(fUnderlined)
{
dwUnderlined=1;
}
hFont=CreateFont(nHeight,0,0,0,nWeight,dwItalic,dwUnderlined,0,
ANSI_CHARSET,0,0,0,0,pFontFace);
D3DXCreateFont(m_pD3DDevice,hFont,&m_pFont);
LogInfo("
}
ndyPike
-66-
CFont::~CFont()
{
SafeRelease(m_pFont);
LogInfo("
}
voidCFont::DrawText(LPSTRpText,intx,inty,D3DCOLORrgbFontColour)
{
RECTRect;
=x;
=y;
=0;
=0;
m_pFont->Begin();
m_pFont->DrawTextA(pText,-1,&Rect,DT_CALCRECT,0);//Calculatethesizeofthe
//rectneeded
m_pFont->DrawTextA(pText,-1,&Rect,DT_LEFT,rgbFontColour);//Drawthetext
m_pFont->End();
}
在CFont类的构造函数中,我们调用CreateFont函数来创建字体,它会返回一个句柄(HFONT)。如果想在DX
中使用这个字体,我们还需要创建字体对象,这得通过调用D3DXCreateFont函数来完成。我们把刚才得到的字体
句柄传递给D3DXCreateFont函数,会得到一个新的LPD3DXFONT型指针,我们会把它保存为CFont类的成员
变量以待用。
在析构函数中我们只需要简单地释放所保存的字体对象。
最后,由DrawText来渲染文字。我们得输入要渲染的文字和它们的坐标(屏幕坐标系统)还有它们的颜色。我们
调用了两次DrawTextA模块,第一次用于计算文字所需要的矩形范围,第二次用于将文字绘制到矩形内,它们在矩
形中是左对齐格式的。
要使用CFont类,我们需要用下面的代码来创建CFont的指针:
m_pFont=newCFont(m_pD3DDevice,"Verdana",12,false,false,false);
有了CFont对象的指针,我们就可以绘制文字了。所以,我们的CGame又多了一个成员RenderText,它会在主
渲染模块中被调用。
voidCGame::RenderText()
{
//Drawsometextatthetopofthescreenshowingstats
charbuffer[255];
DWORDdwDuration=(timeGetTime()-m_dwStartTime)/1000;
if(dwDuration>0)
{
sprintf(buffer,"Duration:%:%:%d.",
dwDuration,m_dwFrames,(m_dwFrames/dwDuration));
}
else
{
sprintf(buffer,"Calculating...");
}
m_pFont->DrawText(buffer,0,0,D3DCOLOR_XRGB(255,255,255));
}
ndyPike
-67-
所以,在绘制每一帧的时候RenderText都会被调用。它会显示出程序运行的时间(单位:秒)、帧数和FPS。
关于显示文字或汉字的更多内容和技巧,请到炎龙工作室(/)查看
《游戏中汉字显示的实现与技巧》一文。
Stepsfor2Din3DRendering(在3D中渲染2D的步骤)
创建2D图形是很简单的事,我们要为每帧做的所有事情只有下面这些步骤:
o像以往一样设置3D摄像机
o激活深度缓冲与灯光
o像以往一样渲染3D对象
o为2D设置摄像机
o禁止深度缓冲与灯光
o渲染2D对象
2DCamera(2D摄像机)
要设置摄像机以渲染2D对象,我们需要改变投影矩阵,由以往的透视投影变成正交投影。当使用透视投影时,离摄
像机越远的物体看起来会越小(这是我们看真实世界时发生的现象);当使用正交投影时,无论物体离摄像机多远它
的大小都不会变。例如,我们在使用3D建模软件(例如3DSMAX)时通常都使用3个正交投影(顶、左、前)和
一个透视投影(3D)。设置2D摄像机的代码如下:
voidCGame::Setup2DCamera()
{
D3DXMATRIXmatOrtho;
D3DXMATRIXmatIdentity;
//Setuptheorthogonalprojectionmatrixandthedefaultworld/viewmatrix
D3DXMatrixOrthoLH(&matOrtho,(float)m_nScreenWidth,(float)m_nScreenHeight,
0.0f,1.0f);
D3DXMatrixIdentity(&matIdentity);
m_pD3DDevice->SetTransform(D3DTS_PROJECTION,&matOrtho);
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matIdentity);
m_pD3DDevice->SetTransform(D3DTS_VIEW,&matIdentity);
//Makesurethatthez-bufferandlightingaredisabled
m_pD3DDevice->SetRenderState(D3DRS_ZENABLE,D3DZB_FALSE);
m_pD3DDevice->SetRenderState(D3DRS_LIGHTING,FALSE);
}
同时,我们也把世界矩阵和视图矩阵设置成了恒等的矩阵,还禁止了深度缓冲和灯光。深度缓冲被禁止后,所有后来
被渲染的对象都会出现在已经被渲染出来的对象(3D对象)的上面。
2DObjects(2D对象)
2D对象实际上是一个带有纹理的面板(矩形,包含两个三角形的三角带),它的每一个顶点的z值都是相同的所以
它看起来是平的。我们创建了一个类CPanel,我们会像使用2D对象一样使用它。
CPlane与以往我们创建的对象是很相似的,它包含一个含有4个顶点(面板的每角一个)的顶点缓冲,而且它的中
心也在原点上。下面是它的代码:
CPanel::CPanel(LPDIRECT3DDEVICE8pD3DDevice,intnWidth,intnHeight,int
nScreenWidth,intnScreenHeight,DWORDdwColour)
{
m_pD3DDevice=pD3DDevice;
m_pVertexBuffer=NULL;
m_pTexture=NULL;
m_nWidth=nWidth;
m_nHeight=nHeight;
m_nScreenWidth=nScreenWidth;
ndyPike
-68-
m_nScreenHeight=nScreenHeight;
m_dwColour=dwColour;
//InitializeVertexBuffer
if(CreateVertexBuffer())
{
if(UpdateVertices())
{
LogInfo("
return;
}
}
LogError("
}
CPanel::~CPanel()
{
SafeRelease(m_pTexture);
SafeRelease(m_pVertexBuffer);
LogInfo("
}
boolCPanel::CreateVertexBuffer()
{
//Createthevertexbufferfromourdevice.
if(FAILED(m_pD3DDevice->CreateVertexBuffer(4*sizeof(PANEL_CUSTOMVERTEX),
0,PANEL_D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT,&m_pVertexBuffer)))
{
LogError("
returnfalse;
}
returntrue;
}
boolCPanel::UpdateVertices()
{
PANEL_CUSTOMVERTEX*pVertices=NULL;
m_pVertexBuffer->Lock(0,4*sizeof(PANEL_CUSTOMVERTEX),(BYTE**)&pVertices,0);
if(m_dwColour==-1)
{
//Nocolourwasset,sodefaulttowhite
m_dwColour=D3DCOLOR_XRGB(255,255,255);
}
//Setalltheverticestoselectedcolour
pVertices[0].colour=m_dwColour;
pVertices[1].colour=m_dwColour;
pVertices[2].colour=m_dwColour;
pVertices[3].colour=m_dwColour;
//Setthepositionsofthevertices
pVertices[0].x=-(m_nWidth)/2.0f;
pVertices[0].y=-(m_nHeight)/2.0f;
pVertices[1].x=-(m_nWidth)/2.0f;
pVertices[1].y=m_nHeight/2.0f;
ndyPike
-69-
pVertices[2].x=(m_nWidth)/2.0f;
pVertices[2].y=-(m_nHeight)/2.0f;
pVertices[3].x=(m_nWidth)/2.0f;
pVertices[3].y=m_nHeight/2.0f;
pVertices[0].z=1.0f;
pVertices[1].z=1.0f;
pVertices[2].z=1.0f;
pVertices[3].z=1.0f;
//Setthetexturecoordinatesofthevertices
pVertices[0].u=0.0f;
pVertices[0].v=1.0f;
pVertices[1].u=0.0f;
pVertices[1].v=0.0f;
pVertices[2].u=1.0f;
pVertices[2].v=1.0f;
pVertices[3].u=1.0f;
pVertices[3].v=0.0f;
m_pVertexBuffer->Unlock();
returntrue;
}
boolCPanel::SetTexture(constchar*szTextureFilePath,DWORDdwKeyColour)
{
if(FAILED(D3DXCreateTextureFromFileEx(m_pD3DDevice,szTextureFilePath,0,0,0,
0,D3DFMT_UNKNOWN,D3DPOOL_MANAGED,
D3DX_DEFAULT,D3DX_DEFAULT,dwKeyColour,
NULL,NULL,&m_pTexture)))
{
returnfalse;
}
returntrue;
}
DWORDCPanel::Render()
{
m_pD3DDevice->SetStreamSource(0,m_pVertexBuffer,sizeof(PANEL_CUSTOMVERTEX));
m_pD3DDevice->SetVertexShader(PANEL_D3DFVF_CUSTOMVERTEX);
if(m_pTexture!=NULL)
{
m_pD3DDevice->SetTexture(0,m_pTexture);
m_pD3DDevice->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE);
}
else
{
m_pD3DDevice->SetTexture(0,NULL);
}
m_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,2);
return2;//Returnthenumberofpolygonsrendered
}
ndyPike
-70-
voidCPanel::MoveTo(intx,inty)
{
//xandyspecifythetopleftcornerofthepanelinscreencoordinates
D3DXMATRIXmatMove;
x-=(m_nScreenWidth/2)-(m_nWidth/2);
y-=(m_nScreenHeight/2)-(m_nHeight/2);
D3DXMatrixTranslation(&matMove,(float)x,-(float)y,0.0f);
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matMove);
}
有一点略微的不同是SetTexture和Render模块激活了纹理透明(稍候讨论)。
这儿还有一个新的函数MoveTo,它会使2D对象显示在我们所指定的位置上。它需要两个输入参数,屏幕坐标x
与y,然后通过正常的矩阵变换来移动2D对象(从屏幕中心到我们想要的位置)。当然,我们同样可以通过正常的
矩阵变换来旋转它。
TextureTransparency(纹理透明)
首先我们得激活Alpha混合,然后才能使用透明纹理。我们需要调用SetRenderState函数来完成这个工作,代码
如下:
//Enablealphablendingsowecanusetransparenttextures
m_pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
//Sethowthetextureshouldbeblended(usealpha)
m_pD3DDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
m_pD3DDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
还有两个新的变化:第一个变化是纹理的载入。我们需要用D3DXCreateTextureFromFileEx函数,它使我们可
以指定颜色键(ColorKey),这意味着纹理中所有与颜色键颜色相同的点都会变成透明的。下面的图11.1、11.2
和11.3就是在本章例子中我们将会使用的纹理。我们已经指定黑色为透明了。
D3DXCreateTextureFromFileEx(m_pD3DDevice,szTextureFilePath,0,0,0,0,
D3DFMT_UNKNOWN,D3DPOOL_MANAGED,D3DX_DEFAULT,
D3DX_DEFAULT,dwKeyColour,NULL,NULL,&m_pTexture);
Fig11.1Fig11.2Fig11.3
第二个变化是纹理的渲染。CPanel的渲染模块中的SetTextureStageState已经被改变了,这样它就能支持透明
了:
m_pD3DDevice->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE);
我们改变了上一章的例子,增加了一些带有透明的2D对象,渲染出来的屏幕会是这样的:
ndyPike
-71-
第十二章:键盘与鼠标输入
Introduction(序)
这一章我们将会创建一个简单的、可以被用户控制的地球模型。用户将可以通过鼠标来控制它的位置,通过键盘控制
它的尺寸和旋转。
DirectInput
我们一直在用Direct3D来绘制3D对象,现在,我们想给程序增加一些与用户交互的功能,这得靠DirectInput。
DirectInput允许我们从任何输入设备读取数据,鼠标、键盘或是游戏操纵杆,我们能用这些数据来改变场景中的元
素。在此教程中,我们只会接触到键盘与鼠标,我们会在将来的教程中接触游戏操纵杆。
在我们的程序中,要增加与用户交互的功能我们需要完成以下步骤:
o初始化DirectInput
o设置键盘
o设置鼠标
o在每一帧,得到键盘与鼠标的当前状态并借以修改场景
o程序结束时清理DirectInput
IncludeandLibraryfiles(头文件与库文件)
首先我们必须要做的,就是在项目中增加一些头文件与库文件,这很简单,不是吗?
odinput.h
ndyPike
-72-
TheControls(控制方案)
此章例子的控制方案如下:
o上键:地球放大
o下键:地球缩小
o左键:增加地球的向左旋转
o右键:增加地球的向右旋转
o鼠标移动:改变地球的位置
o鼠标左键:停止地球旋转
o鼠标右键:默认的旋转方式
InitialisingDirectInput(初始化DirectInput)
首先我们得初始化DirectInput,为此我们增加了一个新的函数InitialiseDirectInput,它会在CGame::
Initialise中被调用。下面这一小段代码用于创建DirectInput对象以待用:
//CreatetheDirectInputobject
if(FAILED(DirectInput8Create(hInst,DIRECTINPUT_VERSION,
IID_IDirectInput8,(void**)&m_pDirectInput,NULL)))
{
LogError("
returnfalse;
}
else
{
LogInfo("
}
第一个参数是我们从WinMain函数获得的应用程序实例句柄;第二个参数是DirectInput的版本,用
DIRECTINPUT_VERSION代表当前的版本(输入其它版本导致Dinput模拟那个版本);第三个是想要的接口,
我们这里就用IID_IDirectInput8;第四个是最重要的,它会返回DInput对象的接口指针已待用;第五个我们不
用理它,输入NULL就对了。
m_pDirectInput是CGame的一个LPDIRECTINPUT8型的成员变量,当然是DInput对象的接口指针了。
Settingupthekeyboard(设置键盘)
创建好DirectInput对象之后我们就可以设置键盘了。下面是设置键盘的代码,我会在后面逐渐地解释它们。
//KEYBOARD=======================================================================
//Createthekeyboarddeviceobject
if(FAILED(m_pDirectInput->CreateDevice(GUID_SysKeyboard,&m_pKeyboard,NULL)))
{
CleanUpDirectInput();
LogError("
returnfalse;
}
else
{
LogInfo("
}
//Setthedataformatforthekeyboard
if(FAILED(m_pKeyboard->SetDataFormat(&c_dfDIKeyboard)))
{
CleanUpDirectInput();
LogError("
returnfalse;
}
else
ndyPike
-73-
{
LogInfo("
}
//Setthecooperativelevelforthekeyboard
if(FAILED(m_pKeyboard->SetCooperativeLevel(hWnd,
DISCL_FOREGROUND|DISCL_NONEXCLUSIVE)))
{
CleanUpDirectInput();
LogError("
returnfalse;
}
else
{
LogInfo("
}
//Acquirethekeyboard
if(m_pKeyboard)
{
m_pKeyboard->Acquire();
}
首先,我们需要调用CreateDevice函数来创建一个键盘设备。第一个参数是一个GUID(全局唯一标识),这里
我们输入GUID_SysKeyboard这个预定义的标识即可;第二个参数用于返回设备的指针,我们把它定义为
CGame的成员变量了;最后一个参数我们还是不用理它,输入NULL就行了。
然后要做的是设置键盘设备的数据格式,这需要调用SetDataFormat函数,并输入预定义的c_dfDIKeyboard。
然后,我们还需要设置协作等级。这需要调用SetCooperativeLevel函数,它的第二个参数由两部分构成,第一部
分用于指定是前台访问还是后台访问(DISCL_FOREGROUND或DISCL_BACKGROUND),第二部分用于指定
是独占还是非独占模式(DISCL_EXCLUSIVE或DISCL_NONEXCLUSIVE)。下面是这四个标识所代表的意义:
DISCL_FOREGROUND:输入设备处于前台(有焦点)时可访问,处于后台时失去设备获取。
DISCL_BACKGROUND:输入设备处于前台、后台时均可访问。
DISCL_EXCLUSIVE:独占模式,一旦得到准许,在获取设备时其它程序便不能独占此设备。
DISCL_NONEXCLUSIVE:非独占模式,这时对该设备的访问将不会因其它应用程序对该设备的使用而受到干扰。
最后要做的是获取设备,通过设备的Acquire函数,这意味着我们有权读取设备的数据了。
Settingupthemouse(设置鼠标)
同样的,在使用鼠标之前我们也得设置它。下面是设置鼠标的源代码,你会发现它们跟设置键盘的代码差不多:
//MOUSE=======================================================================
//Createthemousedeviceobject
if(FAILED(m_pDirectInput->CreateDevice(GUID_SysMouse,&m_pMouse,NULL)))
{
CleanUpDirectInput();
LogError("
returnfalse;
}
else
{
LogInfo("
}
//Setthedataformatforthemouse
if(FAILED(m_pMouse->SetDataFormat(&c_dfDIMouse)))
{
CleanUpDirectInput();
LogError("
ndyPike
-74-
returnfalse;
}
else
{
LogInfo("
}
//Setthecooperativelevelforthemouse
if(FAILED(m_pMouse->SetCooperativeLevel(hWnd,
DISCL_FOREGROUND|DISCL_NONEXCLUSIVE)))
{
CleanUpDirectInput();
LogError("
returnfalse;
}
else
{
LogInfo("
}
//Acquirethemouse
if(m_pMouse)
{
m_pMouse->Acquire();
}
其实跟设置键盘差不多,有一点不同是我们这回输入给CreateDevice的GUID是系统预定的GUID_SysMouse
了,这样就会返回一个鼠标设备的接口指针。另一点不同是设备数据格式,我们用了c_dfDIMouse(而不是原来的
c_dfDIKeyboard),这是标准的鼠标数据格式。最后,我们设置了同样的协作等级并获取了鼠标设备。
BufferedandImmediateData(缓冲数据与即时数据)
现在我们已经设置好鼠标和键盘了,我们可以读取它们的数据了。我们可以读取两种类型的数据:缓冲数据和即时数
据。顾名思义,缓冲数据就是在专用的缓冲区中存储的输入设备的数据,而即时数据就是指设备当前的状态,例如鼠
标左键是被按下还是弹起。这两种数据都是有效的,而且经常被应用程序同时使用。例如,用缓冲数据来控测鼠标的
移动,用即时数据来检测按键的操作。在此教程中我们只接触一下即时数据类型。
Gettingkeyboarddata(读取键盘数据)
我们又有了一个新的函数ProcessKeyboard,它会在Render函数中被调用,这意味着在每一帧它都会被调用一
次。下面是它的代码:
voidCGame::ProcessKeyboard()
{
charKeyboardState[256];
if(FAILED(m_pKeyboard->GetDeviceState(sizeof(KeyboardState),
(LPVOID)&KeyboardState)))
{
return;
}
if(KEYDOWN(KeyboardState,DIK_ESCAPE))
{
//me.
m_fQuit=true;
}
//RotateEarth
if(KEYDOWN(KeyboardState,DIK_RIGHT))
{
m_rRotate-=0.5f;
ndyPike
-75-
}
elseif(KEYDOWN(KeyboardState,DIK_LEFT))
{
m_rRotate+=0.5f;
}
//Setanupperandlowerlimitfortherotationfactor
if(m_rRotate<-20.0f)
{
m_rRotate=-20.0f;
}
elseif(m_rRotate>20.0f)
{
m_rRotate=20.0f;
}
//ScaleEarth
if(KEYDOWN(KeyboardState,DIK_UP))
{
m_rScale+=0.5f;
}
elseif(KEYDOWN(KeyboardState,DIK_DOWN))
{
m_rScale-=0.5f;
}
//Setanupperandlowerlimitforthescalefactor
if(m_rScale<1.0f)
{
m_rScale=1.0f;
}
elseif(m_rScale>20.0f)
{
m_rScale=20.0f;
}
}
这个函数真是简单明了。首先,我们调用了GetDeviceState函数并把当前的键盘状态填充到了一个字符数组中。
然后,我们用了一个简单的宏KEYDOWN来检测我们感兴趣的按键是否费按下了。所以,就基于这几个按键,我们
就能控制一些成员变量了(进而控制程序的运行)。
#defineKEYDOWN(name,key)(name[key]&0x80)
Gettingmousedata(读取鼠标数据)
同样地,我们又有了一个函数ProcessMouse,也是在Render中被调用,下面是它的代码:
voidCGame::ProcessMouse()
{
DIMOUSESTATEMouseState;
if(FAILED(m_pMouse->GetDeviceState(sizeof(MouseState),(LPVOID)&MouseState)))
{
return;
}
//Istheleftmousebuttondown?
if(MOUSEBUTTONDOWN(tons[MOUSEBUTTON_LEFT]))
{
m_nMouseLeft=1;
}
else
{
ndyPike
-76-
m_nMouseLeft=0;
}
//Istherightmousebuttondown?
if(MOUSEBUTTONDOWN(tons[MOUSEBUTTON_RIGHT]))
{
m_nMouseRight=1;
}
else
{
m_nMouseRight=0;
}
m_nMouseX+=;
m_nMouseY+=;
}
我们首先也是调用了GetDeviceState,用鼠标数据填充了一个DIMOUSESTATE型数据结构,它有四个成员:
lX:此次与上次调用GetDeviceState的这段期间,x轴移动的距离。
lY:此次与上次调用GetDeviceState的这段期间,y轴移动的距离。
lZ:此次与上次调用GetDeviceState的这段期间,轮移动的距离。
rgbButtons:鼠标按键状态的数组。
lX,lY和lZ返回此次与上次调用GetDeviceState的这段期间内所移动的设备单位距离(非像素距离)。如果你想
得到鼠标在屏幕上的当前位置,可以调用GetCursorPos这个Win32API函数。我们用MOUSEBUTTONDOWN
这个宏来检测感兴趣的鼠标按键是否被按下。下面是我们定义的宏:
#defineMOUSEBUTTONDOWN(key)(key&0x80)
#defineMOUSEBUTTON_LEFT0
#defineMOUSEBUTTON_RIGHT1
#defineMOUSEBUTTON_MIDDLE2
Usingthedata(使用数据)
在我们的Render3D函数中,我们用刚刚得到的键盘鼠标数据来变更场景。下面是Render3D的代码:
voidCGame::Render3D()
{
//Renderour3Dobjects
D3DXMATRIXmatEarth,matScale,matRotate,matEarthRoll,matEarthMove;
floatrMouseSpeed=20.0f;
//Ifleftmousebuttonisdown,stoptheearthfromrotating
if(m_nMouseLeft==1)
{
m_rRotate=0.0f;
}
//Ifrightmousebuttonisdown,starttheearthrotatingatdefaultspeed
if(m_nMouseRight==1)
{
m_rRotate=2.0f;
}
m_rAngle+=m_rRotate;
//Createthetransformationmatrices
D3DXMatrixRotationY(&matRotate,D3DXToRadian(m_rAngle));
D3DXMatrixScaling(&matScale,m_rScale,m_rScale,m_rScale);
D3DXMatrixRotationYawPitchRoll(&matEarthRoll,0.0f,0.0f,D3DXToRadian(23.44f));
ndyPike
-77-
D3DXMatrixTranslation(&matEarthMove,(m_nMouseX/rMouseSpeed),
-(m_nMouseY/rMouseSpeed),0.0f);
D3DXMatrixMultiply(&matEarth,&matScale,&matRotate);
D3DXMatrixMultiply(&matEarth,&matEarth,&matEarthRoll);
D3DXMatrixMultiply(&matEarth,&matEarth,&matEarthMove);
//Renderourobjects
m_pD3DDevice->SetTransform(D3DTS_WORLD,&matEarth);
m_dwTotalPolygons+=m_pSphere->Render();
}
Cleaningup(清理)
下面是一个简单的清理函数,它会放弃对鼠标键盘设备的获取,清理DirectInput及相关的接口指针。此函数会在
CGame的析构函数中被调用。
voidCGame::CleanUpDirectInput()
{
if(m_pKeyboard)
{
m_pKeyboard->Unacquire();
}
if(m_pMouse)
{
m_pMouse->Unacquire();
}
SafeRelease(m_pMouse);
SafeRelease(m_pKeyboard);
SafeRelease(m_pDirectInput);
}
那末,我们有了一个能控制的地球(译者:给我一个鼠标,我便能控制整个地球):
ndyPike
-78-
第十三章:声音和音乐
Introduction(序)
这一章我们学习怎样用DX来播放声音和音乐。我们将会使用DirectXAudio来播放WAV和MIDI文件,使用
DirectShow来播放MP3文件。此章我们仍然有一个简单的程序作例子,它会播放MP3的背景音乐,当用户用鼠
标点击某个数字时它会播放一些音效。
DirectXAudioandDirectShow(DirectXAudio和DirectShow)
我们已经学习过Direct3D和DirectInput了,现在,我要介绍另外两个DirectX组件:DirectXAudio和
DirectShow。我们用DirectXAudio来播放WAV和MIDI文件;用DirectShow来播放媒体流,例如AVI和
MP3。在此教程中,我们只学习一下用DirectShow播放MP3的方法。
Wav,MidiandMp3-WhenshouldIusewhat?
(WAV,MIDI和MP3——什么时候该用什么?)
那末,什么时候用什么格式好呢?嗯,这基本上取决于你的个人爱好。下面是这些格式的简介,过后我会说一下我的
选择:
Wavfiles(WAV文件)
WAV是一种未经压缩的数字音频,CD音质,但是体积非常庞大。
Midifiles(MIDI文件)
MIDI文件并没有保存音频记录,它实际上更像是一套演奏指令,所以,它的体积是非常小的。MIDI的音质完全取
决于演奏设备(如我们的声卡),它在高端的设备上能表现出色,而在低端设备上则表现较差。
Mp3files(MP3文件)
同WAV文件一样,MP3也是一种数字音频格式。不同的是,MP3文件是经过压缩的,而且是有损压缩,这意味着
它的体积将大大地减小,而音质上将会有一些失真(实际上接近CD音质,基本听不出失真)。而且,MP3是一种
媒体流,这意味着在播放它的时候不会将它整个的读入,取而代之的是分期的做部分读入。
ndyPike
-79-
Mypreference(我的选择)
你大概会选择MP3或MIDI来作背景音乐,特别是当你希望用户能从网上下载你的程序时。MP3和MIDI格式文件
的体积都比较小,它们适合做长时间的背景音乐播放。而当需要播放简短的声效时,例如爆炸声等,我更趋向于使用
WAV文件,品质能稍好一些。如果文件的体积不是问题的话,实际上我们可以使用WAV文件来播放所有的声音和
音乐。
IncludeandLibraryfiles(头文件与库文件)
我们的项目需要增加以下的头文件和库文件:
odmusici.h
odsound.h
odshow.h
SettingupDirectXAudio(设置DirectXAudio)
要使用DirectAudio我们需要创建两个对象:演奏对象(PerformanceObject,有些资料翻译为执行对象)和加
载器(Loader)。演奏对象是DirectAudio中的最高级对象,用于处理从源到合成器的数据流。加载器用于把文
件(WAV或MIDI)加载到声音段以待播放。这两个对象在整个应用程序中我们每样只需要一个,所以,我们已经
把它们创建为CGame的成员变量了。下面是它们的定义:
IDirectMusicPerformance8*m_pDirectAudioPerformance;
IDirectMusicLoader8*m_pDirectAudioLoader;
由于DirectAudio是一个纯COM,所以我们需要初始化COM。其实很简单,我们只需要在CGame的构造函数中
调用CoInitialize函数即可:
CoInitialize(NULL);
初始化COM后,我们就可以创建上述的那两个DirectXAudio对象了。为此,我们给CGame做了一个新的函数
InitialiseDirectAudio,它将会在Initialise函数中被调用。代码如下:
boolCGame::InitialiseDirectAudio(HWNDhWnd)
{
LogInfo("
InitialiseDirectAudio:");
//CreatetheDirectAudioperformanceobject
if(CoCreateInstance(CLSID_DirectMusicPerformance,NULL,CLSCTX_INPROC,
IID_IDirectMusicPerformance8,
(void**)&m_pDirectAudioPerformance)!=S_OK)
{
LogError("
returnfalse;
}
else
{
LogInfo("
}
//CreatetheDirectAudioloaderobject
if(CoCreateInstance(CLSID_DirectMusicLoader,NULL,CLSCTX_INPROC,
IID_IDirectMusicLoader8,
(void**)&m_pDirectAudioLoader)!=S_OK)
{
LogError("
returnfalse;
ndyPike
-80-
}
else
{
LogInfo("
}
//Initialisetheperformanceobject
if(FAILED(m_pDirectAudioPerformance->InitAudio(NULL,NULL,hWnd,
DMUS_APATH_SHARED_STEREOPLUSREVERB,
64,DMUS_AUDIOF_ALL,NULL)))
{
LogError("
returnfalse;
}
else
{
LogInfo("
}
//Gettheourapplications"sounds"directory.
CHARstrSoundPath[MAX_PATH];
GetCurrentDirectory(MAX_PATH,strSoundPath);
strcat(strSoundPath,"Sounds");
//Convertthepathtounicode.
WCHARwstrSoundPath[MAX_PATH];
MultiByteToWideChar(CP_ACP,0,strSoundPath,-1,wstrSoundPath,MAX_PATH);
//Setthesearchdirectory.
if(FAILED(m_pDirectAudioLoader->SetSearchDirectory(GUID_DirectMusicAllTypes,
wstrSoundPath,FALSE)))
{
LogError("
returnfalse;
}
else
{
LogInfo("
}
returntrue;
}
我们用CoCreateInstance函数创建了演奏对象和加载器,然后调用了InitAudio模块来初始化演奏对象。上面代
码中给InitAudio的参数是很典型的,所以你可以沿用它(查看一下SDK中的关于InitAudio函数以及它的参数的
内容以获得更多的了解)。最后,我们设置了搜索目录。搜索目录应该被设置为存放声音文件的目录,以便加载器可
以正确的加载声音文件。在本例中,搜索目录被设置为“Sounds”文件夹,它应该在本例的项目文件夹当中。
Anewclass-CSound(一个新的类——CSound)
我们已经为使用DirectXAudio作了必要的准备工作,现在,我们就可以加载、播放声音和音乐了。为此,我们做
了一个新的类CSound,下面是它的代码,稍后我会解释它是怎样工作的:
CSound::CSound()
{
m_pDirectAudioPerformance=NULL;
m_pDirectAudioLoader=NULL;
m_pSegment=NULL;
m_pGraph=NULL;
m_pMediaControl=NULL;
m_pMediaPosition=NULL;
m_enumFormat=Unknown;
ndyPike
-81-
LogInfo("
}
voidCSound::InitialiseForWavMidi(IDirectMusicPerformance8*pDirectAudioPerformance,
IDirectMusicLoader8*pDirectAudioLoader)
{
m_pDirectAudioPerformance=pDirectAudioPerformance;
m_pDirectAudioLoader=pDirectAudioLoader;
m_enumFormat=WavMidi;
}
voidCSound::InitialiseForMP3()
{
CoCreateInstance(CLSID_FilterGraph,NULL,
CLSCTX_INPROC,IID_IGraphBuilder,(void**)&m_pGraph);
m_pGraph->QueryInterface(IID_IMediaControl,(void**)&m_pMediaControl);
m_pGraph->QueryInterface(IID_IMediaPosition,(void**)&m_pMediaPosition);
m_enumFormat=MP3;
}
CSound::~CSound()
{
Stop();
SafeRelease(m_pSegment);
SafeRelease(m_pGraph);
SafeRelease(m_pMediaControl);
SafeRelease(m_pMediaPosition);
LogInfo("
}
boolCSound::LoadSound(constchar*szSoundFileName)
{
WCHARwstrSoundPath[MAX_PATH];
CHARstrSoundPath[MAX_PATH];
switch(m_enumFormat)
{
caseMP3:
//Gettheourapplications"sounds"directory.
GetCurrentDirectory(MAX_PATH,strSoundPath);
strcat(strSoundPath,"Sounds");
strcat(strSoundPath,szSoundFileName);
//Convertthepathtounicode.
MultiByteToWideChar(CP_ACP,0,strSoundPath,-1,
wstrSoundPath,MAX_PATH);
m_pGraph->RenderFile(wstrSoundPath,NULL);
break;
caseWavMidi:
//Convertthefilenametounicode.
MultiByteToWideChar(CP_ACP,0,szSoundFileName,-1,
wstrSoundPath,MAX_PATH);
//Loadasound
ndyPike
-82-
m_pDirectAudioLoader->LoadObjectFromFile(CLSID_DirectMusicSegment,
IID_IDirectMusicSegment8,
wstrSoundPath,
(void**)&m_pSegment);
m_pSegment->Download(m_pDirectAudioPerformance);
break;
default:
returnfalse;
}
returntrue;
}
boolCSound::Play(DWORDdwNumOfRepeats)
{
switch(m_enumFormat)
{
caseMP3:
//Makesurethatweareatthestartofthestream
m_pMediaPosition->put_CurrentPosition(0);
//Playmp3
m_pMediaControl->Run();
break;
caseWavMidi:
//Setthenumberoftimesthesoundrepeats
m_pSegment->SetRepeats(dwNumOfRepeats);//Toloopthesoundforever,pass
//inDMUS_SEG_REPEAT_INFINITE
//Playtheloadedsound
m_pDirectAudioPerformance->PlaySegmentEx(m_pSegment,NULL,NULL,0,0,
NULL,NULL,NULL);
break;
default:
returnfalse;
}
returntrue;
}
boolCSound::Stop()
{
switch(m_enumFormat)
{
caseMP3:
m_pMediaControl->Stop();
break;
caseWavMidi:
//Stoptheloadedsound
m_pDirectAudioPerformance->StopEx(m_pSegment,0,0);
break;
default:
returnfalse;
}
returntrue;
}
boolCSound::IsPlaying()
{
switch(m_enumFormat)
ndyPike
-83-
{
caseMP3:
REFTIMErefPosition;
REFTIMErefDuration;
m_pMediaPosition->get_CurrentPosition(&refPosition);
m_pMediaPosition->get_Duration(&refDuration);
if(refPosition
{
returntrue;
}
else
{
returnfalse;
}
break;
caseWavMidi:
if(m_pDirectAudioPerformance->IsPlaying(m_pSegment,NULL)==S_OK)
{
returntrue;
}
else
{
returnfalse;
}
break;
default:
returnfalse;
}
}
那末,怎样用CSound来播放音乐呢?这很简单。首先,创建一个CSound对象,然后根据你想播放的声音类型来
调用InitialiseForWavMidi或是InitialiseForMP3,二者选其一。接着,调用LoadSound读取声音文件。最后,
调用Play模块来播放声音即可。很简单,不是吗?嗯,还有两个模块:Stop模块用来停止播放;IsPlaying模块用
来检测声音是否正在播放。下面介绍了各个模块是怎样工作的:
InitialiseForWavMidi
InitialiseForWavMidi模块用于初始化CSound对象以待播放WAV或MIDI文件。它的两个输入参数是我们在
CGame中创建好的演奏对象和加载器的接口指针,然后这两个指针会被保存为它的成员变量以备用。我们会把
CSound的格式设置为WavMidi。
InitialiseForMP3
InitialiseForMP3模块用于初始化CSound对象以待播放MP3文件,它没有输入参数。InitialiseForMP3调用
CoCreateInstance函数创建了一个DirectShow过滤器图管理器对象。我们能使用CoCreateInstance是因为我
们在CGame的构造函数中已经调用过CoInitialize函数了。有了过滤器图管理器对象之后,我们调用它的模块
QueryInterface来创建另外两个对象:一个媒体控制对象和一个媒体定位对象。这三个对象的指针将会被保存为
CSound对象的成员变量。同理,我们把CSound的格式设置为MP3;
LoadSound
初始化好CSound对象后,我们就可以调用LoadSound来读取声音文件了。LoadSound只有一个参数,就是声
音文件的文件名。这里不是一个路径而是一个文件名是因为所有的声音文件都应该在我们的项目的“Sounds”目录
中。
如果这个CSound对象的格式为MP3,我们首先会构造出一个完整的文件路径,然后把它转换为Unicode字符集
的字符串。接着,我们只需要调用过滤器图管理器对象的RenderFile模块并构造一个用于播放指定文件的过滤器图
即可。
ndyPike
-84-
如果这个CSound对象的格式为WavMidi,我们首先会把文件名转换成Unicode字符集的字符串。接着我们会调
用LoadObjectFromFile,它会载入文件并返回一个段(segment)指针。最后,我们调用段的Download模块下
载波段到演奏对象里。
现在我们已经准备好来播放声音了。
Play
当文件载入后,我们就能播放它了。Play模块唯一的可选参数(默认为0)就是重复播放次数,只在播放WAV或
MIDI时有效。
如果这个CSound对象的格式为MP3,我们首先会调用媒体定位对象的put_CurrentPosition模块来确认一下我
们是否在媒体流的开始处。然后,我们调用媒体控制对象的Run模块来播放MP3。
如果这个CSound对象的格式为WavMidi,我们首先会设置重复次数。然后我们调用PlaySegmentEx来播放已
经载入的段。如果你想让声音永远重复,输入DMUS_SEG_REPEAT_INFINITE恒量。
Stop
这能简单地停止正在播放的声音。如果是MP3,就调用媒体控制对象的Stop模块。如果是WAV或MIDI,就调用
演奏对象的StopEx模块并输入要停止的段。
IsPlaying
最后,IsPlaying模块用于检测当前是否正在播放。是就返回true,不是则返回false。如果是MP3,我们会根据
当前的媒体流定位与整个媒体流的长度做对比,以得知我们是否正在播放(看是否在流的终点)。如果是WAV,我
们就调用演奏对象的IsPlaying模块并输入要检测的段以测试。
Cleaningup(清理)
在Csound的析构函数中我们会停止声音的播放并释放对象。在CGame中有一个新的函数CleanUpDirectAudio
代码如下:
voidCGame::CleanUpDirectAudio()
{
//Stopallsounds.
m_pDirectAudioPerformance->Stop(NULL,NULL,0,0);
//CleanUp
m_pDirectAudioPerformance->CloseDown();
SafeRelease(m_pDirectAudioLoader);
SafeRelease(m_pDirectAudioPerformance);
LogInfo("
}
Herewefirststopallsoundsfromplaying(justincase),thenweclosedowntheperformanceobject
,intheCGamedeconstructorweneedtocall
CoUninitializetoclosedowntheCOMlibrary(shownbelow).
这里我们首先停止所有声音的播放,然后关闭演奏对象,释放演奏对象和加载器。而且,在CGame的析构函数中
我们会调用CoUninitialize来关掉COM:
CoUninitialize();
那末现在我们能在程序中播放音乐和声音了。当程序开始时,MP3的背景音乐就会开始播放。移动鼠标并点击不同
的数字我们对听到不同的声音。这里的背景MP3只是一个小片断的循环,愿意的话你可以替换成你喜欢的音乐。
(译者:我把我的MP3都放了一遍爽)
ndyPike
-85-
更多推荐
directx9 0下载
发布评论