现在的位置: 首页 > 综合 > 正文

DirectX9.0 入门手册

2013年02月18日 ⁄ 综合 ⁄ 共 10588字 ⁄ 字号 评论关闭

    下面正式开始,先讲窗口类,创建窗口,销毁窗口,窗口消息处理函数. 

·窗口类WNDCLASS
struct WNDCLASS {
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;
    LPCSTR      lpszClassName;
};
style用来定义窗口的行为。如果打算共同使用GDID3D的话,可以使用CS_OWNDC作为参数。
lpfnWndProc一个函数指针,指向与这个窗口类绑定在一起的处理窗口消息的函数。
cbClsExtracbWndExtra为窗口和为分配内存空间。很少使用到这两个参数,一般设为0
hInstance应用程序的实例句柄。你可以使用GetModuleHandle()来得到它,也可以从Win32程序的入口函数WinMain那里得到它。当然,你也可以把它设为NULL(不知有什么用)
hIconhCursorhbrBackground设置默认的图标、鼠标、背景颜色。不过在这里设置这些其实并不怎么重要,因为我们可以在后面定制自己的渲染方法。
lpszMenuName用来创建菜单
lpszClassName窗口类的名字。我们可以通过这个名字来创建以这个窗口类为模板的窗口。甚至可以通过这个名字来得到窗口的句柄。
设置好窗口类结构的内容后,使用RegisterClass(const WNDCLASS *lpWndClass)函数来注册它。关闭窗口后可以用UnregisterClass(LPCSTR lpClassName, HINSTANCE hInstance)来撤销注册。
·创建窗口CreateWindow
HWND CreateWindow(
   LPCTSTR lpClassName,
   LPCTSTR lpWindowName,
   DWORD dwStyle,
   int x, y,
   int nWidth, nHeight,
   HWND hWndParent,
   HMENU hMenu,
   HINSTANCE hInstance,
   LPVOID lpParam
);
lpClassName窗口类的名字。即窗口类结构体中的lpszClassName成员。
lpWindowName如果你的应用程序有标题栏,这个就是你标题栏上显示的内容。
dwStyle窗口的风格决定你的窗口是否有标题栏、最大最小化按钮、窗口边框等属性。在全屏的模式下,WS_POPUP|WS_VISIBLE是常用的设置,因为它产生一个不带任何东西的全屏窗口。在窗口的模式下,你可以设置很多窗口的风格,具体可以查看相关资料,这里不详细说明,不过WS_OVERLAPPED|WS_SYSMENU|WS_VISIBLE是一组常用的风格。
xy窗口创建的位置。(xy)表示窗口的左上角位置。
nWidthnHeight用来设置窗口的宽度和高度,以像素为单位。如果你想创建一个全屏的窗口,使用GetSystemMetrics(SM_CXSCREEN)GetSystemMetrics(SM_CYSCREEN)可以得到当前显示器屏幕的大小
hWndParent指定这个新建窗口的父窗口。在D3D应用程序中很少用,一般设为NULL
hMenu菜单句柄。
hInstance应用程序的实例句柄。你可以使用GetModuleHandle()来得到它,也可以从Win32程序的入口函数WinMain那里得到它。当然,你也可以把它设为NULL(不知有什么用)
lpParam一个很神秘的参数。除非你知道自己在做什么,否则还是把它设为NULL吧。
·销毁窗口DestroyWindow
       销毁窗口有两种方法,一种是隐式的,一种是显式的。我们都知道Windows操作系统是一个基于消息驱动的系统。流动于系统中的消息使我们的窗口跑起来。在很多软件开发特别是商业软件的开发过程中,窗口的产生和销毁都是交由系统去做的,因为这些不是这类开发的关注所在。但是游戏开发不一样,尽管你也可以只向系统发送一条WM_DESTROY消息来销毁窗口,我们还是希望窗口是销毁的明明白白的。由于窗口的注册、产生和使用都是由我们亲手来做的,那么当然窗口的销毁也得由我们亲自来做。不过还是得说明一点,使用WM_DESTROY消息和DestroyWindow函数来销毁窗口在本质上并无太大差别,使用哪种方法可以说是根据个人的爱好吧。
       销毁窗口后是不是就完事了呢?不,还没有,因为应用程序的消息队列里可能还有没处理完的消息,为了彻底的安全,我们还得把那些消息都处理完。所以结束应用程序的时候,可以使用以下方法:
       MSG msg;
       DestroyWindow(h_wnd);
       while(PeekMessage(&msg , NULL , 0 , 0 , PM_REMOVE))
       {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
       }
