|
1、介绍
1 q: o9 v! t; f* V 微软的Direct3D包括了两个截然不同的API。高级的保留模式提供了场景和对象管理服务以及构建几何引擎。低级的立即模式API提供了直接访问硬件并允许熟练的3D程序员执行自己的渲染和场景管理。这种方式在灵活性和执行性能方面都优于保留模式。+ C2 H$ L( R# a
大多数关于Direct3D的资料都把这两种API分开讲解,许多人也以为它们是互不相关的。实际上,我们要指出,在一个程序中,你或许需要同时使用这两种API。: }2 Y3 s4 L) H
在这篇文章中,讨论同时使用保留模式和立即模式API的两种情形:: G+ B; H/ F% N, T8 h) a6 k
4 o; ?4 n4 m5 O$ Y, B •环枚举设备驱动器
+ k/ E. W+ f1 X/ _) N! ]$ k0 G •环在保留模式应用程序中作为用户可视对象使用执行缓冲
( p9 p! n8 K* G. ? - j' n) P- W( a4 p" Z* T& S
枚举设备驱动器是一种直截了当的操作,比较简单。在保留模式应用程序中使用执行缓冲是这篇文章的重点。5 \( t; q- u+ f* B
本文假设你已经了解了Direct3D。本文不是Direct3D的概述或教程。本文讨论了Direct3D联机文档中没有讲到的问题。这也就是说,你最好先看看Direct3D的文档。( {4 a& l; I9 N$ s( ]
注意,在一般的词汇上讨论Direct3D时,通常使用Direct3D这个词代指整个的3D API,既包括保留模式又包括立即模式。但是,根据Drect3D代码的约定,使用D3D表示立即模式接口或变量,D3DRM表示保留模式接口或变量。由于这种约定,在讨论样例代码或专用接口的时候,Direct3D有时候指的是立即模式而不是整个的3D API集。本文试图划清界限,但你也要意识到,样例代码和注释中也许也会在两种意义下都用Direct3D这个词。9 Y, Z. t/ M3 a I& x
% e! [( _8 E# C8 ~
2、枚举设备驱动器6 K' Y- ^ C! @2 p$ w* a
应用程序使用Direct3D,无论是立即模式还是保留模式,通常需要实时地在用户计算机上枚举可获得的(图形)驱动器。如果图像品质比渲染速度更重要的话,应用程序应该选择最高的位深(bit depth)和(或)分辨率。另一方面,如果需要高速渲染,应用程序会牺牲一些图像质量来换取性能。3 v- \5 G3 |4 W+ ^% M' n
保留模式没有单独包含一个枚举驱动器的方法。代替它的是,所有的Direct3D应用程序都使用IDirect3D::EnumDevices方法。DirectX SDK中的代码样例展示了如何使用这种方法。可以参看Direct3D帮助文件中的保留模式教程。使用这种方法枚举驱动器并不困难。我在只这里稍微说一下,读者可以参考DirectX联机文档获取全部的说明。9 a ^$ W/ E1 {+ e5 I5 e0 d
当开发人员需要在保留模式应用程序中调用立即模式API的时候,最显而易见的问题是:“我如何得到一个指向立即模式Direct3D的指针”?这个很简单:因为Direct3D COM接口准备了一个指向DirectDraw的接口,你可以通过这个DirectDraw接口获得Direct3D指针(记住,这里的Direct3D意味着立即模式)。这个可以简单的分为两步:
6 t. I$ v' ?6 E1 M8 n" Z9 p
+ h; M" y6 A- a; ?! A; U# E; ]) j1 S LPDIRECTDRAW lpDD;
9 K4 O/ q7 E! L( J$ q LPDIRECT3D lpD3D;; C6 J9 l3 l4 l, Q1 x3 K% \# R
HRESULT rval;
1 P; ~6 }4 ]' @9 d J2 g + {! V! j& k2 A3 B5 d
DirectDrawCreate (NULL, &lpDD, NULL);
9 ?" h$ U' z+ a+ Y rval = lpDD->lpVtbl->QueryInterface(lpDD, &IID_IDirect3D, (void**) &lpD3D);
" _( H. K7 W9 o0 y! e 1 i- `- Q9 y3 M( W6 _. o5 \
看到了吗?是不是很简单?现在我们已经有了一个指向Direct3D接口的指针,接下来,我们就可以轻松地调用IDirect3D::EnumDevices方法来枚举可获得的设备了。从现在开始,需要做的工作同立即模式应用程序一模一样:定义一个枚举回调例程,传递地址给IDirect3D::EnumDevices方法。回调函数将会被每一个系统上安装的驱动器调用。从而检查每个驱动器的特性来确定是否适合应用程序的需要。详细内容请参考具体的代码。
5 }" M# s6 V6 H3 ^8 e: x* T$ r 现在,该看看我们更感兴趣的另一个问题:混合使用Direct3D的两种模式。9 N# {5 Q" L' o7 c2 C
: U, l1 m# R; ]4 [: E" H& {7 ~4 x! x
3、在保留模式中使用执行缓冲
& S2 o: ^( q" M/ F7 [9 P7 @ \ 有的时候,应用程序可能想要使用执行缓冲(允许执行自己的变换、灯光或光栅等)。但同时还要使用保留模式中提供的更方便的API函数。这可以通过把执行缓冲看作是Direct3D中的可视对象来处理。
; O2 s' @/ ~5 Z* Z' f 在SDK中,可以找到一个叫做UVIS(User VISual)的例子,这是一个“燃烧的火焰”,它演示了这种技术,我们就以此为例。如果你是那种喜欢自己读代码,自己完成一切的人,那可以不必看下面的内容。如果你喜欢看详细的解释,那么让我们一起来看看这段代码,并讨论其中的几个主要问题。
, s o1 q. p% t8 z5 J
/ o/ I6 H3 w/ U- a 3.1 编译准备
L' a, r# M; ]% N9 \ 如果你明白怎么编译DirectX程序,那么可以不必向下看。如果你以前从未编译过任何DirectX代码,那么看看下面的内容。我们使用微软的Visual C++ 5.0编译UVIS样例。假设你的DirectX所在的路径是C:\dxsdk\sdk,那么:# Q: X/ C( A+ S- M( z2 C' f7 h5 r
1)创建一个新的的project workspace,在其中增加uvis.cpp、rmmain.cpp、rmerror.c三个文件
6 ]1 N) X4 N& \6 d 2)在Tools/Options/Directories中选Include files,加入DirectX头文件所在的路径,如:c:\dxsdk\sdk\include, U" i0 U0 l! R$ c5 \) i/ N# z- b
3)在Tools/Options/Directories中选Library files加入DirectX库文件所在的路径,如:c:\dxsdk\sdk\lib
0 g( v$ \/ u$ x- ] 4)在Project/Settings/Link中的Object/Library Modules加入链接时需要的库:d3drm.lib、ddraw.lib、winmm.lib
5 ~! K I0 N' k 4 C; i$ Q" _& Z0 V8 |% f& v
3.2 Direct3D保留模式样例的组织
: C/ u5 G- F* G v' B' I' Z 为了简化,在Direct3D SDK中所有保留模式样例共享一些通用代码。这使你能够把精力集中在核心代码上,而不必在通用的代码上浪费时间。保留模式通用代码包括两个文件:RMMAIN.CPP和RMERROR.C。7 o2 Q$ Q! ]! [8 b: u: R4 j) m/ ~5 s2 |
通用部分包括创建和管理标准Windows应用程序以及执行基本保留模式初始化和处理的代码。在下面的内容中,我们来看看UVIS的主要部分。我们不会详细的讨论每一个函数调用。我的目的不是解释保留模式或立即模式的用法,而是展示它们如何在一个应用程序中同时被使用。当然,我也希望这篇文章能让你迅速的理解如何把这些代码组织在一起。
, c- a+ \; Z) p4 _/ h# Y' {$ h# a7 E 6 z% c& d) g3 B% Q. {
3.3 RMMAIN.CPP做了些什么0 J. B$ p' z( G& X
在RMMAIN.CPP中的代码形成了Direct3D保留模式应用程序的一般框架。最重要的部分包含在WinMain()函数中,在这个函数里,由两个主要的部分:应用程序安装和初始化阶段以及消息循环,实际的渲染就发生在这个阶段。
( G7 M8 Y4 ]0 e3 R 应用程序安装和初始化。在InitApp()函数中,压缩了大部分的初始化代码。这个函数是最普通的。它先安装通常的窗口类,然后是初始化一些全局遍量。紧接着是调用OverrideDefaults(),这个函数通过填充如下结构定义了一些它自己的设置:
% Z3 T# }, v; Z9 t
7 R( J W9 o& P/ u" J; }: ? typedef struct Defaultstag {
; o0 f) t6 X. f7 w! T! a6 c BOOL bNoTextures;+ Y! T+ _* x" }
BOOL bResizingDisabled;
; {0 j/ ~8 f5 V1 @2 l0 `) W BOOL bConstRenderQuality;% {+ g* n7 Q/ Y" J/ P
char Name[50];( z4 v* W( ]. k) L% j/ A- m7 _
} Defaults;
7 O% L3 h6 n% W8 O0 z, A
0 R" n2 ^$ i( X; L( b2 U* J' F2 X 下一个窗口以Windwos通常的风格创建,样例代码通过如下几步设置保留模式应用程序:枚举设备,创建主D3DRM对象,创建主要的场景和摄像机,设置渲染品质等等。在窗口能被看见之前,InitApp()调用UVIS.CPP 中的BuildScene()函数。这是最激动人心的地方。在这里我们要讨论一下样例代码究竟是如何工作的:既然应用程序被初始化了,下一步就是循环。* H2 l7 M& |5 E, ]% Y
消息/渲染循环。一旦应用程序被初始化,WinMain()设置标准Windows消息循环。在这个循环中就包括了对RenderLoop()的调用。RenderLoop()在Direct3D保留模式例子中执行把对象渲染到屏幕上去的大部分工作。 RenderLoop()对三个不同的保留模式接口进行一系列四个调用。首先,它调用IDirect3DRMFrame::Move()对所有框架应用旋转和速度。然后调用IDirect3DRMViewport::Clear()清除当前的视口和设置背景颜色。下一步是调用IDirect3DRMViewport::Render()把当前场景渲染到当前视口上。最后IDirect3DRMDevice::Update()复制渲染的图像来显示。
# f0 w' S' i5 m1 z 通过IDirect3DRMViewport::Render()调用,所有的工作都实际上完成了。在这里,系统调用了场景中的每一个对象,通知对象渲染它自己。又及,这也是我们将要在下个部分看到的,我们在暗中通过立即模式把对象渲染到保留模式应用程序中。
% i, C. `6 x$ ?: N2 ` ) K6 ~- ~& r) k- M$ l" P/ k$ l
3.4 UVIS.CPP做了些什么& j5 f' E, W4 `1 }$ Q, x; _/ l+ ]
我们在这部分里讲到的虽然很简单,但却是保留模式应用程序的标准框架。现在我们来看看UVIS演示的技术(这也是本文的焦点):在保留模式中使用用户可视对象的能力。
3 i5 P4 V \$ I; }2 z3 G 正如我们在RMMAIN.CPP看到的,InitApp()调用在UVIS.CPP中定义的BuildScene()函数。我们不是在保留模式应用程序中仅仅增加一堆保留模式对象,代替它的是在对象上增加了一个用户可视对象,代表执行缓冲和它的创建以及渲染例程。用户可视对象是一个简单的用户定义的可视对象,与其它的预定义的可视对象一样增加到场景中,而由开发人员提供创建和渲染例程。# Z% C# m+ W& K: d- ^, ]0 r
设置用户可视对象。BuildScene()从为场景创建一些灯光开始。然后调用CreateFire(),这个函数实际创建了在用户可视对象中用到的立即模式对象。让我们通过实际代码看看它是怎么做的。
2 N1 ^2 `0 V$ L9 l+ w0 \0 | 首先,看看在UVIS.CPP中定义的文件结构。* L! Y/ t I) T: P9 b: e ]1 d5 z
8 Y8 I$ C( p ^% H& a6 B1 A5 U% j" q typedef struct _Fire {
9 e0 ` M/ ]* f3 j Flame flames[MAX_FLAMES];+ j$ V% P, l( i( w2 p- l7 |
LPDIRECT3DRMDEVICE dev;
7 i. A6 Q! J x$ J6 E. [1 P LPDIRECT3DEXECUTEBUFFER eb;8 O0 F4 {9 q Q! }2 o8 ?4 M3 b+ x
LPDIRECT3DMATERIAL mat;
# \- |5 o( G( W8 ]3 ]* {* z* ^) ] } Fire;
0 U; Y9 |4 {$ |
_7 M- w! K. O" c. V# e1 R7 p2 D CreateFire()创建了一个文件结构,包含我们要创建的用户可视对象(带有几个火焰的火)的信息。文件结构包含每一个火焰的数据(包括火焰的位置、速度、生命期等)。指向保留模式设备,执行缓冲和材质。结构初始化为空。
+ a1 B% }2 {4 C# X- n7 b+ x+ e# ?
l! T7 ~3 W: D4 T- I+ X7 G2 J Fire* fire;
4 P) s, x" {3 U& \8 H fire = (Fire*)malloc(sizeof(Fire));
$ @- F1 d6 ~8 @2 N- l2 x if (!fire)$ ^( O% }1 j9 d
goto ret_with_error;
/ t$ V" _7 O7 B memset(fire, 0, sizeof(Fire));4 E0 E) U- _4 [0 F1 K; l
! G5 l6 U9 {. b) M) [5 \ IDirect3DRM::CreateUserVisual()函数创建一个用户可视对象,并传递会在uvis变量中这个对象的地址。同这个对象关联的是应用程序定义的数据(在本例中是Fire结构)和回调(在本例中是FireCallback())。在系统想要应用程序渲染用户可视对象的时候这些被调用。
, G1 S [1 `3 Q8 c$ _5 N+ ]
& X' i' S; `/ e [ LPDIRECT3DRMUSERVISUAL uvis = NULL;
: V0 J. H, g+ I" m if (FAILED(lpD3DRM->CreateUserVisual(FireCallback, (void*) fire, &uvis)))4 n: f: o. M2 F/ t( u# V& Q& S
goto ret_with_error;
( H. |7 n1 V" m1 @8 t The DestroyFire() callback will be called when the user visual needs to be destroyed:
# G0 X! C& u* \% Y0 L( O$ T if (FAILED(uvis->AddDestroyCallback(DestroyFire, (void*) fire)))/ [; X) r$ t* T, [
goto ret_with_error;/ b6 `6 r8 ^$ K- G9 d
& h( a) e# H. ^, {0 T7 L3 i 在BuildScene()调用CreateFire()设置用户可视对象之后,就在场景中增加了这个对象。注意,下面的uvis变量是一个不同的变量,但和上面提到的uvis包含同一个值,这个值是CreateFire()函数返回的。
+ z8 |( Q: t) O3 ?
( }# r E7 z& z* P uvis = CreateFire();
w/ V1 R& V/ s- k L if (!uvis)) W0 z) m4 I) ~, ]
goto generic_error;* ?& U- T6 C5 t1 t6 a8 w( T
if (FAILED(frame->AddVisual(uvis))), u6 k0 S' M5 {: P$ l8 c' X7 k
goto generic_error;. F) V! F, w+ _1 ^ V
6 E+ Q5 s) q9 y" } 现在,用户可视对象已经被创建了。现在它仅仅是约束到场景中的一个空结构。当程序开始渲染循环的时候,系统尝试渲染场景中的每一个对象。当到达用户可视对象的时候,调用FireCallback(),而FireCallback()实际调用RenderFire()进行渲染的工作。- K7 G+ t2 i/ o- U3 k
渲染用户可视对象。每次渲染循环RenderFire()都要被调用。创建和维护“火焰”包括如下几步:首先是CreateFireObjects(),它只在渲染循环开始的第一次被调用。这个函数首先从Direct3DRM设备中得到一个指向Direct3D设备的指针,如下:$ o; i* A6 L. ~/ o4 X
7 A& r2 E5 O* A: a/ N dev->GetDirect3DDevice(&lpD3DDev);$ C# H& y6 P0 R$ c7 u
if (!lpD3DDev)
0 k. F( u: j A/ s% N; o goto generic_error;/ E$ j( {& O, Y& g0 |
if (FAILED(lpD3DDev->GetDirect3D(&lpD3D)))6 a, F9 h( l' U
goto generic_error;7 f* u$ E/ s6 K1 @0 @% k. H V. y
( e# p/ e) Z9 T, B) _ H- c S
下一步,如同典型的立即模式应用程序做的那样,创建和填充执行缓冲:设置材质、灯光和明暗状态;创建三角形等等。7 x- O# l$ S5 t$ `9 i9 C
每次遍历渲染循环的时候,RenderFire()都要检查每一个火焰,看看它是否已经“燃尽”,不再有效(这也意味着预先设置的生命期结束了)。火焰“燃尽”的时候(或者火焰第一次生成的时候),InitFlame()被调用,它为火焰设置了生命期、速度和分配一个随机位置。下一步调用UpdateFlame(),根据当前时间为每个火焰更新位置和大小。& Z# U8 {5 D/ t/ h8 w
最后,设置RenderFire()更新火焰。它调用IDirect3DDevice::Execute()。这个函数实际处理执行缓冲并使它渲染到屏幕上。
0 Q' z% g% B, V1 o7 R0 H& g3 b9 F 4 E7 M4 w2 m" }7 U8 k0 Y
4、结论
5 I( e: a3 ^8 F% u9 }: y7 e$ n. h 使用立即模式通过用户可视对象创建应用程序的对象,你能够得到一个更完美的使用Direct3D保留模式的3D世界。记住:保留模式能够处理所有通用的对象,而立即模式能够渲染任何定制的对象或者得到你特别需要的结果。 |
|