|
|
Direct3D 是DirectX的成员之一.顾名思义,他是DirectX中负责实现3D图形绘制的部分.现在让我们来讨论一下如何编制一个简单的3D程序的问题.) j. s/ U# W+ |4 n) ]4 G' z
/ {2 L7 o$ x3 @- h, d
D3D是一个强大的三维图形绘制使用接口.它提供的高级保留模式(Retain mode) 接口功能强大又方便易用,十分适合初学者使用,所以我们使用这个接口来构造我们的程序.! c) Y4 K# P8 |1 f% u) }: [. N
( l, e& ?; j$ Z4 v/ \ 我们的目的是写一个全屏模式的程序.这就需要使用Direct Draw.更关键的是,D3D Retain mode(以下简称RM)是以DirectDraw为基础才能实现的.如果你对DirectDraw了解不多,也没关系,因为我们将要使到的仅是DirectDraw中极少的一部分.
6 l6 u+ M' \( r" O+ M) ~' \
% W5 P7 a7 \: F5 r% n 首先我们通过DirectDraw函数建立DirectDraw对象,函数定义如下:" _! e# H& R4 @$ \$ m8 [
1 F1 L- }/ i0 Q0 J
HRESULT DirectDrawCreate(
/ c, ~* I* Z9 F( O o8 ~ GUID FAR lpGUID,
6 Q, b. [5 w- E) |# m$ O LPDIRECTDRAW FAR *lplpDD, I! N! q! |6 a, c# q9 J
IUnknown FAR* pUnkOutter. J" {* X/ X) w, G
);
, M+ @7 o5 Q* [ Q) t0 h& K: a4 X/ W
第一个参数是全局唯一标识符,代表着要使用的驱动程序.我们在这里简单的使用NULL填充以表示使用活动的显示驱动程序.第二个参数是个指针,如果调用成功则会把建立的DDraw对象地址付给它.2 [- ~% L8 F2 ?; G8 L
第三个参数填NULL就行了.如果调用成功则返回DD_OK./ v8 E, b, A/ u
9 A8 K: X o' A
然后我们就可以用DirectDraw的SetCooperativeLevel函数设置合作级别为独占全屏.函数的一般形式如下:
( `3 x3 _; E! K& n
! n2 g; `" q% U* R! P; Y' i8 @" y HRESULT SetCooperativeLevel(: L, {9 S( k3 z# ]
HWND hwnd,$ q8 Z, N; b& _# i
DWORD dword8 S& Y% ?6 N0 a* x6 }8 m5 E( s
);
/ c0 R2 \# J. g& M& Z( y- H( Q / t" e4 s9 _2 w- g
第一个参数是应用程序的窗口句柄.第二个参数用 DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN填充表示使用独占全屏模式.这种模式效率较高,是一般游戏的通用模式.代码形式如下:
. _1 w" ^2 v& C9 _$ G) z }" a; y
# n n6 o( x; e, i/ V LPDIRECTDRAW lpDD;
' U4 h# L9 m- Z0 G5 |" J- J HRESULT hr;
* e9 h9 [" ?7 w4 ~2 F: U* @ ......//建立DDraw对象,错误处理,等
9 U# {: g8 T2 j% j6 k) @( j0 p; T0 A0 y2 g# b
hr=lpDD->SetCooperativeLevel(NULL,DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);1 ^0 w/ ^( n3 C x$ Z; O
if(hr!=DD_OK)
$ u* l' s' {$ _- `4 w; { //错误处理
* n2 m. {/ d8 l8 h# {6 v3 e& C, Y
$ i6 Y2 D* ~$ m1 h9 W 此后我们就可以设置显示模式了.DirectDraw的SetDisplayMode函数用来设置显示模式.
3 Y' d6 b, q% g- A4 r* D* A1 C/ K' B$ T0 i
//假设已有LPDIRECTDRAW对象的指针lpDD
W3 }1 [- [: h& m3 L
- Q t- U& t) Y hr=lpDD->SetDisplayMode(640,480,16,0,0);
, D u j+ s9 S1 b I, K& t& v if(hr!=DD_OK)( i$ x; o1 P2 e+ u4 m$ x6 V
//错误处理% H0 p1 [4 g' m. ^5 o
) o, E, s- m. H+ \' f- ~
为了尽可能简化,我们没有使用枚举函数来得到可用的显示模式而是直接使用了一般显示设备都支持的640*480分辨率,16位色.
' h5 x: U7 O+ s2 B9 C8 x& y) S1 _5 g5 A/ |9 @
下一步是创建图像输出的目标--主表面.通过DirectDraw的CreateSurface函数来创建表面.首先填充表面的特征到一个DDSURFACEDESC结构.3 ^/ g7 k8 H T' g+ c/ U2 q
0 k# Z+ z) E9 a5 b& ?" c
LPDIRECTDRAWSURFACE lpDDSPrimary=NULL; //主表面
+ R3 Z5 A% X1 l6 H DDSURFACEDESC ddsd;
* `: i, B; I' x7 m ......2 p3 P9 p7 `) I* p7 K" s6 k
ZeroMemory(&ddsd,sizeof(ddsd));
) ^5 |3 y7 L4 L7 E/ | ddsd.dwSize=Sizeof(ddsd);4 f: ^" d3 H% d$ t6 J o K1 s
ddsd.dwFlags=DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
8 z: H* g7 {* x0 Y, p4 X ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE| //主表面" l( _- U# A" A+ }' z
DDSCAPS_FLIP| //可实现翻转动画% E0 q$ [2 N1 p. u
DDSCAPS_COMPLEX|
- H7 g/ y+ {2 b" P DDSCAPS_VIDEOMEMORY|% I4 [; P2 T1 j) f8 P
DDSCAPS_3DDEVICE; //可以作为D3D 设备3 |: B0 ~) u+ ~# ^
ddsd.dwBackBufferCount=1; //后备缓冲表面数目为1
% E( k% g: Q: M# t/ y hr=lpDD->CreateSurface(&ddsd,&lpDDSPrimary,NULL);" V& c& c) o+ z* u2 B( ^
if(hr!=DD_OK)3 Z1 U; [& B. S- _- b" `; E
//错误处理 P2 C- }/ a% K& ?( T4 ]3 v5 y( {+ |
, {0 k+ u1 U' U; b 如果创建成功,我们就有了一个有后缓冲,可实现翻转的,可作为D3D设备的主表面(关于D3D设备,后面还要讲到)然后我们用GetAttachedSurface函数获得指向后备缓冲的指针:( h- d5 L1 X, I7 R
! |& f7 E p" p, P0 Y( A
DDSCAPS ddscaps;4 J# I' o% w0 ?$ _( |- i4 g( P' M
LPDIRECTDRAWSURFACE lpDDSBackbf;
: b( P) {1 N6 \' f) R# ~, F ......& F$ l# n, i9 y
ddscaps.dwCaps=DDSCAPS_BACKBUFFER|DDSCAPS_3DDEVICE ;+ v% t ?( U/ i' G
hr=lpDDSPrimary->GetAttachedSurface(&ddscaps,&lpDDSBackbf);$ B% a: q; ]. l" H! Q4 q
if(hr!=DD_OK)- F' Q- m+ X' ?7 o. e7 X) x
//错误处理+ F8 C* a0 A; M7 W! [0 D6 D7 h4 G
, P7 Y! {0 a" q- u, t 一切准备工作就绪,我们就可以开始使用D3D RM了./ J4 y O0 |5 K% y' C# j
& o7 I5 M' ]3 H8 W; S
RM的最大好处就在于其形象性.不必再在十分抽象的层面上去理解"对象"这一概念.我们完全可以把对象理解为诸如光源,3D物体和摄像机之类的实物.
, f: j0 I- M" E, S5 |# H# Z2 v' }9 {( e
首先通过Direct3DRMCreate建立一个D3D保留模式对象.它是一切RM对象的基础,只有通过它才能建立其他RM对象.4 b9 ?* _' @ s, F: v: Q# F! D* Y l
; R# W! O/ Q7 Y' T2 O6 |& s
LPDIRECT3DRM lpD3Drm;5 H" S4 e% w* Y0 }* a% a1 @
......, P1 C1 E6 W5 ~* _
hr=Direct3DRMCreate(&lpD3Drm);$ E) ~+ s, o3 s5 o4 d
if(hr!=DD_OK)
! w% K/ @$ B1 y: N" x4 G4 J9 M //错误处理
5 [. A4 t6 _! c9 p5 X. }6 f& C; d. |
然后我们用QueryInterface方法查询一个更新版本的接口并使用它:: \4 r! ^4 o* v! Z( R, U
8 S6 D* [6 W* {+ s; i. r7 ^
hr =lpD3Drm->QueryInterface(IID_IDirect3DRM3,(LPVOID *)&lpD3DrmNew);) D+ u" e! O. y: K$ v
if(FAILED(hr)) & E: B& ~9 _9 N3 ]
//错误处理
. ~3 ^, A+ w4 n2 ?# D3 R) E6 \) B3 E8 q9 C: }3 s! V: b$ ~. `
这时旧的接口已经无用,释放它:0 p2 H5 b7 r4 q" S$ [% E
lpD3Drm->Release();
6 }" ^ u( b: c( _5 w
# w7 Q _- f/ n& B) S( l L( P 前面我们已经建立了一个DirectDraw表面(Surface)对象,现在我们用它来建立D3DRM设备(Device)对象.设备对象代表了最终图像渲染到的设备和所用的显示驱动程序.在这里使用的是默认设备即显示器.函数如下:
( p& ~/ f3 v3 B9 M% S) k9 h3 _4 ~
HRESULT CreateDeviceFromSurface(
- S/ r4 ~9 \- c/ i" s LPGUID lpGUID,
+ K" G5 @; `9 G; K. Q, b LPDIRECTDRAW lpDD,
, t0 y9 U0 d7 D4 Y8 |: m LPDIRECTDRAWSURFACE lpDDSBack,
; Q( {$ z Q3 p$ h; s: @% {! E- x& T) z" n LPDIRECT3DRMDEVICE3 * lplpD3DRMDevice,+ o: a6 i& }3 ^' h! X0 M' V
);
) i4 a) f) U3 |. `
6 ]6 ?/ e7 |" i; z+ [' Z 第一个参数是指向GUID结构变量的指针.GUID是"全局唯一标识符",是Windows系统对可用设备的唯一标识.在这里指的是渲染所用的方式(如软加速,硬加速)为了尽可能简化程序可以直接用NULL表示用默认的方式.建立过程代码如下:
% p( |& T H2 W
9 l( s$ v* w: @- H/ [9 ?- J LPDIRECT3DRMDEVICE3 lpDevice;" M+ U& Z( z' p4 c/ S
hr=lpD3DrmNew->CreateDeviceFromSurface(NULL,(LPDIRECTDRAW)lpDD,
4 b8 h5 X& t. f) B4 G0 D+ l8 v7 c6 ^ lpDDSBackbf,& T5 Z+ L7 F! u) Z* w) _
0,&lpDevice);
9 g" g p6 @0 C3 ^3 A/ J if(FAILED(ddrval))
1 ^1 N. C0 A0 ]& }2 S2 n% D) K //错误处理
% P# B8 g2 W/ H2 d6 [4 L! v9 Y/ z
现在我们终于可以创建真正意义上的3D对象了.我们有必要先弄清"Frame"(框架)这一概念."Frame"就像一个玻璃盒子,里面可以装任何3D物体,如飞机,坦克,食人魔.Frame可以被设置方向和位置,这时它所装的3D物体也会与它保持同样的方向,位置.Frame是D3DRM的灵魂.
5 } O v# T& W: B7 v' q; E y$ K) l- v* g4 B f& h
一个场景往往包含一个由Frame构成的层次,即由根Frame及其子Frame(子Frame还可以有子Frame)构成的树型结构.父Frame和子Frame的关系,就像大臂和前臂的关系一样,大臂在自己运动的同时,可以决定前臂的运动状态,反之不可.根Frame是没有父Frame的.
- J8 V6 [+ D( D' {9 J1 T, H8 U4 x
: e" T! @9 H6 o 用CreateFrame函数创建Frame:
& G0 W0 {" X- j8 j& }
) {2 T# q/ f9 O4 m& o5 _# q( K! y HRESULT CreateFrame(
^+ R6 Q% [. ~8 v3 X LPDIRECT3DRMFRAME lpParentFrame,
0 f' \; j2 _ h7 Z# e2 X" E LPDIRECT3DRMFRAME * lpFrame,
( \) b" E0 Z- ^9 ^) Y; m0 _ );
% ]7 @* i: z% G, N8 d; X- s0 y$ s& r# b% |8 ^& F
第一个参数是作为父Frame的Frame的地址.第二个参数是要创建的 Frame.创建过程如下:1 K3 u7 \$ c7 l0 y, n( \
, s) `7 ?8 j. `( V% g: K
LPDIRECT3DRMFRAME lpRoot;
( X6 A1 s1 s- s0 e. B# j LPDIRECT3DRMFRAME lpParent;
/ n: x; t) f6 }: c/ P/ ?/ P0 C$ A LPDIRECT3DRMFRAME lpChild;
. Q- ~3 n6 I% V( A1 C" g ......
% H( i4 U, A" s, J: h lpD3DrmNew->CreateFrame(NULL,&lpRoot); //根Frame
( K# _5 \0 C( v% ^3 [1 j$ ? lpD3DrmNew->CreateFrame(lpRoot,&lpParent); //父Frame(在这里又是Root的子Frame.)" B+ L+ _: T" H; N7 c/ A: U/ i
lpD3DrmNew->CreateFrame(lpParent,&lpChild); //Parent的子Frame.
9 V* \, ?; ? t8 m0 Y/ q) l+ W9 Y. x //省略了错误处理5 [/ ]0 h% ?* U8 @$ G [
, T# y% u4 e. @3 i6 `
创建完Frame后,就可以向里面加入3D模型了.最简单的方法是用Frame对象的load函数:4 V y2 |7 e, |2 K- F [2 N
' x! v( @9 m: _& E, c7 }' k
HRESULT Load(, Y0 {% X( @1 n% P+ M
LPVOID lpvObjSource,
1 z! Z. B1 J& e LPVOID lpvObjId;& N5 c: o! u3 \8 i' k- U% ?
D3DRMLOADOPTIONS flags,
. F! n$ k, x+ s4 f. q ^- P D3DRMLOADTEXTUER3CALLBACK d3drmloadtexturecallback,
/ P# v& h" b) P- ~% j' j7 \. p LPVOID lpArgLTP
' H- |) I5 [3 a J; e$ `7 B" L );
5 s$ E' w1 v4 c7 u7 ]* z" W0 l* P
- [) |8 G0 q* R4 R/ K5 x6 h- T- E 使用时一般形式如下:
) H* m. U+ [! S5 @; Q, @
) I7 t; R7 U$ B0 s5 W0 K% D lpRoot->Load("backgrd.x",NULL,D3DRMLOAD_FROMFILE,NULL,NULL);. Y3 r* ?& c" c$ l0 W9 M9 ~( ?
lpParent->Load("parent.x",NULL,D3DRMLOAD_FROMFILE,NULL,NULL); g: {, s* c( `! {- A
lpChild->Load(......
7 X, S4 q$ j& m7 T) @3 ?, ?, Z ......1 T2 Q z1 ?3 m0 p2 P# J6 t! ?9 }
//省略出错处理
& v M; T- ~+ t8 U2 V% r
* g$ q1 E' u5 r% B7 F 第一个参数是已有的3D模型文件(*.xfile),可以先用建模软件(如3DSMAX)建立模型并导出为*.3DS文件,再用DirectX开发工具包(SDK)提供的CONV3DS工具将其转化为*.X文件.例如我们有一名为backgrd.3ds的文件,要把它转化为Frame对象能Load的.x文件,需要按如下方式运行:5 A5 I! d4 c; Q3 P4 W
) `* I5 j, c, I9 g; I0 t
conv3ds -T -X backgrd.3ds
( I5 O% a9 F0 Z7 L G) G. E' t0 _4 u8 q+ L' G3 u
即可得到名为backgrd.x的文件.关于conv3ds 的其他参数及用法,请参阅DirectX SDK 帮助文档.
2 {7 j( P& q/ E4 c4 ^6 d! K7 S( ~6 \) P4 i* E. B9 h6 B
加载模型到frame还有其他方法如使用网格生成器对象,在这里不再赘述.
7 E$ B" i }1 ?) j& j% A3 c+ p' u1 P4 }8 R2 L& R# U
我们把3D模型加载到frame以后,还需要在场景中引入灯光和摄像机这两个必不可少的东西。首先为摄像机建立一个frame,建立方法和普通frame一样:7 \0 h# i4 z# h# }2 H% c8 v
! h! _2 W" E% Y1 J/ R& J2 Y( x% j
LPDIRECT3DRMFRAME lpCameraFrame;( U1 z8 o$ s* }& c2 N+ c5 ]9 \
……, k% m- P# [) s+ T! u
lpD3DrmNew->CreateFrame(lpRoot,&lpCameraFrame);
* l, }! `0 b$ e$ y$ t |; A" J5 I! Z9 ]4 {: s, N* z
然后就可以创建视口(摄像机)了:1 K) {& a# X* K' K# c: d
HRESULT CreateViewport(
. A" e2 y9 J& J1 z* s LPDIRECT3DRMDEVICE lpDev,
) b3 c. }+ v' l1 s/ ` LPDIRECT3DRMFRAME lpCamera,/ i, I4 m7 w) K% R) v3 a4 Q+ N
DWORD dwXPos,) k, ?# w, r3 z
DWORD dwYPos,
& `& }# `0 T7 q DWORD dwWidth,
; Q3 K) {9 p% d& E' m DWORD dwHeight,# X/ D" N5 V+ V7 o2 G
LPDIRECT3DRMVIEWPORT * lpViewport, O' a) k- ^ a; K
);
7 b% v/ q0 ]7 K$ Z/ ^2 {
& t: m) M9 t- \$ M 第一个参数是我们前面创造的设备,第二个参数是摄影机frame,下面四个参数是视口位置和尺寸。调用时程序形式如下:
+ ^- k* H0 w( K' K9 ]: t: g) z# H" `8 a# @9 X3 u
LPDIRECT3DRMVIEWPORT lpViewport& g: b6 V$ a- ^6 G' x
int width,height;
: s) w( J1 b& @0 ^0 A# f( _4 h ……- \. [ q3 C5 p
width= lpDevice->GetWidth();9 `" ]0 n+ L3 V X7 m" C; ^! w
height= lpDevice->GetHeight();& n" E; F9 h" t1 h+ B Y) [
hr=lpD3DrmNew->CreateViewport(lpDevice,lpCameraFrame,0,0,width,height,
/ r: }$ }' Y; M* f& Q2 I; x &lpViewport);
. o. Q3 U+ c6 e+ p( I if(hr!=DD_OK)
6 Z5 W( k- I) _5 o ……7 y, \6 o' U4 _% c
- T7 S- S3 A8 F+ K. }
在创造Viewport的同时,也就把它装载到了frame中。
) _( y" k# h. N1 U" h/ W 然后为光源创建frame:
% P. M, q7 I$ `# f: @
. C5 E! Q. p0 \5 m! B LPDIRECT3DRMFRAME lpLightFrame;$ w6 E' g: w: Z& \9 b1 B
……; e0 ?2 ]2 P- H, ~) n) Q
lpD3DrmNew->CreateFrame(lpRoot,&lpLightFrame);
* m' f. o9 c8 ~. H" c ……2 q5 g1 U7 W% c3 F/ _4 v5 \
1 ~: G P+ z! N( T/ Y
创建灯光的函数如下所示:: e( ^- l1 P# C' l/ [) H; A- G2 U+ b
HRESULT CreateLightRGB(D3DRMLIGHTTYPE ltLightType,
! l9 B0 D' _9 i6 `* o) o5 G+ ]; m D3DVALUE vRed,
3 u9 h! D" h2 k; l0 z D3DVALUE vGreen,
/ m& b3 N0 J$ s. U4 t( o D3DVALUE vBlue,
5 U4 s8 @; @* Y- K2 O# N# f+ u! c LPDIRECT3DRMLIGHT * lplpD3DrmLight( d0 W) A! k' K9 B& `3 ^' H
);
2 `% J( u( m+ n. N
/ g. z8 b; H& f) P3 Q& Y" ?# q 下面的代码创建一个白色点光源:, q: l+ D5 X0 P; K# ]( ~4 c* y1 U
7 Y) F8 E3 W8 t# N2 M LPDIRECT3DRMLIGHT lpLight1=NULL;
5 }, ~2 [/ f0 ^3 H& Y5 m7 s2 [' q3 ? hr=lpD3DrmNew->CreateLightRGB(D3DRMLIGHT_POINT,7 D5 V9 g) K- b/ e* S; B. D8 _1 g
D3DVAL(1.0),
& r- Z! \% A3 n3 t D3DVAL(1.0),+ L0 p8 O/ @. _- x$ V0 z- {" b
D3DVAL(1.0),3 w9 X( }& L+ X6 ~
&lpLight1);* U2 }# z5 C# Q) l6 l- e7 ?
if(hr!=DD_OK) ……
# u0 {! _0 F6 p# G8 ~! b; U2 m
4 a/ [* F, T. e: ?0 k 光源的数量可以视需要而定。其他几种光源可以参阅DXSDK连机帮助。建立了光源后,还需要用frame对象的AddLight函数将其加载到frame:' ^* x' E( K! r% k8 l7 U
3 G& P x" k0 v/ |. t! G- Z lpLightFrame->AddLight(lpLight1);
$ v% I) ]% z m, h3 P" R) y //以上代码均省略了错误处理 E6 z& w' s0 T2 W" r q
p& U2 X! y) `3 p* X$ P
现在已经基本上把应该创建的都创建了,我们可以移动它们并把它们渲染出来。
. I7 J# _3 m1 c: T# U5 F
: ]: h- C: Y6 F9 W) j7 Q 前面提到frame所装的东西的方向和位置都由frame决定。Frame是由如下函数来设置位置和方向的:
4 t/ [" r7 U) u* \( A3 {: p- m" J7 b0 G% T& i. p( [
HRESULT SetPosition(LPDIRECT3DRM3 lpframe, , O" k! v& S! A9 v$ q" u* W% Z
D3DVALUE X,: c2 A% k x; y( U6 B9 f4 k
D3DVALUE Y,
0 e+ F; C+ q/ p D3DVALUE Z! C* g8 [6 I% m
); //位置
6 Q5 M' B& k& w6 U% ^" c HRESULT AddRotation(D3DRMCOMBINETYPE rctCombine,3 S0 A0 N: i6 h
D3DVALUE x,
: g! N3 \% u+ C6 I D3DVALUE y,/ S+ O: e4 W7 F) J3 F
D3DVALUE z,: `+ S6 L2 u1 G' r! g3 H
); //旋转+ N: d# v7 b4 t( a) w
第一个参数是新的旋转和已有的frame变换如何组合,一般使用D3DRMCOMBINE_BEFORE.7 F r. D1 C& r' U
HRESULT AddTranslation(D3DRMCOMBINETYPE rctCombine,
7 z$ h7 ^3 \0 O/ G o, Q D3DVALUE x,
+ t# V" q( z+ g7 H8 J' } D3DVALUE y,6 \" c8 R( H9 k9 ~, Y& w
D3DVALUE z,
" E7 f) }+ t: N# _) c' ], p ); //移动( v) [% W" |6 s- m
: K- S' `- F( ] 位置可以根据要求随意设置。! Q% ^6 V: e5 s6 X6 h1 K
4 U, w. \6 [" a0 h% ]
下一步,就是渲染了。我们已经创建了Device对象,它的一些函数可以用来设置渲染的质量:5 r1 y. J0 x3 L0 `0 x$ C+ w
; Q; L. U2 A3 _% W( ?- q% d HRESULT SetQuality(D3DRMRENDERQUALITY rqQuality);
$ ?: z( Z3 F# A6 ` HRESULT SetTextureQuality(D3DRMTEXTUREQUALITY rqTexQuality);
+ \& H. N' f$ v- A, Q3 Q! y! b+ E* ]. _0 F1 t
我们设置渲染质量为Gourand底纹,开启灯光,实心填充。设置纹理质量为双线过滤。
. S5 c4 G I/ { a" _
- j" \5 Y% n$ ?) w lpDevice->SetQuality(D3DRMRENDER_GOURAUD);
& e' I& X# m! c/ r1 G* }0 s! { ^* c lpDevice->SetTextureQuality(D3DRMTEXTURE_LINEAR);
% J) I, G/ s. q$ `! L0 _* v
+ x( g& j5 }, C' @ 终于把一切都设好了。以上的工作,可以分类写几个Init函数完成,在程序初始化部分调用。然后就可以在程序主循环中进行渲染动画的工作。由于我们使用全屏独占模式,消息已经不起作用,所以要在WinMain主循环中而不是Winproc的WM_PAINT中来实现。(对于MFC,需要重载WinApp类的Run()函数。)很多地方有可用的程序模版(即已改好了主循环或Run()函数,只需按自己的需要写入代码的程序)可以找。% W4 ]- S5 [- J
r* y8 w7 @3 @+ B' M- r' [8 Q3 b: S
在渲染动画的循环中,我们所要做的只是清空视口,渲染,显示。清空视口需要两步:: R0 Y s4 {. v. l
H( {$ I, ~9 T. \; A
lpViewport->ForceuUpdate(0,0,width,height);+ F. |) [6 B2 d; ~6 @$ i2 G
lpViewport->Clear(D3DRMCLEAR_ALL);3 o7 _$ c7 F2 c% |
. }) |, c7 M# N1 N 然后进行渲染:
& j8 z- P3 r5 U. a
. g- e$ E x& i# r; t lpViewport->Render(lpRoot); //只渲染作为参数的frame以及其子frame.0 h& t, A& u& Z/ n6 y2 c# f
0 c9 S& M& M* c8 g 显示工作:
- ~, S5 q2 g3 a7 k' E+ Z9 T# _" v$ w1 \' C
lpDevice->Update();
2 k7 T" n; q: R! O7 A' X
^8 x3 D! g4 P, w2 x 不要忘记,我们的Device对象是建立在后备缓冲表面(不可见)上的,最后要用Surface对象的Flip方法使其翻转:
% q& o1 I8 @5 T) l8 }/ N" p/ `5 _. F
lpDDSPrimary->Flip(NULL,DDFLIP_WAIT);
5 K1 k: \# H# |5 Q! ^+ R# M5 @ E2 g1 v7 j
如果函数调用成功,渲染后的图像应出现在屏幕上。循环调用这几个函数,并调用根frame的Move函数,即可实现动画效果。
1 p6 a3 g) j, e' a: h ^- q
" x0 K0 c/ I8 Y( b 前面提到了GUID,如果你的系统默认为使用硬件加速,渲染出的效果应该很好而且十分流畅。但是如果使用的是软加速,效果会很不好,有明显的跳帧和延迟。这时可以使用立即模式的列举device的函数,但这已超出了本文的讨论范围。另一种简单的方法为直接在"控制面版"中的"DirectX"中D3D项中查询3D HAL(硬件加速)的GUID并填入到一个GUID型的结构中,这样做虽简单,但兼容性不好。
! t2 ]" d/ Q8 }7 K& ]1 Y5 ~2 Q3 |! T7 J& R- F! A' t& ]
至于循环的控制和退出,熟悉Direct Input的朋友可以轻易的控制循环的结束和物体的动作控制。如果不会使用Direct Input,可以采用有限次循环的方法。
0 S6 l; ]9 |4 A( a, G; { E& B7 J/ @- A2 `
另外,不要忘记在程序结束之前调用所有对象都有的Release()函数来释放所有对象。
6 ]4 {' R8 s% W& D, `. J8 n6 D% o/ f) T; T) Y
在编译之前,不要忘记加入相应的库文件(ddraw.lib,d3drm.lib等)和相应的头文件(主要是ddraw.h和d3drm.h)。
6 r/ P! F; j$ U
2 l% F l$ r& X3 g* B: M0 D 好了,我们暂时就到这里吧,D3DRM还有很多强大的功能,如控制材质,分级贴图,雾效等等。即使是文中提到的各种实现,也是有很多不同的方法的。限于篇幅,只能介绍这么多了,希望可以对喜欢3D编程而又苦于如何开始的朋友一点帮助。 |
|