DirectX 8 模板





5.00/5 (1投票)
2001 年 9 月 27 日
11分钟阅读

135045

1273
编写使用 DirectX 8 的应用程序的框架。
引言
该项目采用与 Microsoft DirectX 框架推荐的相同方法编写。尽管 DirectX 8 中没有像 DirectX 7 那样的构建库,因此必须将文件包含在项目中。这些文件现在位于文件夹 mssdk\samples\multimedia\common 中,其中包含 src 和 include 目录。使用这些文件的最佳方法是通过 工具|选项 的目录选项卡将包含文件添加到 DevStudio 路径中,然后直接将源文件包含到您的项目中。
这里有一个重要的注意事项是,由于其更强的硬件依赖性,DirectX 8 对您允许的操作有严格得多的限制。在 DirectX 7 中,我总是能够全屏运行调试应用程序,然后无任何问题地添加断点。我甚至可以在没有 3D 显卡的笔记本电脑上运行代码。这在 DirectX 8 中是可能的,但该项目在窗口中以每秒五帧的速度运行,因此要在此基础上开发任何严肃的内容将非常困难。这意味着我必须建议所有开发都在窗口化环境中进行,并且在配有适当 3D 显卡的计算机上进行 : (
此项目旨在介绍 DirectX 8,项目中的类旨在以最少的麻烦重用,而演示代码则被清晰地标识出来,以便于移除。
代码
代码通过继承框架库主类开始,并简单地添加了几个 get 和 set 函数。
class CNew3dApp : public CD3DApplication
{
此类旨在覆盖基类中的某些函数,并提供一些虚拟函数的新实现。它还提供了 一些 get 和 set 函数,以简化生活。.覆盖的函数是:
/// called first virtual HRESULT OneTimeSceneInit(); /// called next to initialize app objects virtual HRESULT InitDeviceObjects(); virtual HRESULT DeleteDeviceObjects(); /// render the scene virtual HRESULT Render(); /// move the frame virtual HRESULT FrameMove( FLOAT fMove ); virtual HRESULT RestoreSurfaces(); virtual HRESULT FinalCleanup();
当您盯着 DirectX 看了一段时间后,这些都非常明显,但我会在这里简要描述每个函数。
虚拟函数 OneTimeSceneInit()
是代码访问的第一个函数。这是为了允许代码设置程序将需要的对象。程序使用此函数来开启,在这种情况下没有主要的选项,而是开启 Stats 函数,该函数将在屏幕左上角放置一个屏幕大小的读数和帧速率。
虚拟函数 InitDeviceObjects
是代码主要对象设置的地方。此代码是可重入的,因此如果代码只需要执行一次,则必须确保它只执行一次。我知道当最后一个函数被称为 OneTimeSceneInit
时,这听起来很奇怪,但该函数不应用于更改屏幕等主要方面,而这正是我在这里所做的。
if( !bInitialScreen ) { bInitialScreen = true; if( FAILED( SetScreenSize( 800, 600 ) ) ) return E_FAIL; }
请参阅下面的设置屏幕大小,了解此函数的作用说明。
字体
如果您打开 3dApp.h 文件,您会注意到类中有一个私有成员。
CD3DFont *m_pFont;
该项目使用它来显示出现在屏幕左上角的统计信息,如果我们沿着代码追溯,我们不仅可以看到它的工作原理,还可以快速回顾对象在代码中应如何处理。
在构造函数中,字体被初始化。
m_pFont = new CD3DFont( _T( "Arial" ), 12, D3DFONT_BOLD );在
InitDeviceObjects
函数中,我们为场景设置 3D 对象,字体被初始化并与当前 D3DDevice 相关联。m_pFont->InitDeviceObjects( m_pd3dDevice );
在 DeleteDeviceObjects
函数中,字体调用其自己的 DeleteDeviceObjects
函数。当项目关闭需要删除设备时,就会执行此操作。
下次看到它是在渲染函数中,它会绘制应用程序帧和设备统计信息。然后,它在其应用程序的 InvalidateObjects
函数中调用其自己的 InvalidateObjects
版本,当当前设备发生更改时(例如在窗口和全屏之间切换,或在窗口化应用程序和调试器之间切换)会调用此函数。同样,在 RestoreDeviceObjects
中,当应用程序再次获得焦点时,字体对象会调用其自己的 RestoreDeviceObjects
函数版本。最后,在 FinalCleanup
函数中,字体对象被删除。
虽然希望它不是您见过最令人兴奋的类,但它为如何设计类以在 DirectX 提供的框架内工作提供了一个有用的模板。CD3DFont 类本身有三个函数值得关注,除了它使用 DirectX 框架格式外。它们是:
HRESULT DrawText( FLOAT x, FLOAT y, DWORD dwColor, TCHAR* strText, DWORD dwFlags=0L ); HRESULT DrawTextScaled( FLOAT x, FLOAT y, FLOAT z, FLOAT fXScale, FLOAT fYScale, DWORD dwColor, TCHAR* strText, DWORD dwFlags=0L ); HRESULT Render3DText( TCHAR* strText, DWORD dwFlags=0L );
在当前代码中,我们只使用 DrawText
函数,但以后详细研究其他函数可能会很有趣。
设置屏幕大小
由于引入了适配器概念和像素深度工作方式的变化, SetScreenSize
函数在更改为 DirectX 8 时必须完全重写。
HRESULT SetScreenSize(int nHoriz, int nVert, BOOL bWindowed = FALSE, D3DFORMAT d3dFormat = D3DFMT_X8R8G8B8 );
最后一个参数已更改为 D3DFORMAT
参数,该参数是一个值,用于表示不同类型的(在渲染目标的情况下)像素格式。我们只需要关注两个值: D3DFMT_X8R8G8B8
格式,这是一个 32 位 RGB(红、绿、蓝)格式,每个颜色保留八位;以及 D3DFORMAT_R5G6B5
格式,这是一个 16 位 RGB 格式。代码设置为默认使用 32 位值。
SetScreenSize
函数的其余部分是:
HRESULT hResult = S_OK; int nWidth, nHeight; // Get access to the newly selected adapter, device, and mode DWORD dwDevice; dwDevice = m_Adapters[m_dwAdapter].dwCurrentDevice; for( int i=0; i < ( int )m_Adapters[ m_dwAdapter ].devices[ dwDevice ].dwNumModes; i++ ) { if( m_Adapters[ m_dwAdapter ].devices[ dwDevice ].modes[ i ].Format == d3dFormat ) { nWidth = ( int )m_Adapters[ m_dwAdapter ].devices[ dwDevice ].modes[ i ].Width; nHeight = ( int )m_Adapters[ m_dwAdapter ].devices[ dwDevice ].modes[ i ].Height; if( nWidth == nHoriz && nHeight == nVert ) { m_Adapters[ m_dwAdapter ].devices[ dwDevice ].dwCurrentMode = i; m_Adapters[ m_dwAdapter ].devices[ dwDevice ].bWindowed = bWindowed; m_bWindowed = bWindowed; break; } } } /// Release all scene objects that will be re-created for the new device InvalidateDeviceObjects(); DeleteDeviceObjects(); /// Release display objects, so a new device can be created if( m_pd3dDevice->Release() > 0L ) { return DisplayErrorMsg( D3DAPPERR_NONZEROREFCOUNT, MSGERR_APPMUSTEXIT ); } /// Inform the display class of the change. It will internally /// re-create valid surfaces, a d3ddevice, etc. hResult = Initialize3DEnvironment(); if( FAILED( hResult ) ) return DisplayErrorMsg( hResult, MSGERR_APPMUSTEXIT );
该函数将屏幕大小更改为请求的大小,并根据传入的 bWindowed 的值决定全屏或不全屏。在调试模式下运行时,bWindowed 必须为 TRUE
。主要区别在于适配器概念的使用,对您我来说,适配器意味着显卡,尽管显卡可能拥有多个适配器,如果它内置了多显示器支持。每个适配器支持的设备是指显卡可以运行的视频模式,例如 16 位下的 640/480,32 位下的 800/600 等。
加载和旋转
屏幕上显示的对象是一个 .x 文件,它基本上是一个供 DirectX 代码绘制的向量列表。该文件也包含纹理信息,但目前,仅加载它并将其正确显示到屏幕上应该就足够了。该对象将被加载到一个 D3DMesh 类中,该类位于 d3dfile.h 头文件中。此类将包含处理代码将加载的 x 文件所需的所有信息。
网格使用代码加载:
HRESULT hResult = m_pObjectMesh->Create( m_pd3dDevice, _T( "dolphin.X" ) ); if( FAILED( hResult ) ) return D3DAPPERR_MEDIANOTFOUND;
此代码加载 dolphin.x 文件,尽管没有阻止在此处添加任何其他文件名,但应注意目前该文件必须位于当前目录中,并且由于网格大小不同,对象加载时可能太小。大,或者完全不可见,这将意味着需要对视图进行一些更改,稍后将对此进行讨论。
如果您查看 3dapp.cpp 文件中的代码,您会发现 CD3DMesh 的控制流与字体对象完全相同,因为在每个重载函数中, m_pObjectMesh 对象都会调用一个函数,该函数会将对象与应用程序的其余部分保持同步。
现在需要覆盖三件事。目前我们有一个计算机屏幕和一个我们想在其中以 3D 方式绘制的对象,但计算机如何知道在哪里绘制对象?简单,我们告诉它。
D3DXMATRIX matWorld; D3DXMATRIX matView; D3DXMATRIX matProj; /// Set the Rotation speed D3DXMatrixRotationY( &matWorld, timeGetTime()/550.0f ); m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld ); D3DXMatrixLookAtLH( &matView, &m_vecEye, &vecAt, &vecUp ); m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, -1.0f ); m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
在 Render 函数中,我们定义了三个 D3DMATRIX
类型的对象,一个用于世界,一个用于视图,一个用于投影。然后,我们通过调用 DirectX 8 辅助函数以我们想要的方式设置这些矩阵,最后调用 SetTransform
在 m_pd3dDevice(即绘图设备)上。
世界矩阵( matWorld )指定了世界的样子,这里代码告诉它将矩阵设置为在 y 轴上旋转,因为这是每秒调用几次的 Render 函数,代码会告诉它在每次通过时多旋转一点。然后,当调用 SetTransform
函数时,世界中的任何对象都将在 y 轴上旋转。提高或降低用于划分 timeGetTime
(自 Windows 启动以来的秒数)返回值的数值将分别减慢或加快对象的旋转速度。
视图矩阵( matView )指定了屏幕如何观察世界。它调用 D3DXMatrixLookAtLH
函数,该函数设置了在左手世界中观察世界的方式。这意味着屏幕的 x 轴在最左边为 0,在最右边为最高值。然后调用 SetTransform
并使用 D3DTS_VIEW
常量来告诉 DirectX 这是所需的视图样式。
投影矩阵( matProj )处理对象如何在创建的 3D 世界中感知和缩放。这就是为什么物体在远离时会变小的原因。
更改视图
当运行此项目的代码时,对象首先从屏幕的左侧出现,然后前后移动一点,当它向前移动时向左稍微滑动,当它向后移动时向右稍微滑动。好吧,它没有。整个程序中对象都保持完全静止。这个例子中唯一处理移动对象的是世界矩阵代码,它在每次调用 Render 函数时都会稍微旋转它。事实上,当代码开始时,对象就在您身后。当您向后和向左移动时,对象会进入视野并使自己居中在屏幕中间,然后当它似乎向您和您的左侧移动时,是因为您正在向前和向右移动。所有这些都在 FrameMove 函数中完成,代码为:
static bool bForward = true; if( bForward ) { if( m_vecEye.z > 1000.0f ) bForward = false; m_vecEye.x += 3.0f; m_vecEye.z += 10.0f; } else { if( m_vecEye.z < 400.0f ) bForward = true; m_vecEye.x -= 3.0f; m_vecEye.z -= 10.0f; }
更改在 m_vecEye D3DXVECTOR3
中完成(请注意,当我说是向量时,我指的是 D3DXVECTOR3
,它继承自 Vector 并添加了一些功能),这是一个类成员,用于存储创建 matView 矩阵时视图的眼睛位置。
D3DXMatrixLookAtLH( &matView, &m_vecEye, &vecAt, &vecUp );
向量包含该位置的 x、y 和 z 坐标。x 是世界位置中心左侧的位置,负 x 向左移动。y 是世界位置底部的位置,正 y 值向上移动到屏幕顶部,负 y 值向下移动到屏幕底部,z 是深度。传递给上述函数的向量是眼睛位置,即世界内的屏幕位置,当前是屏幕中心。at 向量是屏幕面对的位置,up 位置是向上方向。
整个效果由 m_vecEye.z 值控制,当该值超过 1000 时,视图停止并开始再次向前移动,直到达到小于 400 的值,此时它停止并开始再次向后移动。应尝试使用这些值以在使用不同模型时获得相同效果。
构建示例
任何人构建示例应该都没有问题,因为 cpp 文件已包含在 zip 文件中。这样做是因为我添加了以下行:
#include "stdafx.h"
在所有 cpp 文件中。这是因为我更喜欢包含 MFC,并且我可能会在某些时候使用它。这不是出于技术原因,而是因为我的工作方式。如果您愿意,可以删除它们并使用标准的 cpp 文件。您应该遇到的唯一可能问题是在 developer studio 的 tools\options\directories 部分中包含指向通用头文件的路径。这些可以在 mssdk\samples\multimedia\common\include 下找到。
演示代码将从开始
/// DEMO CODE
并以结束
/// END DEMO CODE
通过删除这些行之间的代码,您将获得一个用于开始开发自己代码的模板。