·窗口消息处理过程
       窗口消息的处理函数是一个回调函数,什么是回调函数?就是由操作系统负责调用的函数。CALLBACK这个宏其实就是__stdcall,这是一种函数调用的方式,在这里不多说这些了,有兴趣的可以参考一些Windows编程的书籍,里面会有很详尽的说明。
       Windows里面有很多消息,这些消息都跑去哪里了呢?其实它们都在自己的消息队列里等候。消息是怎么从队列里出去的呢?就是通过GetMessagePeekMessage这两个函数。那么消息从队列里出去后又到哪里了呢?嗯,这时候消息就正式进入了我们的窗口消息处理过程,也即是窗口类中lpfnWndProc所指定的函数。一个消息处理函数有四个参数,下面分别说说:
       参数1HWND p_hWnd
       消息不都是传到以窗口类为模板产生的窗口吗?为什么还要使用窗口句柄来指明窗口呢?别忘了一个窗口类是可以产生多个窗口的呀,如果一个应用程序里面有多个窗口,并且它们之中的一些窗口是共用一个窗口类的,那么就得用一个窗口句柄来指明究竟这个消息是哪个窗口发过来的。
       参数2UINT p_msg
       这是一个消息类型,就是WM_KEYDOWN , WM_CLOSE , WM_TIMER这些东东。
       参数3WPARAM p_wparam
       这个参数内容就是消息的主要内容。如果是WM_KEYDOWN消息,那么p_wparam就是用来告诉你究竟是哪个键被按下。
       参数4LPARAM p_lparam
       这个参数的内容一般是消息的一些附加内容。
       最后说明一下DefWindowProc的作用。有时候我们把一个消息传到窗口消息处理函数里面,但是里面没有处理这个消息的内容。怎么办?很容易,交给DefWindowProc处理就对了。
 ·创建IDirect3D接口
