找回密码
 注册
搜索
查看: 4802|回复: 0

[收藏]混合使用Direct3D立即模式和保留模式

[复制链接]
发表于 2005-4-7 20:03:53 | 显示全部楼层 |阅读模式
    1、介绍5 z: p* h' t- U4 w
  微软的Direct3D包括了两个截然不同的API。高级的保留模式提供了场景和对象管理服务以及构建几何引擎。低级的立即模式API提供了直接访问硬件并允许熟练的3D程序员执行自己的渲染和场景管理。这种方式在灵活性和执行性能方面都优于保留模式。+ d2 i9 W+ I/ w8 K  R1 g3 e
  大多数关于Direct3D的资料都把这两种API分开讲解,许多人也以为它们是互不相关的。实际上,我们要指出,在一个程序中,你或许需要同时使用这两种API。+ Y2 L$ \! N3 k5 D" e
  在这篇文章中,讨论同时使用保留模式和立即模式API的两种情形:
0 E( u0 C6 u; c1 w7 a  5 I0 t8 e  U2 j; X
  •环枚举设备驱动器
5 v! @, S$ L+ y  •环在保留模式应用程序中作为用户可视对象使用执行缓冲
( T4 m: O3 i+ i. H2 E  $ c& L- e9 Y* X& \
  枚举设备驱动器是一种直截了当的操作,比较简单。在保留模式应用程序中使用执行缓冲是这篇文章的重点。
& X1 C) m+ R) c/ u6 I5 b  本文假设你已经了解了Direct3D。本文不是Direct3D的概述或教程。本文讨论了Direct3D联机文档中没有讲到的问题。这也就是说,你最好先看看Direct3D的文档。
% o7 `) E7 v0 V+ {" f; e4 F# i  注意,在一般的词汇上讨论Direct3D时,通常使用Direct3D这个词代指整个的3D API,既包括保留模式又包括立即模式。但是,根据Drect3D代码的约定,使用D3D表示立即模式接口或变量,D3DRM表示保留模式接口或变量。由于这种约定,在讨论样例代码或专用接口的时候,Direct3D有时候指的是立即模式而不是整个的3D API集。本文试图划清界限,但你也要意识到,样例代码和注释中也许也会在两种意义下都用Direct3D这个词。
; F0 k% f0 V2 G- U7 n5 T" p6 R  2 p  [2 }% A" _5 B, ?
  2、枚举设备驱动器
6 A  h+ a3 i- `8 f$ e  应用程序使用Direct3D,无论是立即模式还是保留模式,通常需要实时地在用户计算机上枚举可获得的(图形)驱动器。如果图像品质比渲染速度更重要的话,应用程序应该选择最高的位深(bit depth)和(或)分辨率。另一方面,如果需要高速渲染,应用程序会牺牲一些图像质量来换取性能。
/ ], |8 v! Z% W0 [# D1 Z" c) d5 d+ Q  保留模式没有单独包含一个枚举驱动器的方法。代替它的是,所有的Direct3D应用程序都使用IDirect3D::EnumDevices方法。DirectX SDK中的代码样例展示了如何使用这种方法。可以参看Direct3D帮助文件中的保留模式教程。使用这种方法枚举驱动器并不困难。我在只这里稍微说一下,读者可以参考DirectX联机文档获取全部的说明。
+ ^$ q3 E. B8 P/ y& s8 u/ G5 I  当开发人员需要在保留模式应用程序中调用立即模式API的时候,最显而易见的问题是:“我如何得到一个指向立即模式Direct3D的指针”?这个很简单:因为Direct3D COM接口准备了一个指向DirectDraw的接口,你可以通过这个DirectDraw接口获得Direct3D指针(记住,这里的Direct3D意味着立即模式)。这个可以简单的分为两步:
8 N3 r5 C% s) a2 W2 T% ~- \. m. T6 O  6 N- O- B- B0 r- X. h
  LPDIRECTDRAW lpDD;( c5 X/ C( A6 O' _( J! {+ ?
  LPDIRECT3D lpD3D;
& x& U& A7 z/ p! l" E. K  HRESULT rval;  d6 B3 v  ~, A6 v
  # L" [6 Z. R/ s7 K$ {
  DirectDrawCreate (NULL, &lpDD, NULL);
6 G  k! K: J' W( w8 R0 R  rval = lpDD->lpVtbl->QueryInterface(lpDD, &IID_IDirect3D, (void**) &lpD3D);; i, J* a. c3 T$ ~" @$ R1 n" Y2 k/ I" _
  ) P) B6 N4 Z6 g4 f! p. |
  看到了吗?是不是很简单?现在我们已经有了一个指向Direct3D接口的指针,接下来,我们就可以轻松地调用IDirect3D::EnumDevices方法来枚举可获得的设备了。从现在开始,需要做的工作同立即模式应用程序一模一样:定义一个枚举回调例程,传递地址给IDirect3D::EnumDevices方法。回调函数将会被每一个系统上安装的驱动器调用。从而检查每个驱动器的特性来确定是否适合应用程序的需要。详细内容请参考具体的代码。  j+ o# h8 I, W0 O7 D
  现在,该看看我们更感兴趣的另一个问题:混合使用Direct3D的两种模式。
