|
1、介绍" C7 d% m) Y; b# _0 ?
微软的Direct3D包括了两个截然不同的API。高级的保留模式提供了场景和对象管理服务以及构建几何引擎。低级的立即模式API提供了直接访问硬件并允许熟练的3D程序员执行自己的渲染和场景管理。这种方式在灵活性和执行性能方面都优于保留模式。
, T' H( U& H1 _' T6 T% z 大多数关于Direct3D的资料都把这两种API分开讲解,许多人也以为它们是互不相关的。实际上,我们要指出,在一个程序中,你或许需要同时使用这两种API。( e- X& [) u* W y& H4 g
在这篇文章中,讨论同时使用保留模式和立即模式API的两种情形:- f5 h \ I- Q) ]" R" n
4 j" O7 c: E5 i' u$ P! t& {: _# y
•环枚举设备驱动器
& C4 m6 y5 T( o •环在保留模式应用程序中作为用户可视对象使用执行缓冲- d. s3 f( d" o$ ^, H# } h3 t
* E1 _; `: v& v% p/ U; c( E 枚举设备驱动器是一种直截了当的操作,比较简单。在保留模式应用程序中使用执行缓冲是这篇文章的重点。1 K$ s# w( S; N' H2 T) n
本文假设你已经了解了Direct3D。本文不是Direct3D的概述或教程。本文讨论了Direct3D联机文档中没有讲到的问题。这也就是说,你最好先看看Direct3D的文档。
# Q. H: J8 B. F" `4 O 注意,在一般的词汇上讨论Direct3D时,通常使用Direct3D这个词代指整个的3D API,既包括保留模式又包括立即模式。但是,根据Drect3D代码的约定,使用D3D表示立即模式接口或变量,D3DRM表示保留模式接口或变量。由于这种约定,在讨论样例代码或专用接口的时候,Direct3D有时候指的是立即模式而不是整个的3D API集。本文试图划清界限,但你也要意识到,样例代码和注释中也许也会在两种意义下都用Direct3D这个词。
4 O/ v; [; g( e+ r3 @9 Y+ x $ y/ [6 w& ?, ?- Y/ ~
2、枚举设备驱动器$ h! }3 t7 ^0 M' P# b4 {* T, x
应用程序使用Direct3D,无论是立即模式还是保留模式,通常需要实时地在用户计算机上枚举可获得的(图形)驱动器。如果图像品质比渲染速度更重要的话,应用程序应该选择最高的位深(bit depth)和(或)分辨率。另一方面,如果需要高速渲染,应用程序会牺牲一些图像质量来换取性能。
& k0 C$ U2 k3 N 保留模式没有单独包含一个枚举驱动器的方法。代替它的是,所有的Direct3D应用程序都使用IDirect3D::EnumDevices方法。DirectX SDK中的代码样例展示了如何使用这种方法。可以参看Direct3D帮助文件中的保留模式教程。使用这种方法枚举驱动器并不困难。我在只这里稍微说一下,读者可以参考DirectX联机文档获取全部的说明。+ T0 ?* @5 r0 r% f
当开发人员需要在保留模式应用程序中调用立即模式API的时候,最显而易见的问题是:“我如何得到一个指向立即模式Direct3D的指针”?这个很简单:因为Direct3D COM接口准备了一个指向DirectDraw的接口,你可以通过这个DirectDraw接口获得Direct3D指针(记住,这里的Direct3D意味着立即模式)。这个可以简单的分为两步:
0 _, w! x n2 U: d- [& C- ] , Z6 A; B$ t7 i; J7 \
LPDIRECTDRAW lpDD;$ U7 ?+ O. F, A# k/ f
LPDIRECT3D lpD3D;! e- a) o# J5 [$ \. u
HRESULT rval;
1 H7 k7 b9 |9 e8 }3 I
# j" ~$ F5 _8 O7 k! U# { DirectDrawCreate (NULL, &lpDD, NULL);
0 `9 { @$ k6 V8 ?# H5 @% d rval = lpDD->lpVtbl->QueryInterface(lpDD, &IID_IDirect3D, (void**) &lpD3D);- q- D( w9 P! t. Z j% Y. q" W
3 M- E1 _. b2 o" C: a 看到了吗?是不是很简单?现在我们已经有了一个指向Direct3D接口的指针,接下来,我们就可以轻松地调用IDirect3D::EnumDevices方法来枚举可获得的设备了。从现在开始,需要做的工作同立即模式应用程序一模一样:定义一个枚举回调例程,传递地址给IDirect3D::EnumDevices方法。回调函数将会被每一个系统上安装的驱动器调用。从而检查每个驱动器的特性来确定是否适合应用程序的需要。详细内容请参考具体的代码。
+ \4 S5 H# J8 c6 }9 J5 ] 现在,该看看我们更感兴趣的另一个问题:混合使用Direct3D的两种模式。
2 a% K" H4 E6 `/ j) o
, B' x* g8 V" I$ X 3、在保留模式中使用执行缓冲
2 L) ~4 R) I. J' F7 J5 \; P% c! | 有的时候,应用程序可能想要使用执行缓冲(允许执行自己的变换、灯光或光栅等)。但同时还要使用保留模式中提供的更方便的API函数。这可以通过把执行缓冲看作是Direct3D中的可视对象来处理。
* p$ R8 g/ } X 在SDK中,可以找到一个叫做UVIS(User VISual)的例子,这是一个“燃烧的火焰”,它演示了这种技术,我们就以此为例。如果你是那种喜欢自己读代码,自己完成一切的人,那可以不必看下面的内容。如果你喜欢看详细的解释,那么让我们一起来看看这段代码,并讨论其中的几个主要问题。2 T% y/ C7 i" X
6 T: e% A3 C1 m, J, U% D
3.1 编译准备
9 q0 m) t% d [. D& m4 s 如果你明白怎么编译DirectX程序,那么可以不必向下看。如果你以前从未编译过任何DirectX代码,那么看看下面的内容。我们使用微软的Visual C++ 5.0编译UVIS样例。假设你的DirectX所在的路径是C:\dxsdk\sdk,那么:
- _3 J" R/ s/ g 1)创建一个新的的project workspace,在其中增加uvis.cpp、rmmain.cpp、rmerror.c三个文件
& q# O2 D+ j4 B! P8 ~ 2)在Tools/Options/Directories中选Include files,加入DirectX头文件所在的路径,如:c:\dxsdk\sdk\include4 D* I& o. _2 G: K5 K9 [; c& q
3)在Tools/Options/Directories中选Library files加入DirectX库文件所在的路径,如:c:\dxsdk\sdk\lib5 a2 \3 Z% |& U0 r0 B
4)在Project/Settings/Link中的Object/Library Modules加入链接时需要的库:d3drm.lib、ddraw.lib、winmm.lib) N5 O; R2 R$ b* ~- w: W2 F
! K: T$ M2 ^* `2 X$ R
3.2 Direct3D保留模式样例的组织
# y8 {: R3 ]" Z& _ 为了简化,在Direct3D SDK中所有保留模式样例共享一些通用代码。这使你能够把精力集中在核心代码上,而不必在通用的代码上浪费时间。保留模式通用代码包括两个文件:RMMAIN.CPP和RMERROR.C。" h' P3 t2 u7 p) g
通用部分包括创建和管理标准Windows应用程序以及执行基本保留模式初始化和处理的代码。在下面的内容中,我们来看看UVIS的主要部分。我们不会详细的讨论每一个函数调用。我的目的不是解释保留模式或立即模式的用法,而是展示它们如何在一个应用程序中同时被使用。当然,我也希望这篇文章能让你迅速的理解如何把这些代码组织在一起。0 z3 G6 B, E/ m: }4 J
9 ]: B3 D4 m6 m+ A3 }4 D 3.3 RMMAIN.CPP做了些什么& N+ m6 V% Z& ~" `
在RMMAIN.CPP中的代码形成了Direct3D保留模式应用程序的一般框架。最重要的部分包含在WinMain()函数中,在这个函数里,由两个主要的部分:应用程序安装和初始化阶段以及消息循环,实际的渲染就发生在这个阶段。7 d8 J- b! v/ n7 ?- q) Y6 A- L
应用程序安装和初始化。在InitApp()函数中,压缩了大部分的初始化代码。这个函数是最普通的。它先安装通常的窗口类,然后是初始化一些全局遍量。紧接着是调用OverrideDefaults(),这个函数通过填充如下结构定义了一些它自己的设置:% e' |1 Q- d; J$ z
# [4 T6 \' m9 S0 `9 w typedef struct Defaultstag {
( \$ h _" X$ b0 G BOOL bNoTextures;; e$ S5 U+ F0 y0 o1 @) }' _9 L/ w
BOOL bResizingDisabled;* W. b1 A: a# ]8 F/ _$ u1 w
BOOL bConstRenderQuality;( Z3 E- ~0 z* o2 X
char Name[50];
0 v3 l' I. s4 e! ~ } Defaults;- _8 i- H& D4 L
( U5 q1 y7 i- G3 ~ 下一个窗口以Windwos通常的风格创建,样例代码通过如下几步设置保留模式应用程序:枚举设备,创建主D3DRM对象,创建主要的场景和摄像机,设置渲染品质等等。在窗口能被看见之前,InitApp()调用UVIS.CPP 中的BuildScene()函数。这是最激动人心的地方。在这里我们要讨论一下样例代码究竟是如何工作的:既然应用程序被初始化了,下一步就是循环。
* x* z3 D/ ^ Q } ?0 U 消息/渲染循环。一旦应用程序被初始化,WinMain()设置标准Windows消息循环。在这个循环中就包括了对RenderLoop()的调用。RenderLoop()在Direct3D保留模式例子中执行把对象渲染到屏幕上去的大部分工作。 RenderLoop()对三个不同的保留模式接口进行一系列四个调用。首先,它调用IDirect3DRMFrame::Move()对所有框架应用旋转和速度。然后调用IDirect3DRMViewport::Clear()清除当前的视口和设置背景颜色。下一步是调用IDirect3DRMViewport::Render()把当前场景渲染到当前视口上。最后IDirect3DRMDevice::Update()复制渲染的图像来显示。
* v7 P3 \" B$ o 通过IDirect3DRMViewport::Render()调用,所有的工作都实际上完成了。在这里,系统调用了场景中的每一个对象,通知对象渲染它自己。又及,这也是我们将要在下个部分看到的,我们在暗中通过立即模式把对象渲染到保留模式应用程序中。
$ r" M! w9 x% l9 x5 n0 z3 v; b0 \+ T
4 N* \, b W# L* V9 r; W* `: U 3.4 UVIS.CPP做了些什么, ^) r$ F6 \+ {! ?4 N! ?
我们在这部分里讲到的虽然很简单,但却是保留模式应用程序的标准框架。现在我们来看看UVIS演示的技术(这也是本文的焦点):在保留模式中使用用户可视对象的能力。
0 O3 D. ^' `8 N9 w7 U0 @" S 正如我们在RMMAIN.CPP看到的,InitApp()调用在UVIS.CPP中定义的BuildScene()函数。我们不是在保留模式应用程序中仅仅增加一堆保留模式对象,代替它的是在对象上增加了一个用户可视对象,代表执行缓冲和它的创建以及渲染例程。用户可视对象是一个简单的用户定义的可视对象,与其它的预定义的可视对象一样增加到场景中,而由开发人员提供创建和渲染例程。& X% y; d2 T: m% l( \) o
设置用户可视对象。BuildScene()从为场景创建一些灯光开始。然后调用CreateFire(),这个函数实际创建了在用户可视对象中用到的立即模式对象。让我们通过实际代码看看它是怎么做的。
+ Q1 T& [% d4 {! d! q 首先,看看在UVIS.CPP中定义的文件结构。/ p% J8 R; K3 U4 F, i) F/ \
. B8 s7 D0 _$ x4 z8 E typedef struct _Fire {# R8 j9 ~6 q, N
Flame flames[MAX_FLAMES];# K! X3 X5 e% |& G- _' O
LPDIRECT3DRMDEVICE dev;( b# u7 @" K3 h$ h. c: w E
LPDIRECT3DEXECUTEBUFFER eb;9 M& p0 |+ a; `% j( B
LPDIRECT3DMATERIAL mat;" { }! F0 _- r; F
} Fire;
Z# N$ s9 h8 a9 y5 F& N
) ], `! K# q6 p V: r% f CreateFire()创建了一个文件结构,包含我们要创建的用户可视对象(带有几个火焰的火)的信息。文件结构包含每一个火焰的数据(包括火焰的位置、速度、生命期等)。指向保留模式设备,执行缓冲和材质。结构初始化为空。
5 h( c9 Z/ f$ C P! `+ `0 m
' s$ b3 ~( j( l; Z" l Fire* fire;7 ]2 v- `" r2 i+ p1 W. p
fire = (Fire*)malloc(sizeof(Fire));0 Q7 m/ a( ?- K, u8 g1 {
if (!fire)
4 _: r! ]0 P9 G2 L8 R goto ret_with_error;
1 g% G, N* V) b Y8 G+ O. r; X2 |% ^ memset(fire, 0, sizeof(Fire));# w8 _& p( N. ^
* E0 ?% g( x6 g6 G5 N
IDirect3DRM::CreateUserVisual()函数创建一个用户可视对象,并传递会在uvis变量中这个对象的地址。同这个对象关联的是应用程序定义的数据(在本例中是Fire结构)和回调(在本例中是FireCallback())。在系统想要应用程序渲染用户可视对象的时候这些被调用。
# l0 n! n- ]( i1 z
& O3 J/ b: X2 h! b7 X9 j* f4 w LPDIRECT3DRMUSERVISUAL uvis = NULL;" E; X; }- ` {+ ]0 W) ]9 ^3 f
if (FAILED(lpD3DRM->CreateUserVisual(FireCallback, (void*) fire, &uvis)))" ~! h5 N5 E- x
goto ret_with_error;
- W8 |+ B; a0 m' t' S1 s The DestroyFire() callback will be called when the user visual needs to be destroyed:
& Y) R1 M9 a9 q! t- h8 |7 N if (FAILED(uvis->AddDestroyCallback(DestroyFire, (void*) fire))) R! l! D8 l( j& ?) M h
goto ret_with_error; ]* d+ P9 m6 o" [
% {' G" m7 ^- ~4 V. r2 a3 n, ^4 I 在BuildScene()调用CreateFire()设置用户可视对象之后,就在场景中增加了这个对象。注意,下面的uvis变量是一个不同的变量,但和上面提到的uvis包含同一个值,这个值是CreateFire()函数返回的。& X' X4 e7 I" Y. g# U1 ]
# P' J6 K9 N) S3 {1 \/ ~5 R1 P uvis = CreateFire();
: B) ^* F* N& f; j. @; S if (!uvis)/ `) Q1 x+ D( N. @# h
goto generic_error;8 t( ~6 s. }: c6 l8 P
if (FAILED(frame->AddVisual(uvis)))
. S" S& J, P2 z9 m- N% ? goto generic_error;
) r+ `+ R2 ]" i8 w9 Q 1 z5 R- |$ l/ u+ @- w
现在,用户可视对象已经被创建了。现在它仅仅是约束到场景中的一个空结构。当程序开始渲染循环的时候,系统尝试渲染场景中的每一个对象。当到达用户可视对象的时候,调用FireCallback(),而FireCallback()实际调用RenderFire()进行渲染的工作。! Q6 U! J2 U( z) L6 y$ t6 ^4 A. g
渲染用户可视对象。每次渲染循环RenderFire()都要被调用。创建和维护“火焰”包括如下几步:首先是CreateFireObjects(),它只在渲染循环开始的第一次被调用。这个函数首先从Direct3DRM设备中得到一个指向Direct3D设备的指针,如下:
) t5 F, N) W) K3 u' q4 ]+ U4 L ( T! D3 j2 T. d8 U! a; ^
dev->GetDirect3DDevice(&lpD3DDev);) m6 x; B' P2 K
if (!lpD3DDev)$ I o9 B$ Q3 x# g2 @
goto generic_error;, S# ~! l" X3 u9 w/ s3 L9 A& Q
if (FAILED(lpD3DDev->GetDirect3D(&lpD3D)))
/ b8 E# `* }8 c* P& O goto generic_error;" M/ W. m9 R$ Y2 C& P u
5 U( `4 X# P% K) I2 \0 s6 g, v 下一步,如同典型的立即模式应用程序做的那样,创建和填充执行缓冲:设置材质、灯光和明暗状态;创建三角形等等。
! e) M$ Q' A9 ]% o 每次遍历渲染循环的时候,RenderFire()都要检查每一个火焰,看看它是否已经“燃尽”,不再有效(这也意味着预先设置的生命期结束了)。火焰“燃尽”的时候(或者火焰第一次生成的时候),InitFlame()被调用,它为火焰设置了生命期、速度和分配一个随机位置。下一步调用UpdateFlame(),根据当前时间为每个火焰更新位置和大小。
& e, w, L" I! O+ ? 最后,设置RenderFire()更新火焰。它调用IDirect3DDevice::Execute()。这个函数实际处理执行缓冲并使它渲染到屏幕上。9 C) L# ~+ \/ R; r
: q7 G" I E! y) d2 @2 r. s
4、结论
g2 ^, S& |3 L$ _1 x 使用立即模式通过用户可视对象创建应用程序的对象,你能够得到一个更完美的使用Direct3D保留模式的3D世界。记住:保留模式能够处理所有通用的对象,而立即模式能够渲染任何定制的对象或者得到你特别需要的结果。 |
|