DirectX是一组COM组件,COM是一种二进制标准,每一个COM里面提供了至少一个接口,而接口就是一组相关的函数,我们使用DirectX,其实就是使用那些函数。COMC++中的类有点像,只不过COM使用自己的方法来创建实例。创建COM实例的一般方法是使用coCreateInstance函数。有关coCreateInstance的使用方法,可以参考有关COM方面的资料,这里暂时不详细说明了,因为DirectX提供了更简洁的方法来创建DirectX组件的实例。这一章我要讲的就是Direct3D组件的使用方法。
       为了使用D3D中的函数,我们得先定义一个指向IDirect3D9这个接口的指针,顺便说明一下,其实接口也是一个指针,所以我们定义的就是一个指向指针的指针,也即二重指针,为什么要使用二重指针呢,我暂时还不是很懂,所以先留着这个疑问吧^_^。定义完这个接口指针后,例如IDirect3D9 *g_pD3D;现在我们使用Direct3DCreate9这个函数来创建一个D3D接口:
       g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
       Direct3DCreate9这个函数只有一个参数,它表明要创建接口的版本。如果你想创建一个老的接口版本当然也可以,不过没有人会那样做吧。
       创建接口后就可以创建D3D设备了,什么是D3D设备?你可以想象为你机上的那块显卡!什么?你有几块显卡!!没关系,那就创建多几个D3D设备接口吧。创建D3D设备需要的参数很多,如果把那些参数都挤在一个函数里面,那就太长了,所以就把一些参数放进结构体里面,只要先设定好这些结构体,再把这些结构体当作参数传给创建D3D设备的函数,那就清晰多了。首先要讲的就是D3DPRESENT_PARAMETERS这个结构。下面是它的定义:
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;
};
        BackBufferWidthBackBufferHeight后备缓冲的宽度和高度。在全屏模式下,这两者的值必需符合显卡所支持的分辨率。例如(800600),(640480)。
       BackBufferFormat后备缓冲的格式。这个参数是一个D3DFORMAT枚举类型,它的值有很多种,例如D3DFMT_R5G6B5,这说明后备缓冲的格式是每个像素16位,其实红色(R)占5位,绿色(G)占6位,蓝色(B)占5位,为什么绿色会多一位呢?据说是因为人的眼睛对绿色比较敏感。DX9只支持16位和32位的后备缓冲格式,24位并不支持。如果对这D3DFORMAT不熟悉的话,可以把它设为D3DFMT_UNKNOWN,这时候它将使用桌面的格式。
       BackBufferCount后备缓冲的数目,范围是从03,如果为0,那就当成1来处理。大多数情况我们只使用一个后备缓冲。使用多个后备缓冲可以使画面很流畅,但是却会造成输入设备响应过慢,还会消耗很多内存。
       MultiSampleTypeMultiSampleQuality这两个参数可以使你的渲染场景变得更好看,但是却消耗你很多内存资源,而且,并不是所有的显卡都支持这两者的所设定的功能的。在这里我们分别把它们设为D3DMULTISAMPLE_NONE0
       SwapEffect交换缓冲支持的效果类型。它是D3DSWAPEFFECT枚举类型,可以设定为以下三者之一:D3DSWAPEFFECT_DISCARDD3DSWAPEFFECT_FLIPD3DSWAPEFFECT_COPY。如果设定为D3DSWAPEFFECT_DISCARD,则后备缓冲区的东西被复制到屏幕上后,后备缓冲区的东西就没有什么用了,可以丢弃(discard)了。如果设定为D3DSWAPEFFECT_FLIP,则表示在显示和后备缓冲之间进行周期循环。设定D3DSWAPEFFECT_COPY的话,我也不太清楚有什么作用*^_^*。一般我们是把这个参数设为D3DSWAPEFFECT_DISCARD
       hDeviceWindow显示设备输出窗口的句柄
       Windowed如果为FALSE,表示要渲染全屏。如果为TRUE,表示要渲染窗口。渲染全屏的时候,BackBufferWidthBackBufferHeight的值就得符合显示模式中所设定的值。
       EnableAutoDepthStencil如果要使用Z缓冲,则把它设为TRUE
       AutoDepthStencilFormat如果不使用深度缓冲,那么这个参数将没有用。如果启动了深度缓冲,那么这个参数将为深度缓冲设定缓冲格式(和设定后备缓冲的格式差不多)
       Flags可以设置为0D3DPRESENTFLAG_LOCKABLE_BACKBUFFER。不太清楚是用来做什么的,看字面好像是一个能否锁定后备缓冲区的标记。
       FullScreen_RefreshRateInHz显示器的刷新率,单位是HZ,如果设定了一个显示器不支持的刷新率,将会不能创建设备或发出警告信息。为了方便,一般设为D3DPRESENT_RATE_DEFAULT就行了。
       PresentationInterval如果设置为D3DPRENSENT_INTERVAL_DEFAULT,则说明在显示一个渲染画面的时候必要等候显示器刷新完一次屏幕。例如你的显示器刷新率设为80HZ的话,则一秒内你最多可以显示80个渲染画面。另外你也可以设置在显示器刷新一次屏幕的时间内显示1到4个画面。如果设置为D3DPRENSENT_INTERVAL_IMMEDIATE,则表示可以以即时的方式来显示渲染画面,虽然这样可以提高帧速(FPS),但是却会产生图像撕裂的情况。
·创建IDirect3DDevice接口
       当你把D3DPRESENT_PARAMETERS的参数都设置好后,就可以创建一个D3D设备了,和创建D3D接口一样,先定义一个接口指针IDirect3DDevice9 * g_pD3DDevice;然后使用D3D接口里面的CreateDevice函数来创建设备。CreateDevice的声明为:
       HRESULT CreatDevice(
              UINT Adapter,
              D3DDEVTYPE DeviceType,
              HWND hFocusWindow,
              DWORD BehaviorFlags,
              D3DPRESENT_PARAMETERS *pPresentationParameters,
              IDirect3DDevice9** ppReturnedDeviceInterface
       };
       第一个参数说明要为哪个设备创建设备指针,我之前说过一台机可以有好几个显卡,这个参数就是要指明为哪块显卡创建可以代表它的设备指针。但是我怎么知道显卡的编号呢?可以使用D3D接口里面的函数来获得,例如GetAdapterCounter可以知道系统有几块显卡;GetAdapterIdentifier可以知道显卡的具体属性。一般我们设这个参数为D3DADAPTER_DEFAULT
       第二个参数指明正在使用设备类型。一般设为D3DEVTYPE_HAL
       第三个参数指明要渲染的窗口。如果为全屏模式,则一定要设为主窗口。
       第四个参数是一些标记,可以指定用什么方式来处理顶点。
       第五个参数就要用到上面所讲的D3DPRESENT_PARAMETERS
       第六个参数是返回的接口指针。