. m: n# D; Y( S4 W/ E& [  
& T! S& w: R# O' b. Q  3、在保留模式中使用执行缓冲/ @4 L$ D1 V% D& x9 v8 g
  有的时候,应用程序可能想要使用执行缓冲(允许执行自己的变换、灯光或光栅等)。但同时还要使用保留模式中提供的更方便的API函数。这可以通过把执行缓冲看作是Direct3D中的可视对象来处理。) x0 P% b4 Y( R
  在SDK中,可以找到一个叫做UVIS(User VISual)的例子,这是一个“燃烧的火焰”,它演示了这种技术,我们就以此为例。如果你是那种喜欢自己读代码,自己完成一切的人,那可以不必看下面的内容。如果你喜欢看详细的解释,那么让我们一起来看看这段代码,并讨论其中的几个主要问题。
- @7 J& s; s1 ?7 h# K  * c9 D1 ?- Z7 X: y; J/ t
  3.1 编译准备
8 W( |! t" T& l6 S! A( F/ z; N0 v  如果你明白怎么编译DirectX程序,那么可以不必向下看。如果你以前从未编译过任何DirectX代码,那么看看下面的内容。我们使用微软的Visual C++ 5.0编译UVIS样例。假设你的DirectX所在的路径是C:\dxsdk\sdk,那么:
6 A. ?3 `: T3 p3 o  1)创建一个新的的project workspace,在其中增加uvis.cpp、rmmain.cpp、rmerror.c三个文件
$ j) s7 V5 I) Q$ |  2)在Tools/Options/Directories中选Include files,加入DirectX头文件所在的路径,如:c:\dxsdk\sdk\include
) I6 x! v9 Y* C, C  3)在Tools/Options/Directories中选Library files加入DirectX库文件所在的路径,如:c:\dxsdk\sdk\lib/ I8 l, ^% g: a9 w  f" X
  4)在Project/Settings/Link中的Object/Library Modules加入链接时需要的库:d3drm.lib、ddraw.lib、winmm.lib* \; W/ X) k1 Q& W6 x& V) z
  4 F2 N, S: V% W5 i, o* G$ D# G) S
  3.2 Direct3D保留模式样例的组织, {1 O- `9 H: m9 e. T
  为了简化,在Direct3D SDK中所有保留模式样例共享一些通用代码。这使你能够把精力集中在核心代码上,而不必在通用的代码上浪费时间。保留模式通用代码包括两个文件:RMMAIN.CPP和RMERROR.C。
9 U" |. N# p8 l1 f* a. m' o  通用部分包括创建和管理标准Windows应用程序以及执行基本保留模式初始化和处理的代码。在下面的内容中,我们来看看UVIS的主要部分。我们不会详细的讨论每一个函数调用。我的目的不是解释保留模式或立即模式的用法,而是展示它们如何在一个应用程序中同时被使用。当然,我也希望这篇文章能让你迅速的理解如何把这些代码组织在一起。
6 u: e6 @' m+ F  
% A0 j" ~5 B8 |, r, P  3.3 RMMAIN.CPP做了些什么3 S' R2 H7 h/ z8 ]+ A, M
  在RMMAIN.CPP中的代码形成了Direct3D保留模式应用程序的一般框架。最重要的部分包含在WinMain()函数中,在这个函数里,由两个主要的部分:应用程序安装和初始化阶段以及消息循环,实际的渲染就发生在这个阶段。
1 a+ c9 ^- q: O  @1 ?  应用程序安装和初始化。在InitApp()函数中,压缩了大部分的初始化代码。这个函数是最普通的。它先安装通常的窗口类,然后是初始化一些全局遍量。紧接着是调用OverrideDefaults(),这个函数通过填充如下结构定义了一些它自己的设置:/ i; I9 i7 R/ Z1 L- B8 z, ~4 j6 n
  " _9 i/ m% c) i% [$ H
  typedef struct Defaultstag {! t3 t3 K/ W+ e: b, u
  BOOL bNoTextures;! {0 [; h$ E. x& q+ A# b
  BOOL bResizingDisabled;
& ?6 ^% |' E3 M* [# u: `6 v% m3 a  BOOL bConstRenderQuality;
0 I. n+ h+ x: i/ e, |! O+ Q  char Name[50];+ e6 \0 ?  i: h7 d4 h
  } Defaults;" w; v  R2 l: o3 V# b8 ~
  8 H2 \& w7 v( p! Y1 i) m- D! n
  下一个窗口以Windwos通常的风格创建,样例代码通过如下几步设置保留模式应用程序:枚举设备,创建主D3DRM对象,创建主要的场景和摄像机,设置渲染品质等等。在窗口能被看见之前,InitApp()调用UVIS.CPP 中的BuildScene()函数。这是最激动人心的地方。在这里我们要讨论一下样例代码究竟是如何工作的:既然应用程序被初始化了,下一步就是循环。& L( M) [6 e1 `
  消息/渲染循环。一旦应用程序被初始化,WinMain()设置标准Windows消息循环。在这个循环中就包括了对RenderLoop()的调用。RenderLoop()在Direct3D保留模式例子中执行把对象渲染到屏幕上去的大部分工作。 RenderLoop()对三个不同的保留模式接口进行一系列四个调用。首先,它调用IDirect3DRMFrame::Move()对所有框架应用旋转和速度。然后调用IDirect3DRMViewport::Clear()清除当前的视口和设置背景颜色。下一步是调用IDirect3DRMViewport::Render()把当前场景渲染到当前视口上。最后IDirect3DRMDevice::Update()复制渲染的图像来显示。% W* x- e& f6 p4 Q; ^8 n
  通过IDirect3DRMViewport::Render()调用,所有的工作都实际上完成了。在这里,系统调用了场景中的每一个对象,通知对象渲染它自己。又及,这也是我们将要在下个部分看到的,我们在暗中通过立即模式把对象渲染到保留模式应用程序中。& Q' G; ~9 M0 V& Z3 h8 `
  
% W, [+ |. S2 M- G5 S) r1 R' E& U  3.4 UVIS.CPP做了些什么
/ ]% s3 {7 ^8 E6 G0 |9 B/ P% J  我们在这部分里讲到的虽然很简单,但却是保留模式应用程序的标准框架。现在我们来看看UVIS演示的技术(这也是本文的焦点):在保留模式中使用用户可视对象的能力。
; N" U; S6 j0 \' r2 }7 o  正如我们在RMMAIN.CPP看到的,InitApp()调用在UVIS.CPP中定义的BuildScene()函数。我们不是在保留模式应用程序中仅仅增加一堆保留模式对象,代替它的是在对象上增加了一个用户可视对象,代表执行缓冲和它的创建以及渲染例程。用户可视对象是一个简单的用户定义的可视对象,与其它的预定义的可视对象一样增加到场景中,而由开发人员提供创建和渲染例程。8 {7 f; ^% J$ T& d$ N& G8 C" \
  设置用户可视对象。BuildScene()从为场景创建一些灯光开始。然后调用CreateFire(),这个函数实际创建了在用户可视对象中用到的立即模式对象。让我们通过实际代码看看它是怎么做的。
) U; s1 F3 y: b  首先,看看在UVIS.CPP中定义的文件结构。$ @7 \3 C( d0 ?$ _! @( f
  
* e) }: M0 N8 A) R; x% T8 e  typedef struct _Fire {. f* }# ]' r+ g; g0 w3 R- I$ t
  Flame flames[MAX_FLAMES];
/ z1 @6 B. P8 V6 D; o  LPDIRECT3DRMDEVICE dev;
/ X: x5 c' e# L- p" e. `+ h  LPDIRECT3DEXECUTEBUFFER eb;9 R$ o# W. n; M: A; F
  LPDIRECT3DMATERIAL mat;3 U1 O$ m* o5 o
  } Fire;
+ Z1 I, n6 v+ Y* l  
' h+ [3 S4 r0 g; S3 c% Y' p- G  CreateFire()创建了一个文件结构,包含我们要创建的用户可视对象(带有几个火焰的火)的信息。文件结构包含每一个火焰的数据(包括火焰的位置、速度、生命期等)。指向保留模式设备,执行缓冲和材质。结构初始化为空。8 z+ h$ W% U5 U1 {$ V" T8 @
  
, v7 ~, Y+ A% F* u$ l0 d; [  Fire* fire;
3 k, A; z! N4 u# s/ W4 I  fire = (Fire*)malloc(sizeof(Fire));' H3 e: G# ?4 h: R# K
  if (!fire)- E4 \7 U2 F+ R3 i
  goto ret_with_error;& v* B+ n' s& a
  memset(fire, 0, sizeof(Fire));9 Q7 ~9 v, \1 `' e+ f) i' K
  3 U5 g0 M; A5 w% B
  IDirect3DRM::CreateUserVisual()函数创建一个用户可视对象,并传递会在uvis变量中这个对象的地址。同这个对象关联的是应用程序定义的数据(在本例中是Fire结构)和回调(在本例中是FireCallback())。在系统想要应用程序渲染用户可视对象的时候这些被调用。( h% _6 B/ k9 |8 S% R' x/ p$ V
  
5 N4 ?* p$ E8 V1 w+ N  LPDIRECT3DRMUSERVISUAL uvis = NULL;/ h" K/ a: T1 x# h
  if (FAILED(lpD3DRM->CreateUserVisual(FireCallback, (void*) fire, &uvis)))' W/ D6 i2 C" t: o+ u
  goto ret_with_error;% H) y) m/ ^8 F& @0 |- r. \
  The DestroyFire() callback will be called when the user visual needs to be destroyed:
) U& o2 v" ]9 y" H. U$ X5 B  if (FAILED(uvis->AddDestroyCallback(DestroyFire, (void*) fire)))4 `- U# k. @; I9 _, S, Z0 E
  goto ret_with_error;
" _4 g1 H+ Z. u4 n4 O3 z0 P  
! C3 M( C' _; q5 H+ Y  i' O9 f* B  在BuildScene()调用CreateFire()设置用户可视对象之后,就在场景中增加了这个对象。注意,下面的uvis变量是一个不同的变量,但和上面提到的uvis包含同一个值,这个值是CreateFire()函数返回的。
$ @* u+ L1 l% n8 b3 d  / s) \$ B$ h! F/ Q. ~
  uvis = CreateFire();
' U+ c9 b7 k- k& Y' k  _) l7 l  if (!uvis)
6 n9 n" a) q* V  goto generic_error;4 r1 ]8 p- w5 K2 l  O6 N6 o; [! \
  if (FAILED(frame->AddVisual(uvis)))  g" q  Y$ V8 X5 }0 ?& t! i& v' Y
  goto generic_error;
! D1 E, F- A8 m2 N0 q$ l  
" R1 x$ f" K- s7 c  现在,用户可视对象已经被创建了。现在它仅仅是约束到场景中的一个空结构。当程序开始渲染循环的时候,系统尝试渲染场景中的每一个对象。当到达用户可视对象的时候,调用FireCallback(),而FireCallback()实际调用RenderFire()进行渲染的工作。: }: W! l2 I4 P2 \: f! q1 Q# s
  渲染用户可视对象。每次渲染循环RenderFire()都要被调用。创建和维护“火焰”包括如下几步:首先是CreateFireObjects(),它只在渲染循环开始的第一次被调用。这个函数首先从Direct3DRM设备中得到一个指向Direct3D设备的指针,如下:) P: B! F7 h( @$ C+ N" G% \
  " _  Y+ ]1 y8 \! }
  dev->GetDirect3DDevice(&lpD3DDev);: G* ?4 G" v, P0 s. {
  if (!lpD3DDev)& g' D! |' Q/ }0 s, P3 H
  goto generic_error;
