DirectX 基础和 DirectX3D API 入门






4.73/5 (18投票s)
本文介绍了一些有助于学习 DirectX 技术的基本要素。
什么是 DirectX?
本文的重点是为初学者(或任何计算机编程专业的学生)提供 DirectX 技术的概念。因此,第一部分提供了一些理解 DirectX 所需的常规知识。它们可能看起来复杂且互不相关,但它们应该能融会贯通,帮助读者从宏观走向微观。关于 DirectX 技术(尤其是 DirectX 9),互联网上最好的资源之一是 Frank Luna 编写的一系列可购买和下载的 PDF。但无论如何,凭我有限的知识,我将首先定义什么是 DirectX,然后提供一些基本的 DirectX 应用程序示例。要对 DirectX 技术有一个扎实的理解,最好的方法是牢固掌握其基本原理。这意味着将不同的子系统技术(如 Win32 API、基本的 COM 编程、Windows 消息机制、消息循环、硬件抽象层(hal.dll)、图形处理单元)与矩阵和向量等应用数学知识融合在一起。然而,应用数学只是解释了移动、运动、旋转和变换如何在用户界面上发生。但是,不入虎穴,焉得虎子,对吧?
DirectX 是一个由 COM 库集合组成的 SDK,或者说是一系列用于在微软平台上处理与多媒体相关任务(如游戏编程和视频)的 API。具体来说,DirectX 是由一些更小的 API 组成的集合:
- Direct3D:一个图形 API,简化了在屏幕上绘制图像的过程。该组件处理游戏的视觉方面。
- DirectShow:负责媒体流。
- DirectSound:允许开发者播放数字化声音。
- DirectPlay:一个用于创建和设计多人游戏或需要通过网络通信的游戏的 API。
我们将重点关注 Direct3D API。最新版本的 SDK 包含了“Direct Computing”。无论如何,制作任何 Direct3D 程序都需要首先创建一个窗口,一个由窗口句柄标识的标准窗口。您可以使用常规的 Win32 API 函数,或者像 MFC 这样的其他库。创建主窗口的目的是接收消息,并提供一个画布,Direct3D 将在其上渲染图像。Direct3D 将把所有数据绘制到这个窗口上。任何曾经从事或学习过 Windows UI 创建的人都会知道,UI 是通过处理排队到每个 UI 线程消息队列中的消息来工作的。
下一步是创建一个 Direct3D 对象。接着,我们创建一个 Direct3D 设备对象——这是 Direct3D 对象为我们创建的:一个 Direct3D 设备。Direct3D 设备代表系统的显卡,并使用它来绘制图像。也就是说,我们使用 Direct3D 设备来实际绘制应用程序窗口中的图像。接下来,我们配置消息循环。一旦应用程序创建了它们的主窗口,它们通常会进入一个消息泵。完成此操作后,我们便渲染并显示一个场景。当应用程序结束时,消息循环终止,所有创建的对象都必须被释放。如果您熟悉 COM,那么您可能还记得 IUnknown
接口的标准 Release
方法。
停在这里:什么是 IUnknown 接口?
COM 是一种规范,它描述了一套严格的编程步骤和实践,旨在独立于所使用的编程语言。这是因为 COM 是一个二进制标准。当不同的编程语言定义一个执行相同通用目的的函数时,虽然源代码文本不同,但在比特级别,它对集成电路芯片的输入引脚来说是相同的。COM 通过使用严格的标识规则和在系统注册表中注册 COM 组件的额外“管道”代码,将定义(或接口)与实现分离开来。对于操作系统而言,内存中的一个结构应该与任何其他结构看起来相同。IUnknown
是 COM 接口层次结构的根接口。以下是 MSDN 库的参考资料:
COM 定义了一个特殊的接口 IUnknown
,以实现一些基本功能。所有组件对象都必须实现 IUnknown
接口,并且方便的是,所有其他的 COM 和 OLE 接口都派生自 IUnknown
。IUnknown
有三个方法:QueryInterface
、AddRef
和 Release
。在 C++ 语法中,IUnknown
看起来像这样:
interface IUnknown {
virtual HRESULT QueryInterface(IID& iid, void** ppvObj) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
}
AddRef
和 Release
是简单的引用计数方法。当另一个组件对象正在使用该接口时,会调用组件对象的 AddRef
方法;当另一个组件不再需要使用该接口时,会调用组件对象的 Release
方法。当组件对象的引用计数不为零时,它必须保留在内存中;当引用计数变为零时,组件对象可以安全地卸载自己,因为没有其他组件持有对它的引用。
接口指针是对象使用者与对象通信的唯一方式,它只能访问该接口的特定成员函数。使用者永远不会有指向整个对象的指针或访问权限,这是创建二进制标准所必需的。然而,一个对象可以实现任意多个接口,为每个支持的接口提供不同的接口指针——即不同的函数表。DirectX 实际上是一系列 COM 对象,其中之一就是 Direct3D。Direct3D 是一个内部包含其他 COM 对象的 COM 对象。最终,它包含了运行 2D 和 3D 图形所需的一切,无论是使用软件、硬件还是其他任何方式。因此,因为 Direct3D 已经存储在类中,当你看到 Direct3D 函数这样调用时,不要感到惊讶:
device->CreateRenderTargetView()
device->Release()
要更深入地了解组件对象模型,请阅读 Don Box 编写的《Essential COM》。
再次停下:消息循环和消息泵?
想象一下在桌面上拖放一个图标。这个图标可以比作一个数据源,被拖放到一个目标源。拖动就像复制/剪切高亮的文本。放下就像粘贴。图标的位图位置在视频内存中有一个地址。鼠标点击(或键盘事件)是一个事件。每种类型的事件都对应一个回调过程。这意味着事件的类型必须由 Windows 操作系统定义。操作系统反过来创建一个消息,并将其发送给相关的应用程序(或在放下图标时桌面上的目标位图位置)。该目标也可以被视为一个目标,它在接收到数据传输后,回调数据源以完成消息循环。这类似于将复制的文本粘贴到一个消耗数据的目标位置。在一个窗口应用程序中,每个窗口都有一个线程,该线程在一个特定于此线程的消息队列中等待消息。当消息到达时,线程执行相应的回调过程。这种线程的主代码由一个循环构成,每次收到消息时都会执行。
DirectX 需要图形硬件知识
表面(surface)是 Direct3D 主要用来存储 2D 图像数据的像素矩阵。虽然我们可能将像素数据看作是矩阵的行和列格式,但像素数据实际上存储在一个线性数组中。回想一下,在内存中,栈是一个抽象数据结构,而缓冲区就像一个水平数组。Direct3D 维护一个表面的集合,可能有两到三个,称为交换链。交换链,更具体地说是页面翻转技术,用于在帧之间提供平滑的动画。位于前缓冲区插槽中的表面是对应于当前显示在监视器上的图像的表面。监视器并非瞬时显示由前缓冲区表示的图像;在刷新率为 60 Hz 的监视器上,这需要六十分之一秒的时间。
一个基本的 DirectX 程序
假设我们已经创建了一个窗口对象,并且我们想把它涂成蓝色。这需要四个步骤:
- 创建全局变量和函数原型
- 创建一个函数来初始化 Direct3D 并创建 Direct3D 设备
- 创建一个函数来渲染一帧
- 创建一个函数来关闭 Direct3D
假设您已经在 Windows 操作系统上安装了 Visual Studio 2008 或 2010。安装后,您下载并安装最新版本的 DirectX SDK。对应的 DirectX 头文件和库文件应该被转移到相应的 C:\Program Files\Microsoft Visual Studio 10.0\VC Include 和 Lib 目录中,或者在属性部分正确配置 DirectX 的 Bin、Include 和 Lib 目录路径。人们可能会遇到一个编译器错误,提示找不到“dxerr9.h”之类的文件。这是因为我们安装的是 DirectX 11 SDK(2010 年 6 月版),但 dxerr9.h 头文件是随 DirectX 9 SDK 提供的。由于我们目前不研究 Managed DirectX,我们需要记住,C++ 头文件包含某些预定义函数,这些函数要么已被弃用,要么没有包含在最新版本中。
下面的代码示例引用自 http://www.directx.com,这是一个包含大量 DirectX 教程的强大网站。然而,我们的目的是在对 DirectX 的工作原理以及如何有效使用 COM C++ 语法有了大致了解之后,从头开始编写我们自己的代码。
// include the basic windows header files and the Direct3D header file
#include <windows>
#include <windowsx.h>
#include <d3d9.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
// global declarations
LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
创建一个函数来初始化 Direct3D 并创建 Direct3D 设备
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the Direct3D interface
D3DPRESENT_PARAMETERS d3dpp; // create a struct to hold various device information
ZeroMemory(&d3dpp, sizeof(d3dpp)); // clear out the struct for use
d3dpp.Windowed = TRUE; // program windowed, not fullscreen
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard old frames
d3dpp.hDeviceWindow = hWnd; // set the window to be used by Direct3D
// create a device class using this information and information from the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
}
这是完整的代码
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")
// global declarations
LPDIRECT3D9 d3d; // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev; // the pointer to the device class
// function prototypes
void initD3D(HWND hWnd); // sets up and initializes Direct3D
void render_frame(void); // renders a single frame
void cleanD3D(void); // closes Direct3D and releases memory
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
hWnd = CreateWindowEx(NULL,
L"WindowClass",
L"Test",
WS_OVERLAPPEDWINDOW,
300, 300,
800, 600,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hWnd, nCmdShow);
// set up and initialize Direct3D
initD3D(hWnd);
// enter the main loop:
MSG msg;
while(TRUE)
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(msg.message == WM_QUIT)
break;
render_frame();
}
// clean up DirectX and COM
cleanD3D();
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION); // create the Direct3D interface
D3DPRESENT_PARAMETERS d3dpp; // create a struct to hold various device information
ZeroMemory(&d3dpp, sizeof(d3dpp)); // clear out the struct for use
d3dpp.Windowed = TRUE; // program windowed, not fullscreen
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // discard old frames
d3dpp.hDeviceWindow = hWnd; // set the window to be used by Direct3D
// create a device class using this information
// and the info from the d3dpp stuct
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
}
// this is the function used to render a single frame
void render_frame(void)
{
// clear the window to a deep blue
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);
d3ddev->BeginScene(); // begins the 3D scene
// do 3D rendering on the back buffer here
d3ddev->EndScene(); // ends the 3D scene
d3ddev->Present(NULL, NULL, NULL, NULL);
// displays the created frame on the screen
}
// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
d3ddev->Release(); // close and release the 3D device
d3d->Release(); // close and release Direct3D
}
要运行此代码,请启动 Visual Studio C++ 2010 并新建一个空的 VC++ Win32 项目,命名为 Window。由于项目是空的,右键单击源文件,选择“添加新项”,选择 C++ 代码文件,并将其命名为 main.cpp。将代码复制并粘贴到 IDE 平台中,然后运行它。
让我们尝试一个通过渲染背景和绘制一些文本来演示初始化 DirectX 的应用程序。下图显示了在浅蓝色背景下闪烁的文本。我们可以编写一个 C++ 类文件来处理上述步骤(以及其他步骤),并从主源代码文件中引用。当您运行此应用程序时,您会看到文本在闪烁。我们还把画布做得更浅一些蓝色。
有两个头文件,一个 C++ 对象文件和一个 C++ 主源文件:下面显示的是主文件。可下载的 zip 文件名为 Direct3D.zip。下载并解压文件到您 Visual Studio Projects 目录中一个新建的文件夹里。双击项目文件并运行该应用程序。
#include "d3dApp.h"
#include <tchar.h>
#include <crtdbg.h>
class HelloD3DApp : public D3DApp
{
public:
HelloD3DApp(HINSTANCE hInstance, std::string winCaption,
D3DDEVTYPE devType, DWORD requestedVP);
~HelloD3DApp();
bool checkDeviceCaps();
void onLostDevice();
void onResetDevice();
void updateScene(float dt);
void drawScene();
private:
ID3DXFont* mFont;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF |
_CRTDBG_LEAK_CHECK_DF );
#endif
HelloD3DApp app(hInstance, "Here is Direct3D",
D3DDEVTYPE_HAL,
D3DCREATE_HARDWARE_VERTEXPROCESSING);
gd3dApp = &app;
return gd3dApp->run();
}
HelloD3DApp::HelloD3DApp(HINSTANCE hInstance,
std::string winCaption, D3DDEVTYPE devType,
DWORD requestedVP)
: D3DApp(hInstance, winCaption, devType, requestedVP)
{
srand(time_t(0));
if(!checkDeviceCaps())
{
MessageBox(0, "checkDeviceCaps() Failed", 0, 0);
PostQuitMessage(0);
}
D3DXFONT_DESC fontDesc;
fontDesc.Height = 80;
fontDesc.Width = 40;
fontDesc.Weight = FW_BOLD;
fontDesc.MipLevels = 0;
fontDesc.Italic = true;
fontDesc.CharSet = DEFAULT_CHARSET;
fontDesc.OutputPrecision = OUT_DEFAULT_PRECIS;
fontDesc.Quality = DEFAULT_QUALITY;
fontDesc.PitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
_tcscpy(fontDesc.FaceName, _T("Ariel"));
HR(D3DXCreateFontIndirect(gd3dDevice, &fontDesc, &mFont));
}
HelloD3DApp::~HelloD3DApp()
{
ReleaseCOM(mFont);
}
bool HelloD3DApp::checkDeviceCaps()
{
// Nothing to check.
return true;
}
void HelloD3DApp::onLostDevice()
{
HR(mFont->OnLostDevice());
}
void HelloD3DApp::onResetDevice()
{
HR(mFont->OnResetDevice());
}
void HelloD3DApp::updateScene(float dt)
{
}
void HelloD3DApp::drawScene()
{
HR(gd3dDevice->Clear(0, 0,
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0));
RECT formatRect;
GetClientRect(mhMainWnd, &formatRect);
HR(gd3dDevice->BeginScene());
mFont->DrawText(0, _T("Here is Direct3D"), -1,
&formatRect, DT_CENTER | DT_VCENTER,
D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256));
HR(gd3dDevice->EndScene());
HR(gd3dDevice->Present(0, 0, 0, 0));
}
我们从 D3DApp
(包含在 zip 文件中)派生出一个新类,并重写包含在单元结构中的函数。
class HelloD3DApp : public D3DApp
{
public:
HelloD3DApp(HINSTANCE hInstance,
std::string winCaption,
D3DDEVTYPE devType, DWORD requestedVP);
~HelloD3DApp();
bool checkDeviceCaps();
void onLostDevice();
void onResetDevice();
void updateScene(float dt);
void drawScene();
private:
ID3DXFont* mFont; };
};
这个子类有一个数据成员,一个 ID3DXFont
接口,Direct3D 中的文本输出就是通过它完成的。为了让应用程序运行起来,我们实例化子类的一个实例并进入消息循环。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF |
_CRTDBG_LEAK_CHECK_DF );
#endif
HelloD3DApp app(hInstance);
gd3dApp = &app;
return gd3dApp->run();
}
现在,让我们来检查构造函数、析构函数和框架方法的实现。
HelloD3DApp::HelloD3DApp(HINSTANCE hInstance,
std::string winCaption,
D3DDEVTYPE devType, DWORD requestedVP)
: D3DApp(hInstance, winCaption, devType, requestedVP)
{
srand(time_t(0));
if(!checkDeviceCaps())
{
MessageBox(0, "checkDeviceCaps() Failed", 0, 0);
PostQuitMessage(0);
}
D3DXFONT_DESC fontDesc;
fontDesc.Height = 80;
fontDesc.Width = 40;
fontDesc.Weight = FW_BOLD;
fontDesc.MipLevels = 0;
fontDesc.Italic = true;
fontDesc.CharSet = DEFAULT_CHARSET;
fontDesc.OutputPrecision = OUT_DEFAULT_PRECIS;
fontDesc.Quality = DEFAULT_QUALITY;
fontDesc.PitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
_tcscpy(fontDesc.FaceName,_T("Ariel"));
HR(D3DXCreateFontIndirect(gd3dDevice, &fontDesc, &mFont));
}
构造函数首先在初始化列表中构造其父类部分。然后,它为随机数生成器播种。接下来,它使用 checkDeviceCaps
检查设备能力。然后它填写一个 D3DXFONT_DESC
结构,该结构描述了我们用于文本绘制的字体的属性。最后,它调用 D3DXCreateFontIndirect
函数,该函数根据指定的字体描述(第二个参数)返回一个指向 ID3DXFont
接口的指针(通过第三个参数)。请注意,该函数还要求我们传入一个有效的 Direct3D 设备指针的副本(第一个参数),因为字体需要该设备来绘制文本(所有绘制都是通过 Direct3D 设备完成的)。因为构造函数创建了一个 ID3DXFont
对象,所以析构函数必须销毁它。C++ 功能强大,但可能导致内存泄漏。所以我们用析构函数销毁,用 Release()
清理 COM。
HelloD3DApp::~HelloD3DApp()
{
ReleaseCOM(mFont);
}
ReleaseCOM
宏在 d3dUtil.h 中定义,它只是调用 Release
方法并将指针设置为空:
#define ReleaseCOM(x) { if(x){ x->Release();x = 0; } }
对于这个简单的演示,没有设备能力需要检查;因此我们的 checkDeviceCaps
实现只是返回 true
。在 DirectX SDK 安装的 utilities 目录中,有一个 DirectX Caps Viewer,可以让你查看你的图形设备的能力。
bool Hel1oD3DApp::checkDeviceCaps()
{
// Nothing to check.
return true;
}
这个演示中没有什么需要更新的,所以我们的 updateScene
函数什么也不做。
void HelloD3DApp::updateScene(float dt)
{
}
一个很好的 DirectX 系列是由 Frank D. Luna 编写的。由于最新版本的 DirectX SDK 是在几个月前发布的(在撰写本文时),可能还没有很多好的教科书可供学习。关于 DirectX 9 和 10 的书籍应该比一篇文章更有效地解释事情。但无论如何,某些资源需要在重置设备之前释放,某些资源和设备状态需要在重置设备后恢复。为了处理这些情况,我们的框架提供了 onLostDevice
和 onResetDevice
方法,它们分别在设备重置之前和之后被调用。mFont
对象内部包含 Direct3D 资源,需要在重置前后做一些工作;因此我们有:
void HelloD3DApp::onLostDevice()
{
HR(mFont->OnLostDevice());
}
void HelloD3DApp::onResetDevice()
{
HR(mFont->OnResetDevice());
}
在这里,mFont->OnLostDevice
调用 ID3DXFont
在重置前需要执行的任何代码,而 mFont->OnResetDevice
调用 ID3DXFont
在重置后需要执行的任何代码。最后,我们实现 drawScene
方法并输出文本。
void HelloD3DApp::drawScene()
{
HR(gd3dDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(255, 255, 255), 1.0f, 0));
RECT formatRect;
GetClientRect(mhMainWnd, &formatRect);
HR(gd3dDevice->BeginScene());
mFont->DrawText(0, _T("Here is Direct3D"), -1,
&formatRect, DT_CENTER | DT_VCENTER,
D3DCOLOR_XRSB(rand() % 256, rand() % 256, rand() % 256));
HR(gd3dDevice->EndScene());
HR(gd3dDevice->Present(0, 0, 0, 0));
}
首先观察到我们调用了 IDirect3DDevice9::Clear
方法,该方法将后备缓冲区(目标)和深度缓冲区分别清除为 D3DCOLOR_XRSB(0, 0, 255)
(蓝色)和 1.0。IDirect3DDevice9::Clear
的声明是:
HRESULT IDirect3DDevice9::Clear(
DWORD Count,
const D3DRECT* pRects,
DWORD Flags,
D3DCOLOR Color,
float Z,
DWORD Stencil);
在 C 语言中,当一个函数被调用时,它有一个返回值。如果函数返回时出现错误,可以通过研究 errno.h 头文件来获取错误信息。在 COM 中,方法结果返回一个类型为 HRESULT
的错误码。HRESULT
是 32 位整数,向调用者提供有关错误的信息。代码的其余部分可以逐项说明如下:
Count
:pRects
数组中矩形的数量。pRects
:一个要清除的屏幕矩形数组。这允许我们只清除表面的部分区域。Flags
:指定要清除哪些表面。我们可以清除以下一个或多个表面(通过位或运算组合):
表面
D3DCLEAR_TARGET
:渲染目标表面,通常是后备缓冲区。D3DCLEAR_ZBUFFER
:深度缓冲区。D3DCLEAR_STENCIL
:模板缓冲区。
在我们清除了后备缓冲区和深度缓冲区之后,我们获取客户区矩形的尺寸,这将用于在窗口中格式化文本。但这是什么意思呢?计算和清除矩形?让我们简要看一下一些几何描述。
渲染管线
渲染管线涉及将 3D 场景的几何描述生成为 2D 图像的过程。现在考虑模型表示。该场景是对象或模型的集合(这些对象共同构成一个场景)。一个对象被表示为一个三角形网格近似。网格基本上就是一个表面。网格的三角形是我们正在建模的对象的构建块。此外,三角形是 3D 对象的基本构建块。要构造一个对象,我们创建一个描述对象形状和轮廓的三角形列表。三角形列表包含我们要绘制的每个单独三角形的数据。要绘制一个矩形,我们将其分解为两个三角形。事实上,网格也基本上是表面的表示。网格通过点和线的系统来表示表面。点描述了表面的高低区域,线连接这些点以建立如何从一个点到另一个点。
尽管有上述定义,但最简单地说,一个表面就是一个平面。一个平面需要三个点来定义。在数学上,一个点是一维的,一条线段是二维的,像圆形这样的形状是三维的。因此,网格中可以描述的最简单的表面是一个三角形。这意味着网格只能用三角形来描述。这是因为三角形是定义表面的最简单、最精细的方式。一个巨大而复杂的表面显然不能用一个三角形来准确描述。相反,它可以用许多更小的三角形来近似。你可能会说可以用矩形来定义一个表面,但它不像三角形那样精细。一个三角形由它的三个点定义,也称为顶点。由于一个矩形可以被分解成两个三角形,所以两个三角形比一个矩形能更准确地描述一个表面。请注意下面的表达式:
vertex rect[6] = {v0, v1, v2, // triangle0
v0, v2, v3}; // triangle1
一个可能的问题在于,3D 对象的三角形经常与矩形共享许多相同的顶点。虽然在矩形的例子中只复制了两个顶点,但随着模型细节和复杂度的增加,重复顶点的数量可能会增加。为了解决这个问题,我们引入了索引的概念。它的工作原理是:我们创建一个顶点列表和一个索引列表。顶点列表包含所有唯一的顶点,索引列表包含索引到顶点列表中的值,以定义如何将它们组合在一起形成三角形。回到矩形的例子,顶点列表的构造如下:
Vertex vertexList[4] = {v0, v1, v2, v3};
// two triangle corners share the same corners as the rectangle (4)
然后索引列表需要定义顶点列表中的顶点如何组合成两个三角形。
WORD indexList[6] = {0, 1, 2, // triangle0
0, 2, 3}; // triangle1
虚拟摄像机
粗略地说,摄像机让我们能够看到构成模型的东西。光帮助我们看到它。当你看到一个物体时,实际上是光从该物体反射并进入你的眼睛。摄像机指定了观察者可以看到世界的哪一部分,从而指定了我们需要为世界的哪一部分生成 2D 图像。摄像机在世界中定位和定向,并定义了可见的空间体积。投影窗口是视锥体内的 3D 几何体被投影到的 2D 区域,用以创建 3D 场景的 2D 图像表示。重要的是要知道,我们用最小 = (-1, -1) 和最大 = (1, 1) 的尺寸来定义投影窗口。因此,给定一个 3D 场景的几何描述,以及在该场景中定位和对准的虚拟摄像机,渲染管线指的是生成一个可以显示在监视器屏幕上的 2D 图像所需的整个步骤序列,该图像基于虚拟摄像机所看到的内容。现在考虑这个概念。在构建 3D 对象时,你不是用相对于全局场景(或世界空间)的坐标来构建该对象的几何体,而是相对于局部坐标系(局部空间)来指定它们,其中对象是坐标系的中心。
当整个 3D 模型在局部空间中完成后,它被放置到那个全局场景中。但这是如何做到的呢?通过指定我们想要的局部空间坐标系原点相对于全局场景坐标系的位置,并执行坐标变换来完成。在任何计算机 3D 图形主题中,矩阵的主题都有助于阐明其原理。现在,我们必须记住(粗略地),构成一个场景的对象是由三角形组成的。一个三角形由其三个点定义,称为顶点。一组具有唯一位置的三个顶点定义了一个唯一的三角形。为了让图形处理单元渲染一个三角形,我们必须告诉它该三角形三个顶点的位置。举个 2D 例子,假设我们希望渲染一个三角形。我们会将具有位置(比如)(0, 0), (0, 1), 和 (1, 0) 的三个顶点传递给图形处理单元(GPU),GPU 就有足够的信息来渲染我们想要的三角形。
那么我们现在到哪一步了?
由于平板电视屏幕是二维的,我们可以在二维平面上模拟三维场景。我们注意到,物体的大小随深度减小:物体会遮挡其后面的物体,光照和阴影描绘了三维物体的实体形态和体积,而阴影则暗示了光源的位置并指示了物体相对于场景中其他物体的位置。现在,多边形上两条边相交的点是一个顶点。Direct3D 中的顶点包含一个三维空间位置,但也可以将其他数据组件与该顶点耦合。为了让 Direct3D 知道如何处理我们的顶点数据,我们必须定义一个顶点声明,它为 Direct3D 提供了我们顶点结构的描述。我们用三角形网格来近似物体。我们可以通过指定其三个顶点来定义每个三角形。虚拟摄像机被建模为一个视锥体。视锥体内部的空间体积就是摄像机所能看到的。所以,最后,给定一个三维场景的几何描述以及在该场景中定位和对准的虚拟摄像机,渲染管线指的是生成一个能根据虚拟摄像机所见显示在监视器屏幕上的二维图像所需的整个步骤序列。
每个 3D 对象的顶点都是相对于该对象的局部坐标系定义的。接下来,每个对象关联的世界变换(一个矩阵方程)会改变对象的顶点坐标,使它们相对于世界坐标系。完成这一切之后,场景中的顶点都相对于同一个坐标系,并且应该相对于彼此被正确定位、定向和缩放。摄像机也是世界中的一个对象,我们可以选择让摄像机成为我们的参考系,而不是世界坐标系,这样它就相对于摄像机坐标系(视图空间)。投影矩阵会修改每个顶点。在投影矩阵修改之后,背向的三角形被剔除,视锥体外的几何体被丢弃或裁剪。接下来,进行齐次除法,这完成了投影过程并将顶点转换为规范化设备坐标。此时,规范化设备坐标被映射到后备缓冲区的一部分(或整个后备缓冲区),由视口变换定义。最后,进行光栅化。
一个使用矩阵进行变换的例子
下一步是渲染 3D 几何体。为了处理 3D 几何体,我们需要引入使用 4x4 矩阵来通过平移、旋转、缩放和设置我们的相机来变换几何体。几何体是在模型空间中定义的。我们可以使用世界变换来移动它(平移)、旋转它(旋转)或拉伸它(缩放)。
然后,几何体就被认为处于世界空间中。接下来,我们需要将相机或视点定位在某个地方来观察几何体。另一个变换,通过视图矩阵,用于定位和旋转我们的视图。当几何体处于视图空间时,我们最后的变换是投影变换,它将 3D 场景“投影”到我们的 2D 视口中。
这是主源代码文件。其他头文件和对象文件可从本文顶部下载。
#include "d3dApp.h"
#include "DirectInput.h"
#include <crtdbg.h>
#include "GfxStats.h"
#include <list>
#include "Vertex.h"
class ColoredWavesDemo : public D3DApp
{
public:
ColoredWavesDemo(HINSTANCE hInstance, std::string winCaption,
D3DDEVTYPE devType, DWORD requestedVP);
~ColoredWavesDemo();
bool checkDeviceCaps();
void onLostDevice();
void onResetDevice();
void updateScene(float dt);
void drawScene();
// Helper methods
void buildGeoBuffers();
void buildFX();
void buildViewMtx();
void buildProjMtx();
private:
GfxStats* mGfxStats;
DWORD mNumVertices;
DWORD mNumTriangles;
IDirect3DVertexBuffer9* mVB;
IDirect3DIndexBuffer9* mIB;
ID3DXEffect* mFX;
D3DXHANDLE mhTech;
D3DXHANDLE mhWVP;
D3DXHANDLE mhTime;
float mTime;
float mCameraRotationY;
float mCameraRadius;
float mCameraHeight;
D3DXMATRIX mView;
D3DXMATRIX mProj;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif
ColoredWavesDemo app(hInstance, "Surface Waves",
D3DDEVTYPE_HAL,
D3DCREATE_HARDWARE_VERTEXPROCESSING);
gd3dApp = &app;
DirectInput di(DISCL_NONEXCLUSIVE
|DISCL_FOREGROUND, DISCL_NONEXCLUSIVE
|DISCL_FOREGROUND);
gDInput = &di;
return gd3dApp->run();
}
ColoredWavesDemo::ColoredWavesDemo(HINSTANCE hInstance,
std::string winCaption, D3DDEVTYPE devType, DWORD requestedVP)
: D3DApp(hInstance, winCaption, devType, requestedVP)
{
if(!checkDeviceCaps())
{
MessageBox(0, "checkDeviceCaps() Failed", 0, 0);
PostQuitMessage(0);
}
mGfxStats = new GfxStats();
mCameraRadius = 25.0f;
mCameraRotationY = 1.2 * D3DX_PI;
mCameraHeight = 15.0f;
mTime = 0.0f;
buildGeoBuffers();
buildFX();
onResetDevice();
InitAllVertexDeclarations();
}
ColoredWavesDemo::~ColoredWavesDemo()
{
delete mGfxStats;
ReleaseCOM(mVB);
ReleaseCOM(mIB);
ReleaseCOM(mFX);
DestroyAllVertexDeclarations();
}
bool ColoredWavesDemo::checkDeviceCaps()
{
D3DCAPS9 caps;
HR(gd3dDevice->GetDeviceCaps(&caps));
// Check for vertex shader version 2.0 support.
if( caps.VertexShaderVersion < D3DVS_VERSION(2, 0) )
return false;
// Check for pixel shader version 2.0 support.
if( caps.PixelShaderVersion < D3DPS_VERSION(2, 0) )
return false;
return true;
}
void ColoredWavesDemo::onLostDevice()
{
mGfxStats->onLostDevice();
HR(mFX->OnLostDevice());
}
void ColoredWavesDemo::onResetDevice()
{
mGfxStats->onResetDevice();
HR(mFX->OnResetDevice());
// The aspect ratio depends on the backbuffer dimensions, which can
// possibly change after a reset. So rebuild the projection matrix.
buildProjMtx();
}
void ColoredWavesDemo::updateScene(float dt)
{
mGfxStats->setVertexCount(mNumVertices);
mGfxStats->setTriCount(mNumTriangles);
mGfxStats->update(dt);
// Get snapshot of input devices.
gDInput->poll();
// Check input.
if( gDInput->keyDown(DIK_W) )
mCameraHeight += 25.0f * dt;
if( gDInput->keyDown(DIK_S) )
mCameraHeight -= 25.0f * dt;
// Divide to make mouse less sensitive.
mCameraRotationY += gDInput->mouseDX() / 100.0f;
mCameraRadius += gDInput->mouseDY() / 25.0f;
// If we rotate over 360 degrees, just roll back to 0
if( fabsf(mCameraRotationY) >= 2.0f * D3DX_PI )
mCameraRotationY = 0.0f;
// Don't let radius get too small.
if( mCameraRadius < 5.0f )
mCameraRadius = 5.0f;
// Accumulate time for simulation.
mTime += dt;
// The camera position/orientation relative to world space can
// change every frame based on input, so we need to rebuild the
// view matrix every frame with the latest changes.
buildViewMtx();
}
void ColoredWavesDemo::drawScene()
{
// Clear the backbuffer and depth buffer.
HR(gd3dDevice->Clear(0, 0,
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
0x00000000, 1.0f, 0));
HR(gd3dDevice->BeginScene());
// Let Direct3D know the vertex buffer, index buffer and vertex
// declaration we are using.
HR(gd3dDevice->SetStreamSource(0, mVB, 0, sizeof(VertexPos)));
HR(gd3dDevice->SetIndices(mIB));
HR(gd3dDevice->SetVertexDeclaration(VertexPos::Decl));
// Setup the rendering FX
HR(mFX->SetTechnique(mhTech));
HR(mFX->SetMatrix(mhWVP, &(mView*mProj)));
HR(mFX->SetFloat(mhTime, mTime));
// Begin passes.
UINT numPasses = 0;
HR(mFX->Begin(&numPasses, 0));
for(UINT i = 0; i < numPasses; ++i)
{
HR(mFX->BeginPass(i));
HR(gd3dDevice->DrawIndexedPrimitive
(D3DPT_TRIANGLELIST,
0, 0, mNumVertices, 0, mNumTriangles));
HR(mFX->EndPass());
}
HR(mFX->End());
mGfxStats->display();
HR(gd3dDevice->EndScene());
// Present the backbuffer.
HR(gd3dDevice->Present(0, 0, 0, 0));
}
void ColoredWavesDemo::buildGeoBuffers()
{
std::vector verts;
std::vector indices;
GenTriGrid(100, 100, 0.5f, 0.5f,
D3DXVECTOR3(0.0f, 0.0f, 0.0f), verts, indices);
// Save vertex count and triangle count for DrawIndexedPrimitive arguments.
mNumVertices = 100*100;
mNumTriangles = 99*99*2;
// Obtain a pointer to a new vertex buffer.
HR(gd3dDevice->CreateVertexBuffer(mNumVertices * sizeof(VertexPos),
D3DUSAGE_WRITEONLY,
0, D3DPOOL_MANAGED, &mVB, 0));
// Now lock it to obtain a pointer to its internal data, and write the
// grid's vertex data.
VertexPos* v = 0;
HR(mVB->Lock(0, 0, (void**)&v, 0));
for(DWORD i = 0; i < mNumVertices; ++i)
v[i] = verts[i];
HR(mVB->Unlock());
// Obtain a pointer to a new index buffer.
HR(gd3dDevice->CreateIndexBuffer(mNumTriangles*3*sizeof(WORD),
D3DUSAGE_WRITEONLY,
D3DFMT_INDEX16,
D3DPOOL_MANAGED, &mIB, 0));
// Now lock it to obtain a pointer to its internal data, and write the
// grid's index data.
WORD* k = 0;
HR(mIB->Lock(0, 0, (void**)&k, 0));
for(DWORD i = 0; i < mNumTriangles*3; ++i)
k[i] = (WORD)indices[i];
HR(mIB->Unlock());
}
void ColoredWavesDemo::buildFX()
{
// Create the FX from a .fx file.
ID3DXBuffer* errors = 0;
HR(D3DXCreateEffectFromFile(gd3dDevice, "heightcolor.fx",
0, 0, D3DXSHADER_DEBUG, 0, &mFX, &errors));
if( errors )
MessageBox(0, (char*)errors->GetBufferPointer(), 0, 0);
// Obtain handles.
mhTech = mFX->GetTechniqueByName("HeightColorTech");
mhWVP = mFX->GetParameterByName(0, "gWVP");
mhTime = mFX->GetParameterByName(0, "gTime");
}
void ColoredWavesDemo::buildViewMtx()
{
float x = mCameraRadius * cosf(mCameraRotationY);
float z = mCameraRadius * sinf(mCameraRotationY);
D3DXVECTOR3 pos(x, mCameraHeight, z);
D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMatrixLookAtLH(&mView, &pos, &target, &up);
}
void ColoredWavesDemo::buildProjMtx()
{
float w = (float)md3dPP.BackBufferWidth;
float h = (float)md3dPP.BackBufferHeight;
D3DXMatrixPerspectiveFovLH(&mProj, D3DX_PI * 0.25f, w/h, 1.0f, 5000.0f);
}
三角形和网格
下面是一个网格演示的图像。
这是主源文件
// use W and S keys to alter the height of the application
#include "d3dApp.h"
#include "DirectInput.h"
#include <crtdbg.h>
#include "GfxStats.h"
#include <list>
#include "Vertex.h"
class MeshDemo : public D3DApp
{
public:
MeshDemo(HINSTANCE hInstance,
std::string winCaption,
D3DDEVTYPE devType, DWORD requestedVP);
~MeshDemo();
bool checkDeviceCaps();
void onLostDevice();
void onResetDevice();
void updateScene(float dt);
void drawScene();
// Helper methods
void buildGeoBuffers();
void buildFX();
void buildViewMtx();
void buildProjMtx();
void drawCylinders();
void drawSpheres();
private:
GfxStats* mGfxStats;
DWORD mNumGridVertices;
DWORD mNumGridTriangles;
ID3DXMesh* mCylinder;
ID3DXMesh* mSphere;
IDirect3DVertexBuffer9* mVB;
IDirect3DIndexBuffer9* mIB;
ID3DXEffect* mFX;
D3DXHANDLE mhTech;
D3DXHANDLE mhWVP;
float mCameraRotationY;
float mCameraRadius;
float mCameraHeight;
D3DXMATRIX mView;
D3DXMATRIX mProj;
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance,
PSTR cmdLine, int showCmd)
{
// Enable run-time memory check for debug builds.
#if defined(DEBUG)
| defined(_DEBUG)
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF
| _CRTDBG_LEAK_CHECK_DF );
#endif
MeshDemo app(hInstance,
"Mesh Demo", D3DDEVTYPE_HAL,
D3DCREATE_HARDWARE_VERTEXPROCESSING);
gd3dApp = &app;
DirectInput di(DISCL_NONEXCLUSIVE|DISCL_FOREGROUND,
DISCL_NONEXCLUSIVE|DISCL_FOREGROUND);
gDInput = &di;
if(!gd3dApp->checkDeviceCaps())
return 0;
else
return gd3dApp->run();
}
MeshDemo::MeshDemo(HINSTANCE hInstance,
std::string winCaption,
D3DDEVTYPE devType, DWORD requestedVP)
: D3DApp(hInstance, winCaption, devType, requestedVP)
{
if(!checkDeviceCaps())
{
MessageBox(0, "checkDeviceCaps() Failed", 0, 0);
PostQuitMessage(0);
}
mGfxStats = new GfxStats();
mCameraRadius = 10.0f;
mCameraRotationY = 1.2 * D3DX_PI;
mCameraHeight = 5.0f;
HR(D3DXCreateCylinder(gd3dDevice, 1.0f, 1.0f, 6.0f, 20, 20, &mCylinder, 0));
HR(D3DXCreateSphere(gd3dDevice, 1.0f, 20, 20, &mSphere, 0));
buildGeoBuffers();
buildFX();
// If you look at the drawCylinders and drawSpheres functions, you see
// that we draw 14 cylinders and 14 spheres.
int numCylVerts = mCylinder->GetNumVertices() * 14;
int numSphereVerts = mSphere->GetNumVertices() * 14;
int numCylTris = mCylinder->GetNumFaces() * 14;
int numSphereTris = mSphere->GetNumFaces() * 14;
mGfxStats->addVertices(mNumGridVertices);
mGfxStats->addVertices(numCylVerts);
mGfxStats->addVertices(numSphereVerts);
mGfxStats->addTriangles(mNumGridTriangles);
mGfxStats->addTriangles(numCylTris);
mGfxStats->addTriangles(numSphereTris);
onResetDevice();
InitAllVertexDeclarations();
}
MeshDemo::~MeshDemo()
{
delete mGfxStats;
ReleaseCOM(mVB);
ReleaseCOM(mIB);
ReleaseCOM(mFX);
ReleaseCOM(mCylinder);
ReleaseCOM(mSphere);
DestroyAllVertexDeclarations();
}
bool MeshDemo::checkDeviceCaps()
{
D3DCAPS9 caps;
HR(gd3dDevice->GetDeviceCaps(&caps));
// Check for vertex shader version 2.0 support.
if( caps.VertexShaderVersion < D3DVS_VERSION(2, 0) )
return false;
// Check for pixel shader version 2.0 support.
if( caps.PixelShaderVersion < D3DPS_VERSION(2, 0) )
return false;
return true;
}
void MeshDemo::onLostDevice()
{
mGfxStats->onLostDevice();
HR(mFX->OnLostDevice());
}
void MeshDemo::onResetDevice()
{
mGfxStats->onResetDevice();
HR(mFX->OnResetDevice());
// The aspect ratio depends on the backbuffer dimensions, which can
// possibly change after a reset. So rebuild the projection matrix.
buildProjMtx();
}
void MeshDemo::updateScene(float dt)
{
mGfxStats->update(dt);
// Get snapshot of input devices.
gDInput->poll();
// Check input.
if( gDInput->keyDown(DIK_W) )
mCameraHeight += 25.0f * dt;
if( gDInput->keyDown(DIK_S) )
mCameraHeight -= 25.0f * dt;
// Divide to make mouse less sensitive.
mCameraRotationY += gDInput->mouseDX() / 100.0f;
mCameraRadius += gDInput->mouseDY() / 25.0f;
// If we rotate over 360 degrees, just roll back to 0
if( fabsf(mCameraRotationY) >= 2.0f * D3DX_PI )
mCameraRotationY = 0.0f;
// Don't let radius get too small.
if( mCameraRadius < 5.0f )
mCameraRadius = 5.0f;
// The camera position/orientation relative to world space can
// change every frame based on input, so we need to rebuild the
// view matrix every frame with the latest changes.
buildViewMtx();
}
void MeshDemo::drawScene()
{
// Clear the backbuffer and depth buffer.
HR(gd3dDevice->Clear(0, 0,
D3DCLEAR_TARGET
| D3DCLEAR_ZBUFFER, 0xffff0000, 1.0f, 0));
HR(gd3dDevice->BeginScene());
// Let Direct3D know the vertex buffer, index buffer and vertex
// declaration we are using.
HR(gd3dDevice->SetStreamSource(0, mVB, 0, sizeof(VertexPos)));
HR(gd3dDevice->SetIndices(mIB));
HR(gd3dDevice->SetVertexDeclaration(VertexPos::Decl));
// Setup the rendering FX
HR(mFX->SetTechnique(mhTech));
// Begin passes.
UINT numPasses = 0;
HR(mFX->Begin(&numPasses, 0));
for(UINT i = 0; i < numPasses; ++i)
{
HR(mFX->BeginPass(i));
HR(mFX->SetMatrix(mhWVP, &(mView*mProj)));
HR(mFX->CommitChanges());
HR(gd3dDevice->
DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
0, 0, mNumGridVertices,
0, mNumGridTriangles));
drawCylinders();
drawSpheres();
HR(mFX->EndPass());
}
HR(mFX->End());
mGfxStats->display();
HR(gd3dDevice->EndScene());
// Present the backbuffer.
HR(gd3dDevice->Present(0, 0, 0, 0));
}
void MeshDemo::buildGeoBuffers()
{
std::vector verts;
std::vector indices;
GenTriGrid(100, 100, 1.0f, 1.0f,
D3DXVECTOR3(0.0f, 0.0f, 0.0f), verts, indices);
// Save vertex count and triangle count for DrawIndexedPrimitive arguments.
mNumGridVertices = 100*100;
mNumGridTriangles = 99*99*2;
// Obtain a pointer to a new vertex buffer.
HR(gd3dDevice->CreateVertexBuffer(mNumGridVertices * sizeof(VertexPos),
D3DUSAGE_WRITEONLY,
0, D3DPOOL_MANAGED, &mVB, 0));
// Now lock it to obtain a pointer to its internal data, and write the
// grid's vertex data.
VertexPos* v = 0;
HR(mVB->Lock(0, 0, (void**)&v, 0));
for(DWORD i = 0; i < mNumGridVertices; ++i)
v[i] = verts[i];
HR(mVB->Unlock());
// Obtain a pointer to a new index buffer.
HR(gd3dDevice->CreateIndexBuffer(mNumGridTriangles*3*sizeof(WORD),
D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &mIB, 0));
// Now lock it to obtain a pointer to its internal data, and write the
// grid's index data.
WORD* k = 0;
HR(mIB->Lock(0, 0, (void**)&k, 0));
for(DWORD i = 0; i < mNumGridTriangles*3; ++i)
k[i] = (WORD)indices[i];
HR(mIB->Unlock());
}
void MeshDemo::buildFX()
{
// Create the FX from a .fx file.
ID3DXBuffer* errors = 0;
HR(D3DXCreateEffectFromFile(gd3dDevice, "transform.fx",
0, 0, D3DXSHADER_DEBUG, 0, &mFX, &errors));
if( errors )
MessageBox(0, (char*)errors->GetBufferPointer(), 0, 0);
// Obtain handles.
mhTech = mFX->GetTechniqueByName("TransformTech");
mhWVP = mFX->GetParameterByName(0, "gWVP");
}
void MeshDemo::buildViewMtx()
{
float x = mCameraRadius * cosf(mCameraRotationY);
float z = mCameraRadius * sinf(mCameraRotationY);
D3DXVECTOR3 pos(x, mCameraHeight, z);
D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMatrixLookAtLH(&mView, &pos, &target, &up);
}
void MeshDemo::buildProjMtx()
{
float w = (float)md3dPP.BackBufferWidth;
float h = (float)md3dPP.BackBufferHeight;
D3DXMatrixPerspectiveFovLH(&mProj, D3DX_PI * 0.25f, w/h, 1.0f, 5000.0f);
}
void MeshDemo::drawCylinders()
{
D3DXMATRIX T, R;
D3DXMatrixRotationX(&R, D3DX_PI*0.5f);
for(int z = -30; z <= 30; z+= 10)
{
D3DXMatrixTranslation(&T, -10.0f, 3.0f, (float)z);
HR(mFX->SetMatrix(mhWVP, &(R*T*mView*mProj)));
HR(mFX->CommitChanges());
HR(mCylinder->DrawSubset(0));
D3DXMatrixTranslation(&T, 10.0f, 3.0f, (float)z);
HR(mFX->SetMatrix(mhWVP, &(R*T*mView*mProj)));
HR(mFX->CommitChanges());
HR(mCylinder->DrawSubset(0));
}
}
void MeshDemo::drawSpheres()
{
D3DXMATRIX T;
for(int z = -30; z <= 30; z+= 10)
{
D3DXMatrixTranslation(&T, -10.0f, 7.5f, (float)z);
HR(mFX->SetMatrix(mhWVP, &(T*mView*mProj)));
HR(mFX->CommitChanges());
HR(mSphere->DrawSubset(0));
D3DXMatrixTranslation(&T, 10.0f, 7.5f, (float)z);
HR(mFX->SetMatrix(mhWVP, &(T*mView*mProj)));
HR(mFX->CommitChanges());
HR(mSphere->DrawSubset(0));
}
}
下载本文顶部的 MeshDemo.zip 文件。同样,将文件解压到您 Visual Studio Projects 目录中一个新建的子文件夹中。请注意,此源代码包含 DirectX 9 版本的头文件。所以现在我们应该对建模场景的理念有所了解了。构成该场景的所有对象都是小的三角形。每个对象都是在局部坐标系中设计的,然后放置在一个世界坐标系中,其顶点必须通过矩阵变换过程进行定位、定向和缩放。在创建并显示窗口后,我们创建一个 Direct3D 对象,该对象创建一个 Direct3D 设备。现在让我们看一个更深入、更复杂的主题。
粒子系统
当你下载 DirectX SDK 时,你会得到一个示例浏览器,其中包含许多高级的 DirectX 示例,其中一些使用了“着色器”。本文不会涉及着色器,但关于它们的信息有很多。只需知道着色器文件通常具有 .fx 文件扩展名,并且文件本身包含一个数据结构。然而,粒子系统需要更深入的理解。请看下面的图片。
下载 fires.zip 文件并运行它。由于 DirectX 技术的特性,特别是顶点缓冲区,火焰会闪烁,就像波浪闪烁和文本闪烁一样。粒子是一个非常小的物体,在数学上通常被建模为一个点。因此,点图元(D3DPRIMITIVETYPE
的 D3DPT_POINTLIST
)将是显示粒子的一个很好的选择。然而,点图元被光栅化为单个像素。这并没有给我们太大的灵活性,因为我们希望有各种大小的粒子,甚至将整个纹理映射到这些粒子上。
研究火焰时,首先要检查这个系统是如何创建粒子的,然后再看效果文件。
参考文献
- MSDN 库
- 《3D 游戏编程入门》,作者 Frank Luna