| q一章我先不写有关DX的东?我先从最基本的窗口创?然后再慢慢讲解用DX的一些内?
我写q个指南的主要目的是Z学习。我希望自己可以通过写这个指南更快地学会DirectX。同Ӟ我也希望为其他想学习的同伴提供一些学习资料。在~程斚w,我ƈ不是很强的hQ再加上人L会犯错的Q如果我q些文字l贻W大方的?我接受大家对我提出有性的批评Q如果你有更好的x要和我交,可以联系?fowenler@126.com
下面正式开始吧,先讲H口c?创徏H口,销毁窗?H口消息处理函数.
H口cWNDCLASS
struct WNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; };
styleQ用来定义窗口的行ؓ。如果打共同用GDI和D3D的话Q可以用CS_OWNDC作ؓ参数?BR> lpfnWndProcQ一个函数指针,指向与这个窗口类l定在一L处理H口消息的函数?BR> cbClsExtra和cbWndExtraQؓH口和ؓ分配内存I间。很用到q两个参敎ͼ一般设?Q?BR> hInstanceQ应用程序的实例句柄。你可以使用GetModuleHandle()来得到它Q也可以从Win32E序的入口函数WinMain那里得到它。当Ӟ你也可以把它设ؓNULLQ不知有什么用Q?BR> hIconQhCursorQhbrBackgroundQ设|默认的图标、鼠标、背景颜艌Ӏ不q在q里讄q些其实q不怎么重要Q因为我们可以在后面定制自己的渲染方法?BR> lpszMenuNameQ用来创?BR> lpszClassNameQ窗口类的名字。我们可以通过q个名字来创Zq个H口cMؓ模板的窗口。甚臛_以通过q个名字来得到窗口的句柄?BR> 讄好窗口类l构的内容后Q用RegisterClass(const WNDCLASS *lpWndClass)函数来注册它。关闭窗口后可以用UnregisterClass(LPCSTR lpClassName, HINSTANCE hInstance)来撤销注册?BR>
创徏H口CreateWindow
HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, y, int nWidth, nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam );
lpClassNameQ窗口类的名字。即H口cȝ构体中的lpszClassName成员?BR> lpWindowNameQ如果你的应用程序有标题栏,q个是你标题栏上显C的内容?BR> dwStyleQ窗口的风格军_你的H口是否有标题栏、最大最化按钮、窗口边框等属性。在全屏的模式下QWS_POPUP|WS_VISIBLE是常用的讄Q因为它产生一个不带Q何东西的全屏H口。在H口的模式下Q你可以讄很多H口的风|具体可以查看相关资料Q这里不详细说明Q不q?WS_OVERLAPPED|WS_SYSMENU|WS_VISIBLE是一l常用的风格?BR> x和yQ窗口创建的位置?xQy)表示H口的左上角位置?BR> nWidth和nHeightQ用来设|窗口的宽度和高度,以像素ؓ单位。如果你惛_Z个全屏的H口Q用GetSystemMetrics(SM_CXSCREEN)和GetSystemMetrics(SM_CYSCREEN)可以得到当前昄器屏q的大小
hWndParentQ指定这个新建窗口的父窗口。在D3D应用E序中很用Q一般设为NULL?BR> hMenuQ菜单句柄?BR> hInstanceQ应用程序的实例句柄。你可以使用GetModuleHandle()来得到它Q也可以从Win32E序的入口函数WinMain那里得到它。当Ӟ你也可以把它设ؓNULLQ不知有什么用Q?BR> lpParamQ一个很秘的参数。除非你知道自己在做什么,否则q是把它设ؓNULL吧?BR>
销毁窗口DestroyWindow
销毁窗口有两种ҎQ一U是隐式的,一U是昑ּ的。我们都知道Windows操作pȝ是一个基于消息驱动的pȝ。流动于pȝ中的消息使我们的H口跑v来。在很多软g开发特别是商业软g的开发过E中Q窗口的产生和销毁都是交ql去做的Q因些不是这cd发的x所在。但是游戏开发不一P管你也可以只向pȝ发送一条WM_DESTROY消息来销毁窗口,我们q是希望H口是销毁的明明白白的。由于窗口的注册、生和使用都是由我们亲手来做的Q那么当然窗口的销毁也得由我们亲自来做。不q还是得说明一点,使用WM_DESTROY消息和DestroyWindow函数来销毁窗口在本质上ƈ无太大差别,使用哪种Ҏ可以说是Ҏ个h的爱好吧?BR> 销毁窗口后是不是就完事了呢Q不Q还没有Q因为应用程序的消息队列里可能还有没处理完的消息Qؓ了彻底的安全Q我们还得把那些消息都处理完。所以结束应用程序的时候,可以使用以下ҎQ?BR> MSG msg; DestroyWindow(h_wnd); while(PeekMessage(&msg , NULL , 0 , 0 , PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); }
H口消息处理q程
H口消息的处理函数是一个回调函敎ͼ什么是回调函数Q就是由操作pȝ负责调用的函数。CALLBACKq个宏其实就是__stdcallQ这是一U函数调用的方式Q在q里不多说这些了Q有兴趣的可以参考一些Windows~程的书c,里面会有很详的说明?BR> Windows里面有很多消息,q些消息都跑d里了呢?其实它们都在自己的消息队列里{候。消息是怎么从队列里出去的呢Q就是通过 GetMessage和PeekMessageq两个函数。那么消息从队列里出d又到哪里了呢Q嗯Q这时候消息就正式q入了我们的H口消息处理q程Q也xH口cMlpfnWndProc所指定的函数。一个消息处理函数有四个参数Q下面分别说_
参数1QHWND p_hWnd
消息不都是传CH口cMؓ模板产生的窗口吗Qؓ什么还要用窗口句柄来指明H口呢?别忘了一个窗口类是可以生多个窗口的呀Q如果一个应用程序里面有多个H口Qƈ且它们之中的一些窗口是q一个窗口类的,那么得用一个窗口句柄来指明I竟q个消息是哪个窗口发q来的?BR> 参数2QUINT p_msg
q是一个消息类型,是WM_KEYDOWN , WM_CLOSE , WM_TIMERq些东东?BR> 参数3QWPARAM p_wparam
q个参数内容是消息的主要内宏V如果是WM_KEYDOWN消息Q那么p_wparam是用来告诉你究竟是哪个键被按下?BR> 参数4QLPARAM p_lparam
q个参数的内容一般是消息的一些附加内宏V?BR> 最后说明一下DefWindowProc的作用。有时候我们把一个消息传到窗口消息处理函数里面,但是里面没有处理q个消息的内宏V怎么办?很容易,交给DefWindowProc处理对了?BR> 嗯,q一章就说到q了Q下一章介l如何创建D3D接口及如何用D3D讑֤?BR>
创徏IDirect3D接口
DirectX是一lCOMlgQCOM是一U二q制标准Q每一个COM里面提供了至一个接口,而接口就是一l相关的函数Q我们?DirectXQ其实就是用那些函数。COM和C++中的cL点像Q只不过COM使用自己的方法来创徏实例。创建COM实例的一般方法是使用 coCreateInstance函数。有关coCreateInstance的用方法,可以参考有关COM斚w的资料,q里暂时不详l说明了Q因?DirectX提供了更z的Ҏ来创建DirectXlg的实例。这一章我要讲的就是Direct3Dlg的用方法?BR> Z使用D3D中的函数Q我们得先定义一个指向IDirect3D9q个接口的指针,Z说明一下ؓ什么要定义q个指针。首先,我们要知道接口的内容是一些纯虚拟函数Q所以接口是不能被实例化的,但是我们可以定义一个指向接口的指针。其ơ,我们要知道利用多态性我们可以用一个基cL针来讉KzcM的方法。既然接口是不能被实例化的,那么我们肯定是用从接口z出来的类Q或l构Q的Ҏ。怎么获到q个zcȝ指针呢?是通过之前定义的接口指针(也即是基cL针)来获得。所以我们所需做的是把一个接口指针的地址传给某个函数Q让q个函数来帮我们获到正确的派生类指针Q这h们就可以使用接口指针来做一些实际的东西了。实际上Q我们只需要知道接口里面有什么方法以及它能完成什么工作就行了Q至于这些方法是怎么实现的我们不必去兛_。我们要做的是定义一个接口指针,把它传给某个函数Q函C我们的接口指针有意义Q接着我们使用接口Q就q么单。定义完q个接口指针后,例如IDirect3D9 *g_pD3D;现在我们使用Direct3DCreate9q个函数来创Z个D3D接口Q?BR> g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
Direct3DCreate9q个函数只有一个参敎ͼ它表明要创徏接口的版本。如果你惛_Z个老的接口版本当然也可以,不过没有Z那样做吧?BR> 创徏接口后就可以创徏D3D讑֤了,什么是D3D讑֤Q你可以惌ZZ的那块显卡!什么?你有几块昑֍Q!没关p,那就创徏多几个D3D讑֤接口吧。创建D3D讑֤需要的参数很多Q如果把那些参数都挤在一个函数里面,那就太长了,所以就把一些参数放q结构体里面Q只要先讑֮好这些结构体Q再把这些结构体当作参数传给创徏D3D讑֤的函敎ͼ那就清晰多了。首先要讲的是D3DPRESENT_PARAMETERSq个l构。下面是它的定义Q?BR> struct D3DPRESENT_PARAMETERS{ UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; };
BackBufferWidth和BackBufferHeightQ后备缓冲的宽度和高度。在全屏模式下,q两者的值必需W合昑֍所支持的分辨率。例如(800Q?00Q,Q?40Q?80Q?BR> BackBufferFormatQ后备缓冲的格式。这个参数是一个D3DFORMAT枚DcdQ它的值有很多U,例如D3DFMT_R5G6B5Q这说明后备~冲的格式是每个像素16位,其实U色QRQ占5位,l色QGQ占6位,蓝色QBQ占5位,Z么绿色会多一位呢Q据说是因ؓ人的眼睛对绿色比较敏感。DX9只支?6位和32位的后备~冲格式Q?4位ƈ不支持。如果对qD3DFORMAT不熟悉的话,可以把它设ؓD3DFMT_UNKNOWNQ这时候它用桌面的格式?BR> BackBufferCountQ后备缓冲的数目Q范围是??Q如果ؓ0Q那当?来处理。大多数情况我们只用一个后备缓册Ӏ用多个后备缓冲可以画面很流畅,但是却会造成输入讑֤响应q慢Q还会消耗很多内存?BR> MultiSampleType和MultiSampleQualityQ前者指的是全屏抗锯齿的cdQ后者指的是全屏抗锯齿的质量{。这两个参数可以使你的渲染场景变得更好看Q但是却消耗你很多内存资源Q而且Qƈ不是所有的昑֍都支持这两者的所讑֮的功能的。在q里我们分别把它们设?D3DMULTISAMPLE_NONE??BR> SwapEffectQ交换缓冲支持的效果cdQ指定表面在交换链中是如何被交换的。它是D3DSWAPEFFECT枚DcdQ可以设定ؓ以下三者之一Q?D3DSWAPEFFECT_DISCARDQD3DSWAPEFFECT_FLIPQD3DSWAPEFFECT_COPY。如果设定ؓ D3DSWAPEFFECT_DISCARDQ则后备~冲区的东西被复制到屏幕上后Q后备缓冲区的东西就没有什么用了,可以丢弃QdiscardQ了。如果设定ؓD3DSWAPEFFECT_FLIPQ则表示在显C和后备~冲之间q行周期循环。设定D3DSWAPEFFECT_COPY的话Q我也不太清楚有什么作?^_^*。一般我们是把这个参数设为D3DSWAPEFFECT_DISCARD?BR> hDeviceWindowQ显C备输出窗口的句柄
WindowedQ如果ؓFALSEQ表C渲染全屏。如果ؓTRUEQ表C渲染H口。渲染全屏的时候,BackBufferWidth和BackBufferHeight的值就得符合显C模式中所讑֮的倹{?BR> EnableAutoDepthStencilQ如果要使用Z~冲或模板缓Ԍ则把它设为TRUE?BR> AutoDepthStencilFormatQ如果不使用深度~冲Q那么这个参数将没有用。如果启动了深度~冲Q那么这个参数将为深度缓冲设定缓冲格式(和设定后备缓冲的格式差不多)
FlagsQ可以设|ؓ0或D3DPRESENTFLAG_LOCKABLE_BACKBUFFER。不太清楚是用来做什么的Q看字面好像是一个能否锁定后备缓冲区的标记?BR> FullScreen_RefreshRateInHzQ显C器的刷新率Q单位是HZQ如果设定了一个显C器不支持的h率,会不能创徏讑֤或发告信息。ؓ了方便,一般设为D3DPRESENT_RATE_DEFAULTp了?BR> PresentationIntervalQ如果设|ؓD3DPRENSENT_INTERVAL_DEFAULTQ则说明在显CZ个渲染画面的时候必要等候显C器h完一ơ屏q。例如你的显C器h率设?0HZ的话Q则一U内你最多可以显C?0个渲染画面。另外你也可以设|在昄器刷Cơ屏q的旉内显C??个画面。如果设|ؓD3DPRENSENT_INTERVAL_IMMEDIATEQ则表示可以以实时的方式来显C渲染画面,虽然q样可以提高帧速(FPSQ,但是却会产生囑փ撕裂的情c?BR>
创徏IDirect3DDevice界面
当你把D3DPRESENT_PARAMETERS的参数都讄好后Q就可以创徏一个D3D讑֤了,和创建D3D接口一P先定义一个接口指针IDirect3DDevice9 * g_pD3DDevice;然后使用D3D接口里面的CreateDevice函数来创备。CreateDevice的声明ؓQ?BR> HRESULT CreatDevice( UINT Adapter, D3DDEVTYPE DeviceType, HWND hFocusWindow, DWORD BehaviorFlags, D3DPRESENT_PARAMETERS *pPresentationParameters, IDirect3DDevice9** ppReturnedDeviceInterface };
W一个参数说明要为哪个设备创备指标,我之前说q一台机可以有好几个昑֍Q这个参数就是要指明为哪块显卡创建可以代表它的设备指标。但是我怎么知道昑֍的编号呢Q可以用D3D接口里面的函数来获得Q例如GetAdapterCounter可以知道pȝ有几块显卡; GetAdapterIdentifier可以知道昑֍的具体属性。一般我们设q个参数为D3DADAPTER_DEFAULT?BR> W二个参数指明正在用设备类型。一般设为D3DEVTYPE_HAL?BR> W三个参数指明要渲染的窗口。如果ؓ全屏模式Q则一定要设ؓȝ口?BR> W四个参数是一些标讎ͼ可以指定用什么方式来处理点?BR> W五个参数就要用C面所讲的D3DPRESENT_PARAMETERS?BR> W六个参数是q回的界面指针?BR>
开始渲?/B>
有了讑֤接口指针Q就可以开始渲染画面了。渲染是一个连l不断的q程Q所以必定要在一个@环中完成Q没错,是W一章讲的那个消息@环。在渲染开始之前我们要用IDirect3DDevice9::Clear函数来清除后备缓冲区?BR> HRESULT Clear( DWORD Count, const D3DRECT *pRects, DWORD Flags, D3DCOLOR Color, float Z, DWORD Stencil );
CountQ说明你要清I的矩Ş数目。如果要清空的是整个客户区窗口,则设?Q?BR> pRectsQ这是一个D3DRECTl构体的一个数l,如果count中设?Q则q个数组中就得有5个元素。它可以使我们只清除屏幕中的某一部分?BR> FlagsQ一些标记组合,它指定我们要清除的目标缓冲区。只有三U标讎ͼD3DCLEAR_STENCIL , D3DCLEAR_TARGET , D3DCLEAR_ZBUFFER?分别为清除模板缓冲区、清除目标缓冲区Q通常为后备缓冲区Q、清除深度缓冲区?BR> ColorQ清除目标区域所使用的颜艌Ӏ?BR> floatQ设|Z~冲的Z初始倹{小于或{于q个Z初始值的Z值才会被改写Q但它的值只能取0?之间。如果还不清楚什么是Z~冲的话Q可以自己找相关数据看一下,q里不介l了Q呵c?BR> StencilQ设|范本缓冲的初始倹{它的取D围是0?的nơ方?。其中n是范本缓冲的深度?BR> 清除后备~冲区后Q就可以对它q行渲染了。渲染完毕,使用Present函数来把后备~冲区的内容昄到屏q上?BR> HRESULT Present( const RECT *pSourceRect, const RECT *pDestRect, HWND hDestWindowOverride, const RGNDATA *pDirtyRegion );
pSourceRectQ你惌昄的后备缓冲区的一个矩形区域。设为NULL则表C把整个后备缓冲区的内定w昄?BR> pDestRectQ表CZ个显C区域。设为NULL表示整个客户昄区?BR> hDestWindowOverrideQ你可以通过它来把显C的内容昄C同的H口厅R设为NULL则表C显C到ȝ口?BR> pDirtyRegionQ高U用。一般设为NULL?BR>
点属性与点格式
点可谓?D世界中的基本元素。在计算机所能描l的3D世界中,M物体都是由多边Ş构成的,可以是三边ŞQ也可以是四边Ş{。由于三边ŞQ即三角形所h的特D性质军_其在3D世界中得到广泛的使用。构成三角Ş需要三个点Q这些点的性质是q章所要讲的内宏V?BR> 也许你已l知道顶点的l构定义Q你可能会奇怪ؓ什么D3D会知道我们“随侎쀝定义的那些l构呢?其实那些点的定义可不是那么随便的哦。下面列丑֜Direct3D中,点所h的所有属性?BR> Q?Q位|:点的位|,可以分别指定x,y,x三个|也可以用D3DXVECTOR3l构来定义?BR> Q?QRHWQ齐ơ坐标W的倒数。如果顶点ؓ变换点的话Q就要有q个倹{设|这个值意味着你所定义的顶点将不需要Direct3D的辅助(不能作变换、旋转、放大羃、光照等Q,要求你自己对点数据q行处理。至于W是什么,W和XYZ一P只是一个四元组的一部分。RHW的英文是Reciprocal of the Homogenous WQ即1/WQ它是ؓ了处理矩늚工作变得Ҏ一些(|U性代数的东东快都忘了Q要恶补一下才行)。一般设RHW的gؓ1.0?BR> Q?Q合加权:用于矩阵混合。高U应用,q里不讲了(其实我不会,^_^Q?BR> Q?Q顶ҎU:学过高等数学应该知道法U是什么吧Q在q里是指l过点且和由顶点引出的边相垂直的线Q即和三角Ş那个面垂直。用三个分量来描q它的方向,q个属性用于光照计?BR> Q?Q顶点大:讑֮点的大,q样点可以不用只占一个像素了?BR> Q?QO反射Ԍ卛_U照到物体上生反的着艌Ӏ理解这个比较麻烦,因ؓ3D光照和真实光照没什么关p,不能像理解真实光照那样去理解3D光照?BR> Q?Q镜面反色Q它可以让一?D物体的表面看h很光滑?BR> Q?Q纹理坐标:如果惌在那些用多边形组成的物体上面贴上U理Q就要用纹理坐标。由于纹理都是二l的Q所以用两个值就可以表示U理上面某一点的位置。在U理坐标中,只能?.0?.0之间取倹{例?0.0 , 0.0)表示U理的左上角Q(1.0 , 1.0Q表C纹理的右下角?BR> 好了Q请C上面属性的序。我们定义一个顶点结构的时候,不一定要包括全部的属性,但是一定要按照上面的顺序来定义。例如:
struct MYVERTEX { D3DXVECTOR3 position; float rhw; D3DCOLOR color; }
上面定义了一个有漫反色的变换顶炏V?BR> 定义完了点的结构后Q我们就要告诉D3D我们定义的是什么格式。ؓ了方便,我们通常会用#define来定义一个叫做描q“灵z顶Ҏ式?QFVFQFlexible Vertex FormatQ的宏。例如:#define MYFVF D3DFVF_XYZ | D3DFVF_NORMAL。根据之前定义的点属性结构体Q我们要定义相对应的宏。假如顶点结构中有位|属性,那么p使用D3DFVF_XYZQ如果是变换点的话Q就要用D3DFVF_XYZRHWQ如果用了漫反色属性的话,p使用D3DFVF_DIFFUSE。这些值是可以l合使用的,像上面那L“|”符号作l符。定义完灉|点格式后,使用IDirect3DDevice9::SetFVF函数来告诉D3D我们所定义的顶Ҏ式,例如Qg_pD3DDevice->SetFVF( MYFVF );
点~冲
处理点信息的地Ҏ两个Q一个是在数l里Q另一个是在D3D所定义的顶点缓冲里。换个说法的话就是一个在我们所能直接操作的内存里,另一个在D3D 理的内存里。对于我们这些对操作pȝ底层了解不多的菜鸟来_直接操作内存实在是太恐怖了Q所以还是交lD3D帮我们处理吧Q虽然不知道背后有些什么操作。要x点信息交给D3D处理Q我们就要先创徏一个顶点缓冲区Q可以用IDirect3DDevice9-> CreateVertexBufferQ它的原型是Q?BR> HRESULT CreateVertexBuffer( UINT Length, DWORD Usage, DWORD FVF, D3DPOOL Pool, IDirect3DVertexBuffer9** ppVertexBuffer, HANDLE* pSharedHandle );
LengthQ缓冲区的长度。通常是顶Ҏ目乘以顶点大,使用Sizeof( MYVERTEX )可以知道顶点的大小了?BR> UsageQ高U应用。设?可以了?BR> FVFQ就是我们之前定义的灉|点格式?BR> PoolQ告诉D3D顶点缓冲存储在内存中的哪个位置。高U应用,通常可取的三个值是QD3DPOOL_DEFAULTQD3DPOOL_MANAGEDQD3DPOOL_SYSTEMMEM。多数情况下使用D3DPOOL_DEFAULT可以了?BR> ppVertexBufferQ返回来的指向IDirect3DVertexBuffer9的指针。之后对点~冲q行的操作就是通过q个指针啦。到q里q要再提醒一下,对于q些接口指针Q在使用完毕后,一定要使用Release来释攑֮?BR> pSharedHandleQ设为NULLp了?BR> 得到一个指向IDirect3DVertexBuffer9的指针后Q顶点缓冲也创建完毕了。现在要做的是把之前保存在数组中的点信息攑֜点~冲区里面。首先,使用IDirect3DVertexBuffer9::Lock来锁定顶点缓冲区Q?BR> HRESULT Lock( UINT OffsetToLock, UINT SizeToLock, void **ppbData, DWORD Flags );
OffsetToLockQ指定要开始锁定的~冲区的位置。通常在v始位|?开始锁定?BR> SizeToLockQ指定在锁定的缓冲区的大。设?的话是表示要锁定整个缓冲区?BR> ppbDataQ用来保存返回的指向点~冲区的指针。通过q个指针来向点~冲区填充数据?BR> FlagsQ高U应用。通常设ؓ0?BR> 填充为顶点缓冲区后,使用IDirect3DDevice9::Unlock来解锁?BR> 最后在渲染的时候用IDirect3DDevice9::SetStreamSource来告诉D3D要渲染哪个顶点缓冲区里面的顶炏V?BR> HRESULT SetStreamSource( UINT StreamNumber, IDirect3DVertexBuffer9 *pStreamData, UINT OffsetInBytes, UINT Stride );
StreamNumberQ设|数据流的数量。顶点缓冲最多可以?6个数据流。确定所支持的数据流的数量,可以查D3DCAPS中的MaxStreams成员的倹{通常设ؓ0Q表CZ用单数据?BR> pStreamDataQ要与数据流l定的数据。在q里我们要把点~冲Z数据绑定?BR> OffsetInBytesQ设|从哪个位置开始读数据。设?表示从头读v?BR> StrideQ数据流里面数据单元的大。在q里是每个顶点的大小?BR>
索引~冲
很多时候,盔R的三角Ş会共用一些顶点,例如l成四方形的两个三角形就q了一条边Q即q了两个顶点信息。如果不使用索引Q我们需要六个顶点的信息来绘制这个四方ŞQ但实际上绘制一个四方Ş只要四个点信息p够了。如果用了索引׃一样了Q在点~冲区里我们可以只保存四个顶点的信息Q然后通过索引来读取顶点信息。要使用索引得先创徏一个烦引缓册Ӏ也许读到这里你会有个疑问,创徏一个烦引缓冲不更费内存I间了吗Q其实不Ӟ索引~冲区的元素保存的是数字Q一个数字所占用的内存肯定要比一个顶Ҏ占用的小得多啦。当你节省了几千个顶点,你就会发现浪贚w么一点点索引~冲区是很值得的?BR> 创徏索引~冲的函数是QIDirect3DDevice9::CreateIndexBuffer
HRESULT CreateIndexBuffer( UINT Length, DWORD Usage, D3DFORMAT Format, D3DPOOL Pool, IDirect3DIndexBuffer9** ppIndexBuffer );
LengthQ烦引缓冲区的长度。通常使用索引数目乘以sizeofQWORDQ或sizeof(DWORD)来设|,因ؓ索引L数据cd是字节(WORDQ或双字节(DWORDQ,嗯,一个WORD只有两个字节QDWORD也就只有四个字节Q比点的大小多了吧?BR> UsageQ和CreateVertexBuffer中的Usage讄一栗一般设??BR> FormatQ设|烦引格式。不是D3DFMT_INDEX16是D3DFMT_INDEX32的啦?BR> PoolQ又是和CreateVertexBuffer中的一栗一般设为D3DPOOL_DEFAULT?BR> ppIndexBufferQ指向IDirect3DIndexBuffer9的指针。操作烦引缓冲区靠它的啦。记得用完后要Release啊?BR> 和填充顶点缓冲区一P要填充烦引缓冲区Q要先用IDirect3DIndexBuffer9::Lock来锁定缓冲区?BR> HRESULT Lock( UINT OffsetToLock, UINT SizeToLock, void **ppbData, DWORD Flags );
是不是和IDirect3DVertexBuffer9::Lock一样呢Q具体说明也可以参照上面的内宏V填充完之后使用IDirect3DIndexBuffer9::UnLock来解锁?BR> 最后用IDirect3DDevice9::SetIndices来告诉设备要使用哪个索引?BR> HRESULT Setindices( IDirect3DindexBuffer9* pIndexData, UINT BaseVertexIndex );
pIndexDataQ设|用哪个烦引缓册Ӏ?BR> BaseVertexIndexQ设|以点~冲Z的哪个顶点ؓ索引0?BR> 有关点的知识就说到q了。一下章说说炏V线、三角Şq种D3D所支持的图元(drawing primitivesQ?BR>
D3D中的囑օ?BR> 在D3D中,一共有三种基本囑օQ分别是炏V线和三角Ş。点是最单的囑օQ由它可以构成一U叫点列Qpoint listQ的囑օcd。线是由两个不重合的Ҏ成的Q一些不相连的线l成的集合就叫线列(line listQ,而一些首q但不Ş成环路的U的集合叫U带Qline stripsQ。同理,单独的三角Ş集合叫三角形列Qtriangle listQ,cM于线带的三角形集合就叫三角Ş带(triangle stripsQ,另外Q如果多个三角Şq一个顶点作为它们的一个顶点的话,那么q个集合叫三角形扇Qtriangle fansQ。还是画图比较容易理解吧Q?BR>


q些囑օ有什么用呢?基本上我们可以用这些图元来L们想要的M物体。例如画一个四方Ş可以使用三角形带来画Q画一个圆则用三角Ş扇?BR> 现在介绍一U不需要顶点缓冲来渲染的方法,是使用IDirect3DDevice9::DrawPrimitiveUP函数。UP是User Pointer的意思,也即是说要用用户定义的内存I间?BR> HRESULT DrawPrimitiveUP( D3DPRIMITIVETYPE PrimitiveType, unsigned int PrimitiveCount, const void *pVertexStreamZeroData, unsigned int VertexStreamZeroStride );
PrimitiveTypeQ要l画的图元的U类。就是上面介l的那六U类型?BR> PrimitiveCountQ要l画的图元的数量。假设有n个顶点信息,l画的图元类型是点列的话Q那么图元的数量是nQ如果绘ȝ囑օcd是线列的话,那么囑օ的数量就是n/2Q如果是U带的话是n-1Q三角Ş列就是n/3Q三角Ş带就是n-2Q三角Ş扇出是n-2?BR> pVertexStreamZeroDataQ存储顶点信息的数组指针
VertexStreamZeroStrideQ顶点的大小
使用点~冲来绘d?/B>
很多时候我们用顶Ҏ定义囑Ş之后Q就把这些顶点信息放q顶点缓冲里面,然后再进行渲染。用点缓冲的好处以及如何创徏点~冲我已l在上一章已讲过了,现在讲讲怎么把顶点缓冲里面的囑օl画出来。其实也很简单,和上面的IDirect3DDevice9::DrawPrimitiveUP函数差不多,我们使用IDirect3DDevice9::DrawPrimitive函数。不q在使用q个函数之前Q我们得告诉讑֤我们使用哪个数据源,使用 IDirect3DDevice9::SetStreamSource函数可以讑֮数据源?BR> HRESULT SetStreamSource( UINT StreamNumber, IDirect3DVertexBuffer9 *pStreamData, UINT OffsetInBytes, UINT Stride );
StreamNumberQ设|和哪个数据梆定。如果用单数据的话,q里设ؓ0。最多支?6个数据流?BR> pStreamDataQ要l定的数据。也是我们创徏的顶点缓冲区里面的数据?BR> OffsetInBytesQ设|从哪个字节开始读赗如果要L个缓冲区里面的数据的话,q里设ؓ0?BR> StrideQ单个数据元素的大小。如果数据源是顶点缓冲的话,那么q里是每个点信息的大(Sizeof(vertex)Q?BR> 讄好数据源后,可以用IDirect3DDevice9::DrawPrimitive来绘M?BR> HRESULT DrawPrimitive( D3DPRIMITIVETYPE PrimitiveType, unsigned int StartVertex, unsigned int PrimitiveCount );
PrimitiveTypeQ要l画的图元的U类?BR> StarVertexQ?讄从顶点缓冲区中的W几个顶点画赗没有特D情况当然是x全部的顶点画出来啦,所以一般这里设|从0开始?BR> PrimitiveCountQ要l画的图元的数量?BR> 好了Q这章比较简单。写到这章的时候我才发现这不是入门手册Q有一些重要但是我觉得没必要讲的东西我都没有讲明。如果是新手看我写的q些东西Q搞不好q会被我qh了,呵呵。所以还是徏议大家看DXSDK里面的说明文档,虽然是英文的Q但是很详细Q我现在都还没有看完呢?BR> 嗯,前面四章把最基本的东西讲完了Q用前面的知识我们可以M些简单的静止囑Ş。下一章就开始讲矩阵了,它可以我们的图形动h?/FONT>
向量Q也叫矢量,英文叫vectorQ?/B>
向量是包含大小Q长度)和方向的一个量。向量有2l的Q也?l甚?l的。在DX的所有结构体中,有一个结构体是用来表C?l向量的Q它是 D3DVECTORQ这个结构体很简单,只有三个成员Qx、y、z。一般来_如果不涉及到向量q算的话Q用q个l构体来定义一个向量就可以了。我们可以它来表C方向以及顶点在3D世界中的位置{。如果你要对那些向量q行一些运的话,使用D3DVECTOR很不方便了Q因为在D3DVECTORq个l构体中没有重蝲M的运符Q如果想要做一个加法运,得分别对结构体中的每一个成员进行运了。嘿嘿,不用怕,在DX里面有个叫D3DX的东东(包含 d3dx.h头文ӞQ它里面定义了很多方便我们进行数学计的函数和结构。其中就有D3DXVECTOR2QD3DXVECTOR3Q?D3DXVECTOR4q三个结构体。看它们的名字就应该知道它们的作用了吧。对?l和4l的l构体这里就不讲了,其实它们也很单,?D3DXVECTOR3差不多。不q要说明一点的是D3DXVECTOR3是从D3DVECTORzq来的,说明它和D3DVECTOR一P有x?y、zq三个成员,除此之外QD3DXVECTOR3q重载了部分算术运符Q这h们就可以像对待整型那样对D3DXVECTOR3的对象进行加减乘除以及判断是否相{的q算了。同Ӟ׃D3DXVECTOR3是从D3DVECTORzq来的,所以两者的对象可以互相赋|在这两种cd中随便{换?BR> q是单说一下向量的数学q算吧。矢量的加减法很单,是分别把两个向量的各个分量作加减运。向量的乘除法也很简单,它只能对一个数D行乘除法Q运的l果是向量中的各个分量分别寚w个数D行乘除法后得出的l果。向量的模就是向量的长度Q就是各个分量的qx的和的开斏V向量的标准化就是得向量的模ؓ1Q这对在3D世界中实现光照是很有用的。对于向量的q算Q还有两个“乘法”,那就是点乘和叉乘了。点乘的l果是两个向量的模怹Q然后再与这两个向量的夹角的余u值相乘。或者说是两个向量的各个分量分别怹的结果的和。很明显Q点乘的l果是一个数Q这个数Ҏ们分析这两个向量的特点很有帮助。如果点乘的l果?Q那么这两个向量互相垂直Q如果结果大?Q那么这两个向量的夹角小?0度;如果l果于0Q那么这两个向量的夹角大?0 度。对于叉乘,它的q算公式令h头晕Q我׃说了Q大家看下面的公式自己领悟吧…?BR> //v3 = v1 X v2 v3.x = v1.y*v2.z ?v1.z*v2.y v3.y = v1.z*v2.x ?v1.x*v2.z v3.z = v1.x*v2.y ?v1.y*v2.x
是不是很难记啊,如果暂时C了就了。其实我们主要还是要知道叉乘的意义。和点乘的结果不一P叉乘的结果是一个新的向量,q个新的向量与原来两个向量都垂直Q至于它的方向嘛Q不知大家是否还记得左手定则。来QZ的左手,按照W一个向量(v1Q指向第二个向量Qv2Q弯曲你的手掌,q时你的拇指所指向的方向就是新向量Qv3Q的方向了。通过叉乘Q我们很Ҏ得到某个^面(׃个向量决定的Q的法线了?BR> l于写完了上面的文字Q描q数学问题可真是费劲Q自己又不愿意画图,辛苦大家了。如果你觉得上面的文字很枯燥Q那也没关系。因Z面的不是重点Q下面介l的函数才是希望大家要记住的?BR> D3DX中有很多很有用的函数Q它们可以帮助我们实C面所讲的所有运。不q下面我只说和D3DXVECTOR3有关的函敎ͼ
计算点乘QFLOAT D3DXVec3DotQ?BR> CONST D3DXVECTOR3* pV1, CONST D3DXVECTOR3* pV2Q?
计算叉乘QD3DXVECTOR3* D3DXVec3CrossQ?BR> D3DXVECTOR3* pOut, CONST D3DXVECTOR3* pV1, CONST D3DXVECTOR3* pV2Q?
计算模:FLOAT D3DXVec3Length( CONST D3DXVECTOR3* pV)
标准化向量:D3DXVECTOR3* D3DXVec3Normalize( D3DXVECTOR3* pOut, CONST D3DXVECTOR3 pV)
对于D3DXVECTOR3的加减乘除运,上面已经讲了Q用+ - * / p了?BR>
矩阵与矩阵运?/B>
什么是矩阵Q这个概念还真不好解释,不过学过U性代数的定都知道矩阵长什么样Q那我在q里׃解释了。在D3D中,定义矩阵的结构体是D3DMATRIXQ?BR> typedef struct _D3DMATRIX { union { struct { float _11, _12, _13, _14; float _21, _22, _23, _24; float _31, _32, _33, _34; float _41, _42, _43, _44; }; float m[4][4]; }; } D3DMATRIX;
看这个结构的样子Q你应该很清楚怎么使用它来定义一个矩阵了吧。在q里我顺便说一下C++中union的特性吧。像上面定义的结构体所C,?union里面有两个部分,一个是l构体,另一个是二维数组Q它?6个元素。在union中,所有的成员都是q一个内存块的,q是什么意思呢Ql看上面的代码,l构体中的成员_11和成员m数组的第一个元素是q一个内存空_卛_们的值是一LQ你对_11赋值的同时也对m[0][0]q行了赋|_11和m[0][0]的值是一L。这h什么好处呢Q比如你定义了一个矩阵变量D3DMATRIX mat;你想讉K矩阵中第三行W四列的元素Q可以这样做Qmat._34Q另外也可以q样:mat.m[2][3]Q数l是从位|?开始储存的哦)。看h使用后者比较麻烦,不过当你把中括号里面的数换成i和jQ用mat.m[i][j]来访问矩阵中的元素,你就应该知道它的好处了吧?BR> 实际上直接用D3DMATRIX的情况不多,因ؓ在D3DX中有个更好的l构体,那就是D3DXMATRIX。和D3DXVECTOR3怼Q?D3DXMATRIX是从D3DMATRIXl承q来的,它重载了很多q算W,使得矩阵的运很单。矩늚q算Ҏ我不打算多说了,下面只介l和矩阵性质有关的三个函数?BR> 产生一个单位矩阵:D3DXMATRIX *D3DXMatrixIdentity( D3DXMATRIX *pout);//q回l果
求{|矩阵:D3DXMATRIX *D3DXMatrixTranspose( D3DXMATRIX *pOut,//q回的结?BR> CONST D3DXMATRIX *pM );//目标矩阵
求逆矩阵:D3DXMATRIX *D3DXMatrixInverse( D3DXMATRIX *pOut,//q回的结?BR> FLOAT *pDeterminant,//设ؓ0 CONST D3DXMATRIX *pM );//目标矩阵
至于什么是单位矩阵Q什么是转置矩阵Q什么是逆矩阉|׃说了Q可以看一下线性代数的书,一看就明白了。简单的加减乘除法可以用D3DXMATRIXl构体里面重载的q算W。两个矩늛乘也可以用函数来实现Q这在接下来的矩阵变换中讲到?BR>
矩阵变换
矩阵的基本变换有三种Q^U,旋{和羃放?BR> q移Q?BR>D3DXMATRIX *D3DXMatrixTranslation( D3DXMATRIX* pOut,//q回的结?BR> FLOAT x, //X轴上的^U量 FLOAT y, //Y轴上的^U量 FLOAT z) //Z轴上的^U量
lX轴旋转: D3DXMATRIX *D3DXMatrixRotationX( D3DXMATRIX* pOut, //q回的结?BR> FLOAT Angle //旋{的弧?BR>);
lY轴旋转: D3DXMATRIX *D3DXMatrixRotationY( D3DXMATRIX* pOut, //q回的结?BR> FLOAT Angle //旋{的弧?BR>);
lZ轴旋转: D3DXMATRIX *D3DXMatrixRotationZ( D3DXMATRIX* pOut, //q回的结?BR> FLOAT Angle //旋{的弧?BR>);
l指定u旋{Q?BR>D3DXMATRIX *D3DXMatrixRotationAxis( D3DXMATRIX *pOutQ?/q回的结?BR> CONST D3DXVECTOR3 *pVQ?/指定轴的向量 FLOAT Angle//旋{的弧?BR>);
~放Q?BR>D3DXMATRIX *D3DXMatrixScaling( D3DXMATRIX* pOut, //q回的结?BR> FLOAT sx, //X轴上~放的量 FLOAT sy, //Y轴上~放的量 FLOAT sz //Z轴上~放的量 );
好了Q这章就写这么一些东ѝ如果你觉得好像没学C么的话,可能是因Z知道上面的知识有什么用吧。下一章我介l世界空间、视囄_也叫摄像机空_以及投媄Q这三者对应的是世界矩c视囄阵和投媄矩阵。搞清楚q三个空间的作用后,我们可以利用这章的知识使我们的3D世界动v来了?BR>
无论计算机图形技术如何发展,只要它以二维的屏q作为显CZ质,那么它显C的囑փ即多么的有立体感,也还是二l的。有时我会想Q有没有以某个空间作为显CZ质的的可能呢Q不q即使有Q也只能是显C某个范围内的图像,不可能有无限大的I间作ؓ昄介质,如果有,那就是现实世界了?BR> 既然昄器的屏幕是二l的Q那么我们就要对囑փ作些处理Q让它可以欺骗我们的眼睛Q生一U立体的真实感。在D3D中,q种处理是一pd的空间变换,从模型空间变C界空_再变到视囄_最后投影到我们的显C器屏幕上?BR>
世界I间与世界矩?/B>
什么是模型I间呢?每个模型Q?D物体Q都有它自己的空_I间的中心(原点Q就是模型的中心。在模型I间里,只有模型上的不同Ҏ位置的相对关pR那什么是世界I间呢?世界是物体Q模型)所存在的地斏V当我们把一个模型放q世界里面去Q那么它有了一个世界坐标,q个世界坐标是用来标C界中不同的模型所处的位置的。在世界I间里,世界的中心就是原点(0, 0, 0Q,也就是你昄器屏q中间的那一炏V我们可以在世界I间里摆攑־多个模型Qƈ且设|它们在世界I间中的坐标Q这h型与模型之间有了相对的位置?BR> 世界矩阵有什么用呢?我们可以利用它来改变世界I间的坐标。这P在世界空间里面的模型可以移动、旋转和~放了?BR> 我们可以使用上一章末所讲的那几个函数来产生世界矩阵。例如生一个绕X轴旋转的转阵QD3DXMatrixRotationX(&matrix,1)。利用matrixq个矩阵Q就可以使世界空间中的物体绕X轴{?弧度?BR> 可以l合后面的例子来理解世界矩阵?BR>
视图I间与视囄?/B>
世界I间建立h后,我们不一定能看到模型Q因为我们还没有“眼睛”啊。在视图I间里,我们可以建立我们在三l空间中的眼睛:摄像机。我们就是通过q个虚拟的摄像机来观察世界空间中的模型的。所以视囄间也叫摄像机I间?BR> 要徏立vq个虚拟的摄像机Q我们需要一个视囄阵,产生视图矩阵的一个函数是Q?
D3DXMATRIX *D3DXMatrixLookAtLH( D3DXMATRIX* pOut, CONST D3DXVECTOR3* pEye, CONST D3DXVECTOR3* pAt, CONST D3DXVECTOR3* pUp );
pOutQ返回的视图矩阵指针
pEyeQ设|摄像机的位|?BR> pAtQ设|摄像机的观察点
pUpQ设|方向“上?BR> q个函数的后~LH是表C左手系的意思,聪明的你一定能够猜定有个叫D3DXMatrixLookAtRH的函数。至于左手系和右手系的区别,q里׃多说了,C左手pM的Z正方向是指向昄器里面的p了。只能弄懂了视图矩阵的含义,建立视图矩阵完成可以不依赖函敎ͼ自己手动完成。视囄阵其实就是定义了摄像机在世界I间中的位置、观察点、方向“上”这些信息?BR> 可以l合后面的例子来理解视图矩阵?BR>
投媄与投q?/B>
定义投媄矩阵很像是定义摄像机的镜_下面看它的函数声明:
D3DXMATRIX *D3DXMatrixPerspectiveFovLH( D3DXMATRIX* pOut, FLOAT fovY, FLOAT Aspect, FLOAT zn, FLOAT zf );
pOutQ返回的投媄矩阵指针
fovYQ定义镜头垂直观察范_以弧度ؓ单位。对于这个参敎ͼ下面是我的理解:如果定义为D3DX_PI/4Q?0度角Q,那么是表示以摄像机的观察方向ؓq_U,上方45度角和下?5度角是摄像机所能看到的垂直范围了。嗯Q可以想象一下自q眼睛Q如果可以把自己眼睛的fovYD?D3DX_PI/2Q?80度角Q,那么我们可以不用抬头就看得见头的东西了。如果设为D3DX_PI的话。。。我先编译一下试试(building…)。哈哈,l果啥也看不见。很难想象如果自p同时看到所有方向的物体Q那么将是一个怎样的画面啊?BR> AspectQ设|纵横比。如果定义ؓ1Q那么所看到的物体大不变。如果定义ؓ其它|你所看到的物体就会变形。不q一般情况下q个D为显C器屏幕的长宽比。(l于明白Z么有些h会说电视上的自己看v来会比较胖了……)
znQ设|摄像机所能观察到的最q距?BR> zfQ设|摄像机所能观察到的最q距?BR> ·一段代码
L以下代码片段Q?BR> D3DXMATRIXA16 matWorld; D3DXMatrixIdentity( &matWorld ); D3DXMatrixRotationX( &matWorld, timeGetTime()/1000.0f ); g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f ); D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f ); D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f ); D3DXMATRIXA16 matView; D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec ); g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); D3DXMATRIXA16 matProj; D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/2, 1.0f, 1.0f, 500.0f ); g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
通过上面三个转换Q就建立了一个我们可以通过昄器屏q来观察?D世界。上面三个{换分别是Q?BR> 从模型空间到世界I间的世界{换:SetTransform( D3DTS_WORLD, &matWorld )?BR> 从世界空间到视图I间的视图{换:SetTransform( D3DTS_VIEW, &matView )?BR> 从视囄间到到屏q的投媄转换QSetTransform( D3DTS_PROJECTION, &matProj )?BR> 现在来观察matWorldQmatViewQmatProjq三个矩늚特点。我们用D3DXMatrixRotationX函数来生了一个绕 X轴旋转的转换矩阵Q通过讄世界转换Q在世界I间里面的物体将lX轴作旋{。然后我们定义了三个三维的向量,用来讄摄像机的位置Q观察方向和定义方向 “上”。用D3DXMatrixLookAtLH函数来把q三个向量放q视囄阵里面去。然后通过讄视图转换Q我们就建立了一个虚拟的摄像机。最后通过D3DXMatrixPerspectiveFovLH函数Q我们得C个投q阵,用来讄虚拟摄像机的镜头?BR> 我还是解释一下上面说的那个方向“上”是什么东西吧。这个“上”其实指的就是摄像机在刚建立的时候是如何摆放的,是向左边侧着摆,q是向右边侧着摆,q是倒过来摆Q都是通过q个方向“上”来指定的。按照正常的理解Q摄像机的“上”方向就是Y轴的正方向,但是我们可以指定方向“上”ؓY轴的负方向,q样世界建立h后就是颠倒的了。不q颠倒与否,也是相对来说的了Q试问在没有引力的世界中Q谁能说出哪是上哪是下呢Q是不是看得一头雾水啊Q只要自׃手改变一下这些参敎ͼ可以体会到了?BR> 讄上面三个转换的先后顺序ƈ不一定得按照世界到视囑ֈ投媄q个序Q不q习惯上按照q种序来写Q感觉会好一炏V?BR>
使用矩阵怹来创Z界矩?/B>
在世界空间中的物体运动往往是很复杂的,比如物体自n旋{的同Ӟq绕世界的原Ҏ转。怎么实现q种q动呢?通过矩阵怹来把两个矩阵“”在一赗现在我们假设某一物体建立在世界的原点上,看以下代码:
//定义三个矩阵 D3DXMATRIX matWorld, matWorldYQmatMoveLeft;
//一个矩阉|物体Ud(30,0,0)处,一个矩阵物体l原点(0,0,0Q旋?BR>D3DXMatrixTranslation(&matMoveRight,30,0,0); D3DXMatrixRotationY(&matWorldY, radian/1000.0f);
//W一ơ矩늛乘。先旋{Q再q移 D3DXMatrixMultiply(&matWorld, &matWorldY, &matMoveRight);
//W二ơ矩늛乘。在W一ơ矩늛乘的l果上,再以Y轴旋?BR>D3DXMatrixMultiply(&matWorld, &matWorld, &matWorldY);
//讄世界矩阵 m_pD3DDevice->SetTransform( D3DTS_WORLD, &matWorld );
矩阵怹的时候,矩阵的先后顺序很重要Q如果顺序弄错了Q物体就不会按我们预料的那样q动。从最后一ơ矩늛乘看P最后相乘的两个矩阵?matWorld和matWorldYQ其中matWorld又是由matWorldY和matMoveRight怹得来的,那么q三个矩늛乘的序是(matWorldY,matMoveRight,matWorldY)。这个顺序意味着什么呢Q第一个matWorldY使物体绕Y轴旋转,q时候的物体q处于原点,所以它lY轴旋转也是l自w的旋{。它转呀转呀Q这时候matMoveRight来了Q它把物体从Q?Q?Q?Q移CQ?0Q?Q?0Q,q时候物体就不再是绕Y轴旋转了Q它是在Q?0Q?Q?Q这个位|l绕自n旋{。然后matWorldY又来了,它物体再次以Y轴旋转,不过此时物体不在原点了,所以物体就以原点ؓ中心作画圆的q动Q它自n的旋转仍在l)Q这个圆的半径是30。如果换一个顺序,把matMoveRight攑֜W一的话Q那么就是先Ud再旋转再旋{Q第二次旋{没用Q,q时候物体就只是dq动而已Q它自n没有旋{。如果把matMoveRight攑֜最后,那么是先旋转再旋{Q第二次旋{没用Q再UdQ这时候物体就没有作画圆运动了Q它只是在(30Q?Q?Q这个位|上作自w旋转。好了,理解q个需要一点点惌力。你可以先写好几个矩늛乘的序Q自己想象一下相乘的l果会物体作什么运动,然后再编译执行程序,看看物体的运动是不是和自己想像中的一Pq样可以ȝ自己的空间思维能力?BR> 好了Q又写完一章了。下一章可能要q一些日子才能写。因p没找到工作,国庆q后得出发L工了Q接下来的日子要作一些找工前的准备,所以就没什么时间l写了。至于什么时候写W七,呵呵Q应该不用很久,扑ֈ工作后立d来这里报道~~大家我好运吧^_^ |