# E" a) u! p+ J$ X  if (FAILED(lpD3DDev->GetDirect3D(&lpD3D)))
6 l  ?+ d# ~$ ?( Q: R  goto generic_error;+ E+ R5 q4 ~- f& R5 l0 u+ P3 z
  
: ?" s# A. I0 O4 ]  下一步,如同典型的立即模式应用程序做的那样,创建和填充执行缓冲:设置材质、灯光和明暗状态;创建三角形等等。& g! S1 L- \, u( e7 o- w0 x7 u
  每次遍历渲染循环的时候,RenderFire()都要检查每一个火焰,看看它是否已经“燃尽”,不再有效(这也意味着预先设置的生命期结束了)。火焰“燃尽”的时候(或者火焰第一次生成的时候),InitFlame()被调用,它为火焰设置了生命期、速度和分配一个随机位置。下一步调用UpdateFlame(),根据当前时间为每个火焰更新位置和大小。
2 }5 L0 h1 D8 M* _  最后,设置RenderFire()更新火焰。它调用IDirect3DDevice::Execute()。这个函数实际处理执行缓冲并使它渲染到屏幕上。- c  j1 v- d' A4 @/ R0 K
  4 D2 U$ m" V2 y
  4、结论9 G0 Z) s' q* Z5 E2 _6 g* V$ r7 ]( G
  使用立即模式通过用户可视对象创建应用程序的对象,你能够得到一个更完美的使用Direct3D保留模式的3D世界。记住:保留模式能够处理所有通用的对象,而立即模式能够渲染任何定制的对象或者得到你特别需要的结果。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|宁德市腾云网络科技有限公司 ( 闽ICP备2022007940号-5|闽公网安备 35092202000206号 )

GMT+8, 2025-12-13 08:26 , Processed in 0.019349 second(s), 15 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表