本文对与Microsoft DirectX 8.0版有关的常见开发问题进行解答,其中包括有关Direct3D、DirectSound及DirectPlay的章节。
问:我在试图编译示例时,为何得到那么多错误消息?
答:您可能没有将 include 路径设置正确。许多编译器(Microsoft Visual C++)包含 SDK 的一个较早版本,因此如果您的 include 路径首先搜索标准的编译器 include 目录,则您会得到不正确版本的头文件。为解决这一问题,请确保 include 路径和库路径被设为搜索 DirectX include 和库路径。另请参见 SDK 中的dxreadme.txt 文件。如果您安装 DirectX SDK 而您又在使用 Visual C++,则可以选择让安装程序为您设置各个 include 路径。
问:我得到关于全局唯一标识符(GUID)符号重复或缺失的连接器错误,怎么办?
答:您使用的各种 GUID 应该得到一次性定义,且只能定义一次。如果您在插入 DirectX 头文件之前用 #define 定义 INITGUID 符号,则会插入 GUID 的定义。因此,你应确保只对一个编译单元进行此类操作。这一方法的一个替代方案就是用 dxguid.lib 库进行连接,其中包含所有 DirectX GUID 的定义。如果您使用这一方法(建议),则您永远不要通过 #define 定义 INITGUID 符号。
问:我能否将指针指向一个到较低版本号的 DirectX 接口?
答:不能。DirectX 接口属于 COM 接口 。这意味着不要求将较高版本号的接口从相应的低版本号导出。因此, 获得到 DirectX 对象的一个不同接口的唯一安全方法就是使用接口的 QueryInterface 方法。该方法是标准的 IUnknown 接口的一部分,所有 COM 接口必须从其导出。
问:我能在同一应用程序中将 DirectX 8 组件和DirectX 7 或更早的组件混用吗?
答:您可以随意混用不同版本的“不同组件”;例如,您可以在将 DirectPlay 8 和 DirectDraw 7 用在同一应用程序中。但是,您通常不可以将“同一组件”的不同版本混用在同一应用程序中;例如,您不能混用 DirectDraw 7 和 Direct3D 8 (鉴于这些实际上是同一组件,因为 DirectDraw 已被含入 DirectX 8 的 Direct3D)。
问:Release 或 AddRef 方法的返回值有何含义?
答:返回值将是对象的当前参照计数。但是,COM 规范声明,您不应依赖该返回值;该值通常仅供用于调试目的。您观察到的值可能并非所期待的,因为各种其它系统对象可能保持着对你所创建的 DirectX 对象的参照。因此,您不应编写反复调用 Release 的代码,一直到参照计数为零,因为此时可以将对象释放,即使另一组件可能仍旧在对其进行参照。
问:我释放 DirectX 接口的次序很重要吗?
答:应当没有问题,因为 COM 接口是参照计数的。但是,在某些 DirectX 版本中,接口的释放次序有一些已知的缺陷。安全起见,在可能的情况下,建议您以与创建时相反的次序释放接口。
问:智能指针是什么,我要用它们吗?
答:智能指针是一个 C++ 模板类,旨在封装指针功能。尤其有一些标准智能指针类,用于封装 COM 接口指针。这些指针自动进行 QueryInterface,而不是进行造型,并替您处理 AddRef 和 Release。您是否使用这些指针,大体上是个人偏好。如果您的代码包含大量的接口指针复制操作,即使用多重 AddRefs 和 Releases,则智能指针可能会使您的代码更加简洁和不易出错。否则,不用也罢。Visual C++ 包含一个标准的 Microsoft COM 智能指针,是在 "comdef.h" 头文件中定义的(请在帮助中查找 com_ptr_t)。
问:我在调试 DirectX 应用程序时遇到问题,能提示一下吗?
答:调试 DirectX 应用程序时最常见的问题就是试图在 DirectDraw 表面被锁定时进行调试。这一情形会在 Windows 9x 系统上导致 “Win16 Lock”,因此而无法绘制调试窗口。在锁定表面时指定 D3DLOCK_NOSYSLOCK 标志,通常会消除该现象。Windows 2000没有这一问题。在开发一个应用程序时,最好运行调试版本的 DirectX 运行时(在安装 SDK 时进行选择);该版本进行某些参数证实, 并将一些有用的消息输出到调试程序的输出窗口。
问:如何正确地检查返回代码?
答:使用 SUCCEEDED 和 FAILED 宏。 DirectX 方法可以返回多个成功和失败代码,因此一个简单的“==D3D_OK”或类似的测试结果不总是够用的。
问:DirectDraw 有何变化?
答:DirectDraw 的大多数功能现已被含入新的 Direct3D8 接口。编制单纯 2D 应用程序的开发人员可能会希望继续使用旧的 DirectX 7 接口。对于编制带有某些 2D 元素的 3D 应用程序的开发人员,鼓励其使用 Direct3D 替代程序(例如点对象和公告牌纹理),因为这会改善性能和灵活性。
问:我如何禁用 ALT+TAB 以及其它的任务切换功能?
答:请切勿这样做。
问:有什么值得推荐的对 COM 进行解释的书吗?
答:《Inside COM》,Dale Rogerson编写,Microsoft Press出版,其中对 COM 进行了很好的介绍。如要详细考察 COM,《Essential COM》,Don Box编写,Longman出版,这本书也很值得推荐。
问:有什么关于一般性 Windows 编程的书吗?
答:很多。但笔者认为值得推荐的书有:《Programming Windows》,Charles Petzold编写,Microsoft Press 出版。《Advanced Windows》,Jeffrey Richter编写,Microsoft Press 出版。
问:我在何处可以找到有关 3D 图形技巧的信息?
答:关于这一主题的标准书是《Computer Graphics: Principles and Practice》,Foley、Van Dam等编写;对于任何想要了解几何、光栅化以及照明技巧的人来讲,这都是一个宝贵的资源。comp.graphics.algorithms 新闻组的常见问题解答也包含有用的资料。
问:Direct3D 能够仿真硬件未提供的功能吗?
答:这要依具体情况而定。Direct3D 具有一个特性齐全的顶点处理流水线(包含对定制顶点着色器的支持)。但是,没有为像素级操作提供任何的仿真功能;应用程序必须检查相应的特征位,并使用 ValidateDevice API 来确定是否支持。
问:Direct3D 包含有软件光栅器吗?
答:没有。Direct3D 现在支持插件式软件光栅器。但是,目前并不默认提供任何软件光栅器。
问:Direct3D 几何代码使用 3DNow!或 Pentium III SIMD 指令吗?
答:使用。Direct3D 几何流水线有多个不同的代码路径(具体取决于处理器类型),并将使用 3DNow! 或 Pentium III SIMD 指令所提供的特殊的浮点操作(如果这些指令可用的话)。其中包括定制顶点着色器的处理。
问:我如何防止透明的像素被写到 z 缓冲区?
答:您可以借助高于或低于某一给定门限的 alpha 值来将像素滤除。您通过描绘状态 ALPHATESTENABLE、ALPHAREF 和 ALPHAFUNC 来控制这一操作。
问:模板缓冲区是什么?
答:模板缓冲区是一个记录每个像素信息的附加的缓冲区,很象一个 z 缓冲区。实际上,该缓冲区就驻在 z 缓冲区的某些位中。常见的模板/z 缓冲区格式为 15 位的 z 和 1 位的模板,或 24 位的 z 和 8 位的模板。在描绘多边形时,可以针对每个像素,对模板的内容进行简单的算术操作。例如,可以增加或减少模板缓冲区,或在模板值没能通过一项简单的比较测试时,拒绝像素。可以将帧缓冲区的一个区域标出,然后只对标出(或未标出)的区域进行描绘,上述操作对于此类效果十分有用。各种体积效果就是很好的例子,比如阴影量。
问:我如何使用模板缓冲区来描绘阴影量?
答:这一效果以及其它体积模板缓冲区效果的关键在于模板缓冲区和 z 缓冲区之间的交互作用。带有阴影量的场景是分三个阶段描绘的。首先,照常使用 z 缓冲区来描绘没有阴影的场景。然后,在模板缓冲区中将阴影标出,如下所示。使用不可见的多边形绘制阴影量的正面,其中 z 测试被启用,而 z 写入被禁用,且在每有一个像素通过 z 测试时,就将模板缓冲区增加一次。以同样方式描绘阴影量的背面,但是要减少模板值。
现在,请考虑单独一个像素的情形。假设摄像机不在阴影量中,场景中的相应点就有有四种可能性。如果从摄像机到点的光线不与阴影量相交,则不会绘制任何的阴影多边形,而模板缓冲区依旧为零。否则,如果点在阴影量的前面,则阴影多边形会被输出 z 缓冲区,而模板依旧保持不变。如果点位于阴影量下面,则会描绘同样多的正面阴影,而模板为零,即增加的次数与减少的次数一样多。
最后一种可能性就是点位于阴影量中。在这种情况下,阴影量的背面会被输出 z 缓冲区,但正面则不然,因而模板缓冲区会为非零。结果就是,帧缓冲区位于阴影中的一些部分具有非零的模板值。最后,要实际描绘阴影,整个场景会被一个 alpha 混色的多边形集充溢一遍,其中仅模板值非零的像素受到影响。在随 DirectX SDK 一起提供的 “Shadow Volume”示例中有关于该技巧的一个示例。
问:Texel(特塞尔)对齐规则是什么?我怎样才能得到一一映射?
答:这一点在 DirectX 8 文档中(Directly Mapping Texels to Pixels)得到了全面的解释。但是总而言之,您应将屏幕坐标偏移一个像素的 –0.5,以便正确地与 Texel 对齐。大多数的插卡正确符合对齐规则,但是有一些较老的插卡或驱动程序则不然。要解决这些问题,建议您最好与相关的硬件厂商取得联系,请求得到更新过的应驱动程序或其所建议的变通办法。
问:D3DCREATE_PUREDEVICE 标志有何用途?
答:如果在创建设备时指定了 D3DCREATE_PUREDEVICE 标志,则 Direct3D 将创建一个“纯粹”的设备。这禁用 Get* 族类的方法,并使顶点处理仅限于硬件。这使得 Direct3D 运行时能够进行某些优化,以提供到驱动程序的最佳路径,而不必跟踪那么多的内部状态。也就是说,您使用 PUREDEVICE 时可以见到一定的性能优势,但却牺牲了某些便利条件。
问:我如何使用颜色键控?
答:DirectX 8 并不支持颜色键控。您应当换用 alpha 混色/测试,大体上这是一个更加灵活的技巧,没有与颜色键控相关的一些问题。
问:我如何枚举多监视器系统中的所有显示设备?
答:与其它的枚举功能相同,该功能已从基于回调变为由应用程序借助 IDirect3D8 接口的各种方法来进行简单的反复。调用 GetAdapterCount 来确定系统中显示适配器的数目。调用 GetAdapterMonitor 来确定适配器所连接的物理监视器(该方法返回一个HMONITOR,您然后就可以在 Win32 API GetMonitorInfo 中使用该值,以确定关于物理监视器的信息)。确定某一具体显示适配器的特征,或在该适配器上创建一个 Direct3D 设备,简单到在调用 GetDeviceCaps、CreateDevice 或其它方法时,通过替代 D3DADAPTER_DEFAULT 来传递相应的适配器编号。
2.2 几何(顶点)处理问:D3DVERTEX 等等顶点类型有何变动?
答:不再显式支持“预罐装”的顶点类型。多重顶点流系统允许对顶点数据进行更加灵活的装配。如果您想使用其中一个“传统”的顶点格式,则您可以建立一套相应的FVF 代码。
问:我对顶点流不大清楚,其工作原理如何?
答:Direct3D 对从一个或多个顶点流馈入流水线的每个顶点进行组装。只有一个顶点流时,就对应于 DirectX 8 以前的老模型,即顶点来自单独一个源。借助 DirectX 8,不同的顶点组件可以来自不同的源;例如,一个顶点缓冲区可能含有位置和法线,而另一个则含有颜色值和纹理坐标。
问:顶点着色器是什么?
答:顶点着色器是一个用于处理单一顶点的过程。这是借助一种类似于汇编的简单语言来进行定义的,由 D3DX 实用程序汇编为一个 Direct3D 接受的令牌流。顶点着色器接受单独一个顶点和一组常量值的输入,并输出一个顶点位置(在剪贴空间),还可能输出一组用于光栅化的颜色和纹理坐标。请注意,在您有一个定制的顶点着色器时,顶点组件就不再有任何由 Direct3D 施加给它们的语义,而顶点就只是由您所创建的顶点着色器进行解释的任意数据。
问:顶点着色器进行透视划分或剪裁吗?
答:不。顶点着色器在已变换的顶点位置的剪贴空间输出一个纯系坐标。透视分割和剪裁是由后着色器自动进行的。
问:我能借助顶点着色器生成几何图形吗?
答:顶点着色器无法创建或消灭顶点;其一次只对单一顶点进行操作,即作为输入接收一个未经处理的顶点,而输出单独一个经过处理的顶点。因此可以将其用于操作已有的几何图形(应用变形或进行外观变换操作),但实际上无法生成新的几何图形。
问:我能将一个定制的顶点着色器应用到固定功能几何流水线(或者进行相反的操作)吗?
答:不能。您必须选择其一。如果您正在使用一个定制的顶点着色器,则您负责进行整个顶点变换操作。
问:如果我的硬件并不支持定制的顶点着色器,我可以使用吗?
答:可以。Direct3D 软件顶点处理引擎完全支持定制的顶点着色器,且性能指标出奇的高。
问:我如何确定硬件是否支持我的定制的顶点着色器?
答:能够硬件支持顶点着色器的设备被要求填充 D3DCAPS8::VertexShaderVersion 字段,以指示其所支持的顶点着色器的版本级别。所有声称支持某一级别的顶点着色器的设备,必须支持所有合法的顶点着色器,这些顶点着色器符合针对该级别或较低级别的规范。
问:有多少个常量寄存器可以用于顶点着色器?
答:要求支持 DX8 顶点着色器的设备至少支持 96 个常量寄存器。设备的支持能力可能会超过这一最低数目,且可以通过 D3DCAPS8::MaxVertexShaderConst 字段进行报告。
问:我可以在带有不同纹理坐标的顶点之间共享位置数据吗?
答:该情形的一个通常示例就是一个立方体,其中您想为每个面使用一个不同的纹理。很不幸,答案是不行;目前还还不能独立索引每个顶点组件。即使是多顶点流,也是所有的顶点一起索引。
问:在我提交一列带索引的原语时,Direct3D是处理缓冲区中所有的顶点,还是只处理我索引过的顶点?
答:在使用软件几何流水线时,Direct3D 首先转换您所提交的范围中的所有的顶点,而不是“根据要求”按照索引对其进行转换。这对于密集数据(即其中使用了大多数的顶点)效率更高,尤其是在可以使用 SIMD 指令时。如果您的数据比较松散(即很多顶点未被使用),则您可能需要考虑重新排列您的数据,以避免多余的转换。在使用硬件几何加速时,顶点经常是根据需要进行转换的。
问:索引缓冲区是什么?
答:索引缓冲区与顶点缓冲区极其类似,但其包含的是用于 DrawIndexedPrimitive 调用的索引。强烈建议您尽可能使用索引缓冲区,而不要使用原始的由应用程序分配的内存,其道理与顶点缓冲区相同。
我注意到 32 位的索引现在是一种支持类型;我可以将其用在所有的设备上吗? 不可以。你必须检查 D3DCAPS8::MaxVertexIndex 字段,以确定设备所支持的最大索引值。该值必须大于 216-1 (0xffff)才能支持 D3DFMT_INDEX32 类型的索引缓冲区。另外请注意,某些设备可能支持 32 位的索引,但其所支持的最大索引值却小于 232- 1 (0xffffffff);这样,应用程序必须遵从设备所报告的限制。
问:将多重顶点流用于固定功能流水线有何限制?
答:固定功能流水线要求每条顶点流水线是一个严格的 FVF 子集,即根据一个完整的 FVF 声明预定的。另外请注意,您必须遵从 D3DCAPS8::MaxStreams 字段所报告的流水线数目的限制(现在的许多设备和/或驱动程序仅支持单一流水线)。
2.3 性能调谐问:我如何能够改善我的 Direct3D 应用程序的性能?
答:在优化性能时,需要考察下列几个关键问题:
·批处理大小 Direct3D 已为大批量原语进行过优化。一次调用所能发送的多边形越多,其效果也就越好。凭经验而论,建议平均每次调用100 个以上的多边形。低于该水平,您可能不会得到最好的性能,而高于该水平,您收到的效果会递减,且可能会与并行事项发生冲突(参见下面的论述)。 ·状态更改 更改描绘状态这种操作可能会很昂贵,尤其是在更改纹理时。因此,每帧所作的状态更改应尽可能地少,这一点很重要。另外,请尽量降低对顶点或索引缓冲区的更改。注意:在 DirectX 中更改缓冲区已不在象在以前版本中那样昂贵了,但依旧建议尽量避免更改顶点缓冲区。 ·并行 如果能够安排描绘与其它处理同时进行,则您可以充分利用系统性能。这一目标可能会与降低描绘状态更改的目标相抵触。您需要在进行批处理以降低状态更改和较早将数据推出到驱动程序以达到并行目的之间找到一个平衡点。以循环方式使用多个顶点缓冲区,会有助于并行功能。 ·纹理上载 将纹理上载到设备会消耗带宽并导致与顶点数据争用带宽。因此,不要占用过多的内存这一点很重要,因为这会强制缓存方案为每一帧上载过多的纹理。 ·顶点缓冲区和索引缓冲区 您应当一直使用顶点缓冲区和索引缓冲区,而不是由应用程序分配的普通内存块。顶点缓冲区和索引缓冲区的锁定语义至少可以避免多于的复制操作。对于某些驱动程序,顶点缓冲区和索引缓冲区可能是更理想的存储器中的某些区域(可能是在视频或 AGP 存储器中),供硬件进行访问。 ·状态宏锁定 这些是在 DirectX 7.0 中推出的,为将一系列的状态更改(包括照明、材质和矩阵更改)记录成一个宏提供了一个机制,该宏然后就可以通过单一的调用来重放了。这有两个优势: 您进行一次调用而不是多次调用,从而降低调用开销。 有感悟能力的驱动程序可以对状态更改进行预分析和预编译,从而使到图形硬件的提交速度快得多。状态更改可能依旧很昂贵,但使用状态宏至少会有助于降低部分成本。 ·仅使用单独一个 Direct3D 设备 如果您需要描绘到多个目标,则请使用 SetRenderTarget。如果您是要创建一个带有多个 3D 窗口的窗口化程序,则请使用 CreateAdditionalSwapChain API。运行时已为单一设备进行过优化,使用多个设备时会有相当多的速度折扣。问:我应当使用哪些基本类型(条形、扇形、列表等)?
答:在真实数据中遇到的许多网格,都具有多个多边形共享顶点的特性。为将性能最大化,最好将所转换的顶点中的重复率降低,并横跨总线将其发给描绘设备。使用简单的三角列表根本实现不了任何顶点共享,因而这是最不理想的方法。这一点已很清楚。然后所要作的选择就是使用条形和扇形(暗示多边形之间的具体连接关系),还是使用索引列表。在数据自然归入各种条形和扇形的情况下,这些就是最为合适的选择,因为发给驱动程序的数据被降至最低。但是,将网格分解条形和扇形经常会造成大量分离块,暗示有大量的 DrawPrimitive 调用。因此,最富效率的方法通常是使用带三角列表的单独一个 DrawIndexedPrimitive 调用。使用索引列表的另一个优势就是,这在连续三角形仅共享单独一个顶点时也有益处。总而言之,如果您的数据自然归入各种较大的条形和扇形,就使用条形和扇形,否则就使用索引列表。
问:如果我要生成动态数据,怎样才算是较好地使用顶点缓冲区了呢?
答:具体的实施步骤。
借助 D3DUSAGE_DYNAMIC 和 D3DUSAGE_WRITEONLY 使用标志以及 D3DPOOL_DEFAULT 缓冲池标志来创建一个顶点缓冲区。(如果您正使用软件顶点处理功能的话,还要指定 D3DUSAGE_SOFTWAREPROCESSING) I = 0 设置状态(纹理、描绘状态等) 检查缓冲区内是否有空间,即 i.e.I + M <= N? (其中 M 是新顶点的数目) 如果为真,则借助 D3DLOCK_NOOVERWRITE 对 VB 进行 Lock (锁定)。这告知 Direct3D 和驱动程序,您将要添加顶点,而并不修改您先前批处理过的顶点。因此,如果当时正在进行一项DMA 操作,则并不中断该操作。否则,转至 11 在 I 处填充 M 个顶点 解锁 (Unlock) 调用 Draw[Indexed]Primitive。对于未经索引的,请将 I 用作 StartVertex 参数。对于已编索的基本类型,请确保索引指向顶点缓冲区的正确部分(要做到这一点,使用 SetIndices 调用的 BaseVertexIndex 参数可能最为容易) I + = M 转到 3 好,空间已用尽,就让我们开始一个新的顶点缓冲区。我们不想使用同一个顶点缓冲区,因为有可能有一个 DMA 操作正在进行之中。我们将这一情形告知 Direct3D, 方法是借助 D3DLOCK_DISCARD 标志,将“同一个”顶点缓冲区锁定。这意思是,“你可以给我一个新的指针了,因为我已用完旧的指针,不再在意旧的内容。” I = 0 转至 4(或 6)问:D3DX 图象文件加载器函数支持哪些文件格式?
答:D3DX 图象文件加载器函数支持 BMP、TGA、PNG、JPG、DIB、PPM 和 DDS 文件。
问:D3DX 中的文字描绘函数好象失效,我的操作有什么问题吗?
答:使用 ID3DXFont::DrawText 功能时的一个常见错误就是为颜色参数指定一个零 alpha 组件,从而导致完全透明(即不可见)的文本。对于完全不透明的文本,请确保色彩参数的 alpha 成分完全饱和(255)。
问:对于字体描绘,我应当使用 ID3DXFont 还是 SDK 框架 CD3DFont 类?
答:ID3DXFont 类能够处理字间距,因为它使用 GDI 来绘制字符串。这可能会有点慢,因为每次均需要调用GDI。
CD3DFont 设计用于加速和使用纹理化基本类型来绘制字符。它只能处理简单字体,并不支持 ID3DXFont 可用的全套格式选项,但适用于简单而快速的显示,诸如帧速率计数等等。对于产品代码,您可能需要实施您自己的字形描绘功能,即借助纹理化基本类型和/或基于GDI 的方案(带避免重新绘制的缓存功能)。
问:我的应用程序启动时,为何为发出一阵净噪声?
答:我注意到其它应用程序也有这个问题。您可能安装了调试用 DirectX 运行时。运行时的调试版本用静噪声填充缓冲区,以便帮助开发人员捕捉未经初始化缓冲区的缺陷。您无法保证DirectSound 缓冲区在创建后的内容;尤其是您无法将缓冲区清零。
问:我如何确保我的游戏将能够与各种网络地址转换器和Internet连接共享设置正常工作?
答:网络地址转换器(Network Address Translator,NAT)和Internet连接共享(Internet Connection Sharing,ICS)是较为复杂的主题,在 MSDN 上的另一篇文章中有更详尽的论述。但是,下列提示可以作为
很好的一般性指导:Nats2-msdn.htm。
有关点对点游戏、将服务器驻于 NAT 后面的事宜,以及针对各种不同的 Windows 操作系统上的 ICS 的具体建议,请参考更详尽的文档。
问:DPNSVR 是做什么用的?
答:DPNSVR 是一种用于枚举请求的转发服务,消除了多个 DirectPlay 应用程序在端口使用上的冲突所导致的各种问题。使用 DPNSVR 使得 DirectPlay 能够自动选择要使用的端口,同时又允许客户端对您的游戏进行枚举。默认为,DirectPlay 会使用 DPNSVR,因为这通常为应用程序提供了最大的灵活性;但是,您可以将其禁用,方法是在创建您的会话时指定 DPNSESSION_NODPNSVR 标志。如果客户端使用 DPNSVR 端口来枚举主机,而主机又使用自己的端口来作出响应的话,使用 DPNSVR 可能会导致客户端一侧的 NAT 发生问题;NAT 可能会拒绝将数据包转递给客户端,因为数据包不是来自请求被发往的同一端口。
问:IDirectPlay8LobbyClient::Initialize 为何返回 DPNERR_NOTALLOWED?
答:DirectPlay 不允许每个进程有一个以上的前端客户端或应用程序,因而试图创建多个客户端会导致返回这一错误。
本文地址:http://com.8s8s.com/it/it557.htm