Direct3D 是DirectX的成员之一.顾名思义,他是DirectX中负责实现3D图形绘制的部分.现在让我们来讨论一下如何编制一个简单的3D程序的问题.
D3D是一个强大的三维图形绘制使用接口.它提供的高级保留模式(Retain mode) 接口功能强大又方便易用,十分适合初学者使用,所以我们使用这个接口来构造我们的程序.
我们的目的是写一个全屏模式的程序.这就需要使用Direct Draw.更关键的是,D3D Retain mode(以下简称RM)是以DirectDraw为基础才能实现的.如果你对DirectDraw了解不多,也没关系,因为我们将要使到的仅是DirectDraw中极少的一部分.
首先我们通过DirectDraw函数建立DirectDraw对象,函数定义如下:
HRESULT DirectDrawCreate(
GUID FAR lpGUID,
LPDIRECTDRAW FAR *lplpDD,
IUnknown FAR* pUnkOutter
);
第一个参数是全局唯一标识符,代表着要使用的驱动程序.我们在这里简单的使用NULL填充以表示使用活动的显示驱动程序.第二个参数是个指针,如果调用成功则会把建立的DDraw对象地址付给它.
第三个参数填NULL就行了.如果调用成功则返回DD_OK.
然后我们就可以用DirectDraw的SetCooperativeLevel函数设置合作级别为独占全屏.函数的一般形式如下:
HRESULT SetCooperativeLevel(
HWND hwnd,
DWORD dword
);
第一个参数是应用程序的窗口句柄.第二个参数用 DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN填充表示使用独占全屏模式.这种模式效率较高,是一般游戏的通用模式.代码形式如下:
LPDIRECTDRAW lpDD;
HRESULT hr;
......//建立DDraw对象,错误处理,等
hr=lpDD-SetCooperativeLevel(NULL,DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
if(hr!=DD_OK)
//错误处理
此后我们就可以设置显示模式了.DirectDraw的SetDisplayMode函数用来设置显示模式.
//假设已有LPDIRECTDRAW对象的指针lpDD
hr=lpDD-SetDisplayMode(640,480,16,0,0);
if(hr!=DD_OK)
//错误处理
为了尽可能简化,我们没有使用枚举函数来得到可用的显示模式而是直接使用了一般显示设备都支持的640*480分辨率,16位色.
下一步是创建图像输出的目标--主表面.通过DirectDraw的CreateSurface函数来创建表面.首先填充表面的特征到一个DDSURFACEDESC结构.
LPDIRECTDRAWSURFACE lpDDSPrimary=NULL; //主表面
DDSURFACEDESC ddsd;
......
ZeroMemory(&ddsd,sizeof(ddsd));
ddsd.dwSize=Sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE| //主表面
DDSCAPS_FLIP| //可实现翻转动画
DDSCAPS_COMPLEX|
DDSCAPS_VIDEOMEMORY|
DDSCAPS_3DDEVICE; //可以作为D3D 设备
ddsd.dwBackBufferCount=1; //后备缓冲表面数目为1
hr=lpDD-CreateSurface(&ddsd,&lpDDSPrimary,NULL);
if(hr!=DD_OK)
//错误处理
如果创建成功,我们就有了一个有后缓冲,可实现翻转的,可作为D3D设备的主表面(关于D3D设备,后面还要讲到)然后我们用GetAttachedSurface函数获得指向后备缓冲的指针:
DDSCAPS ddscaps;
LPDIRECTDRAWSURFACE lpDDSBackbf;
......
ddscaps.dwCaps=DDSCAPS_BACKBUFFER|DDSCAPS_3DDEVICE ;
hr=lpDDSPrimary-GetAttachedSurface(&ddscaps,&lpDDSBackbf);
if(hr!=DD_OK)
//错误处理
一切准备工作就绪,我们就可以开始使用D3D RM了.
RM的最大好处就在于其形象性.不必再在十分抽象的层面上去理解"对象"这一概念.我们完全可以把对象理解为诸如光源,3D物体和摄像机之类的实物.
首先通过Direct3DRMCreate建立一个D3D保留模式对象.它是一切RM对象的基础,只有通过它才能建立其他RM对象.
LPDIRECT3DRM lpD3Drm;
......
hr=Direct3DRMCreate(&lpD3Drm);
if(hr!=DD_OK)
//错误处理
然后我们用QueryInterface方法查询一个更新版本的接口并使用它:
hr =lpD3Drm-QueryInterface(IID_IDirect3DRM3,(LPVOID *)&lpD3DrmNew);
if(FAILED(hr))
//错误处理
这时旧的接口已经无用,释放它:
lpD3Drm-Release();
前面我们已经建立了一个DirectDraw表面(Surface)对象,现在我们用它来建立D3DRM设备(Device)对象.设备对象代表了最终图像渲染到的设备和所用的显示驱动程序.在这里使用的是默认设备即显示器.函数如下:
HRESULT CreateDeviceFromSurface(
LPGUID lpGUID,
LPDIRECTDRAW lpDD,
LPDIRECTDRAWSURFACE lpDDSBack,
LPDIRECT3DRMDEVICE3 * lplpD3DRMDevice,
);
第一个参数是指向GUID结构变量的指针.GUID是"全局唯一标识符",是Windows系统对可用设备的唯一标识.在这里指的是渲染所用的方式(如软加速,硬加速)为了尽可能简化程序可以直接用NULL表示用默认的方式.建立过程代码如下:
LPDIRECT3DRMDEVICE3 lpDevice;
hr=lpD3DrmNew-CreateDeviceFromSurface(NULL,(LPDIRECTDRAW)lpDD,
lpDDSBackbf,
0,&lpDevice);
if(FAILED(ddrval))
//错误处理
现在我们终于可以创建真正意义上的3D对象了.我们有必要先弄清"Frame"(框架)这一概念."Frame"就像一个玻璃盒子,里面可以装任何3D物体,如飞机,坦克,食人魔.Frame可以被设置方向和位置,这时它所装的3D物体也会与它保持同样的方向,位置.Frame是D3DRM的灵魂.
一个场景往往包含一个由Frame构成的层次,即由根Frame及其子Frame(子Frame还可以有子Frame)构成的树型结构.父Frame和子Frame的关系,就像大臂和前臂的关系一样,大臂在自己运动的同时,可以决定前臂的运动状态,反之不可.根Frame是没有父Frame的.
用CreateFrame函数创建Frame:
HRESULT CreateFrame(
LPDIRECT3DRMFRAME lpParentFrame,
LPDIRECT3DRMFRAME * lpFrame,
);
第一个参数是作为父Frame的Frame的地址.第二个参数是要创建的 Frame.创建过程如下:
LPDIRECT3DRMFRAME lpRoot;
LPDIRECT3DRMFRAME lpParent;
LPDIRECT3DRMFRAME lpChild;
......
lpD3DrmNew-CreateFrame(NULL,&lpRoot); //根Frame
lpD3DrmNew-CreateFrame(lpRoot,&lpParent); //父Frame(在这里又是Root的子Frame.)
lpD3DrmNew-CreateFrame(lpParent,&lpChild); //Parent的子Frame.
//省略了错误处理
创建完Frame后,就可以向里面加入3D模型了.最简单的方法是用Frame对象的load函数:
HRESULT Load(
LPVOID lpvObjSource,
LPVOID lpvObjId;
D3DRMLOADOPTIONS flags,
D3DRMLOADTEXTUER3CALLBACK d3drmloadtexturecallback,
LPVOID lpArgLTP
);
使用时一般形式如下:
lpRoot-Load("backgrd.x",NULL,D3DRMLOAD_FROMFILE,NULL,NULL);
lpParent-Load("parent.x",NULL,D3DRMLOAD_FROMFILE,NULL,NULL);
lpChild-Load(......
......
//省略出错处理
第一个参数是已有的3D模型文件(*.xfile),可以先用建模软件(如3DSMAX)建立模型并导出为*.3DS文件,再用DirectX开发工具包(SDK)提供的CONV3DS工具将其转化为*.X文件.例如我们有一名为backgrd.3ds的文件,要把它转化为Frame对象能Load的.x文件,需要按如下方式运行:
conv3ds -T -X backgrd.3ds
即可得到名为backgrd.x的文件.关于conv3ds 的其他参数及用法,请参阅DirectX SDK 帮助文档.
加载模型到frame还有其他方法如使用网格生成器对象,在这里不再赘述.
我们把3D模型加载到frame以后,还需要在场景中引入灯光和摄像机这两个必不可少的东西。首先为摄像机建立一个frame,建立方法和普通frame一样:
LPDIRECT3DRMFRAME lpCameraFrame;
……
lpD3DrmNew-CreateFrame(lpRoot,&lpCameraFrame);
然后就可以创建视口(摄像机)了:
HRESULT CreateViewport(
LPDIRECT3DRMDEVICE lpDev,
LPDIRECT3DRMFRAME lpCamera,
DWORD dwXPos,
DWORD dwYPos,
DWORD dwWidth,
DWORD dwHeight,
LPDIRECT3DRMVIEWPORT * lpViewport
);
第一个参数是我们前面创造的设备,第二个参数是摄影机frame,下面四个参数是视口位置和尺寸。调用时程序形式如下:
LPDIRECT3DRMVIEWPORT lpViewport
int width,height;
……
width= lpDevice-GetWidth();
height= lpDevice-GetHeight();
hr=lpD3DrmNew-CreateViewport(lpDevice,lpCameraFrame,0,0,width,height,
&lpViewport);
if(hr!=DD_OK)
……
在创造Viewport的同时,也就把它装载到了frame中。
然后为光源创建frame:
LPDIRECT3DRMFRAME lpLightFrame;
……
lpD3DrmNew-CreateFrame(lpRoot,&lpLightFrame);
……
创建灯光的函数如下所示:
HRESULT CreateLightRGB(D3DRMLIGHTTYPE ltLightType,
D3DVALUE v