·开始渲染
       有了设备接口指针,就可以开始渲染画面了。渲染是一个连续不断的过程,所以必定要在一个循环中完成,没错,就是第一章讲的那个消息循环。在渲染开始之前我们要用IDirect3DDevice9::Clear函数来清除后备缓冲区。
HRESULT Clear(
   DWORD Count,
   const D3DRECT *pRects,
   DWORD Flags,
   D3DCOLOR Color,
   float Z,
   DWORD Stencil
);
        Count说明你要清空的矩形数目。如果要清空的是整个客户区窗口,则设为0
       pRects这是一个D3DRECT结构体的一个数组,如果count中设为5,则这个数组中就得有5个元素。
       Flags一些标记组合。只有三种标记:D3DCLEAR_STENCIL , D3DCLEAR_TARGET , D3DCLEAR_ZBUFFER
       Color清除目标区域所使用的颜色。
       float设置Z缓冲的Z初始值。小于或等于这个Z初始值的Z值才会被改写,但它的值只能取01之间。如果还不清楚什么是Z缓冲的话,可以自己找相关资料看一下,这里不介绍了,呵呵。
       Stencil设置模板缓冲的初始值。它的取值范围是02n次方减1。其中n是模板缓冲的深度。
       清除后备缓冲区后,就可以对它进行渲染了。渲染完毕,使用Present函数来把后备缓冲区的内容显示到屏幕上。
HRESULT Present(
   const RECT *pSourceRect,
   const RECT *pDestRect,
   HWND hDestWindowOverride,
   const RGNDATA *pDirtyRegion
);
        pSourceRect你想要显示的后备缓冲区的一个矩形区域。设为NULL则表示要把整个后备缓冲区的内容都显示。
       pDestRect表示一个显示区域。设为NULL表示整个客户显示区。
       hDestWindowOverride你可以通过它来把显示的内容显示到不同的窗口去。设为NULL则表示显示到主窗口。
       pDirtyRegion高级使用。一般设为NULL
  ·顶点属性与顶点格式
顶点可谓是3D世界中的基本元素。在计算机所能描绘的3D世界中,任何物体都是由多边形构成的,可以是三边形,也可以是四边形等。由于三边形,即三角形所具有的特殊性质决定其在3D世界中得到广泛的使用。构成三角形需要三个点,这些点的性质就是这章所要讲的内容。
       也许你已经知道顶点的结构定义,你可能会奇怪为什么D3D会知道我们“随便”定义的那些结构呢?其实那些顶点的定义可不是那么随便的哦。下面列举在Direct3D中,顶点所具有的所有属性。
       1)位置:顶点的位置,可以分别指定x,y,x三个值,也可以使用D3DXVECTOR3结构来定义。
       2RHW:齐次坐标W的倒数。如果顶点为变换顶点的话,就要有这个值。设置这个值意味着你所定义的顶点将不需要Direct3D的辅助(不能作变换、旋转、放大缩小、光照等),要求你自己对顶点数据进行处理。至于W是什么,WXYZ一样,只是一个四元组的一部分。RHW的英文是Reciprocal of the Homogenous W,即1/W,它是为了处理矩阵的工作变得容易一些(呼,线性代数的东东快都忘了,要恶补一下才行)。一般设RHW的值为1.0
       3)混合加权:用于矩阵混合。高级应用,这里不讲了(其实我不会,^_^
       4)顶点法线:学过高等数学就应该知道法线是什么吧?在这里是指经过顶点且和由顶点引出的边相垂直的线,即和三角形那个面垂直。用三个分量来描述它的方向,这个属性用于光照计算。
       5)顶点大小:设定顶点的大小,这样顶点就可以不用只占一个像素了。
       6)漫反射色:即光线照射到物体上产生反射的着色。理解这个比较麻烦,因为3D光照和真实光照没什么关系,不能像理解真实光照那样去理解3D光照。
       7)镜面反射色:它可以让一个3D物体的表面看起来很光滑。
       8)纹理坐标:如果想要在那些用多边形组成的物体上面贴上纹理,就要使用纹理坐标。由于纹理都是二维的,所以用两个值就可以表示纹理上面某一点的位置。在纹理坐标中,只能在0.01.0之间取值。例如(0.0 , 0.0)表示纹理的左上角,(1.0 , 1.0)表示纹理的右下角。
       好了,请记住上面属性的顺序。我们定义一个顶点结构的时候,不一定要包括全部的属性,但是一定要按照上面的顺序来定义。例如:
       struct MYVERTEX
{
              D3DXVECTOR3 position;
              float rhw;
              D3DCOLOR color;
       }
       上面定义了一个有漫反射色的变换顶点。
       定义完了顶点的结构后,我们就要告诉D3D我们定义的是什么格式。为了方便,我们通常会用#define来定义一个叫做描述“灵活顶点格式”(FVFFlexible Vertex Format)的宏。例如:#define MYFVF D3DFVF_XYZ | D3DFVF_NORMAL。根据之前定义的顶点属性结构体,我们要定义相对应的宏。假如顶点结构中有位置属性,那么就要使用D3DFVF_XYZ;如果是变换顶点的话,就要使用D3DFVF_XYZRHW;如果使用了漫反射色属性的话,就要使用D3DFVF_DIFFUSE。这些值是可以组合使用的,像上面那样用“|”符号作为连结符。定义完灵活顶点格式后,使用IDirect3DDevice9::SetVertexShader函数来告诉D3D我们所定义的顶点格式,例如:g_pD3DDevice->SetVertexShader( MYFVF );
·顶点缓冲
       处理顶点信息的地方有两个,一个是在数组里,另一个是在D3D所定义的顶点缓冲里。换个说法的话就是一个在我们所能直接操作的内存里,另一个在D3D管理的内存里。对于我们这些对操作系统底层了解不多的菜鸟来说,直接操作内存实在是太恐怖了,所以还是交给D3D帮我们处理吧,虽然不知道背后有些什么操作。要想把顶点信息交给D3D处理,我们就要先创建一个顶点缓冲区,可以使用IDirect3DDevice9->CreateVertexBuffer,它的原型是:
HRESULT CreateVertexBuffer(
   UINT Length,
   DWORD Usage,
   DWORD FVF,
   D3DPOOL Pool,
   IDirect3DVertexBuffer9** ppVertexBuffer,
   HANDLE* pSharedHandle
);
       Length缓冲区的长度。通常是顶点数目乘以顶点大小,使用Sizeof( MYVERTEX )就可以知道顶点的大小了。
       Usage高级应用。设为0就可以了。
       FVF就是我们之前定义的灵活顶点格式。
       Pool告诉D3D将顶点缓冲存储在内存中的哪个位置。高级应用,通常可取的三个值是:D3DPOOL_DEFAULTD3DPOOL_MANAGEDD3DPOOL_SYSTEMMEM。多数情况下使用D3DPOOL_DEFAULT就可以了。
       ppVertexBuffer返回来的指向IDirect3DVertexBuffer9的指针。之后对顶点缓冲进行的操作就是通过这个指针啦。到这里还要再提醒一下,对于这些接口指针,在使用完毕后,一定要使用Release来释放它。
       pSharedHandle设为NULL就行了。
       得到一个指向IDirect3DVertexBuffer9的指针后,顶点缓冲也就创建完毕了。现在要做的就是把之前保存在数组中的顶点信息放在顶点缓冲区里面。首先,使用IDirect3DVertexBuffer9::Lock来锁定顶点缓冲区:
HRESULT Lock(
   UINT OffsetToLock,
   UINT SizeToLock,
   void **ppbData,
   DWORD Flags
);
       OffsetToLock指定要开始锁定的缓冲区的位置。通常在起始位置0开始锁定。
       SizeToLock指定在锁定的缓冲区的大小。设为0的话就是表示要锁定整个缓冲区。
       ppbData用来保存返回的指向顶点缓冲区的指针。通过这个指针来向顶点缓冲区填充数据。
       Flags高级应用。通常设为0
       填充为顶点缓冲区后,使用IDirect3DDevice9::Unlock来解锁。
       最后在渲染的时候使用IDirect3DDevice9::SetStreamSource来告诉D3D要渲染哪个顶点缓冲区里面的顶点。
HRESULT SetStreamSource(
   UINT StreamNumber,
   ID

抱歉!评论已关闭.