Direct3D 10 入门






4.33/5 (13投票s)
使用 Direct3D 10 创建您的第一个应用程序。
引言
欢迎来到 Direct3D 10 编程系列教程。要跟上本教程,您需要熟悉 C++ 编程。我假设您也对 Win32 API 编程略有了解,并且知道如何使用该 API 创建一个简单的窗口(如果您不熟悉,请先查看这些 Win32 API 教程)。
Direct3D 10 带来了许多承诺;它旨在将游戏和实时应用程序的视觉水平提升到新的高度。近期图形硬件在质量和性能上的巨大提升带来了新的需求和必要性,而 Direct3D 10 将回应这些需求,并为未来的改进和新功能提供一个灵活的平台。
基本概念
前缓冲
与显示器屏幕上的实际像素绑定的缓冲区。
后缓冲
D3D 使用后缓冲作为输出缓冲区,然后在每一帧中交换后缓冲和前缓冲以显示场景。这可以避免显示器图像闪烁。
渲染目标
Direct3D 设备用于渲染的表面。
DXGI
DirectX 10 设计中的第一个新组件是 DirectX 图形基础结构,它是提供图形硬件访问的主要设备无关层;DXGI 负责处理以前在 D3D API 中实现的某些基本功能。
应用程序可以通过 Direct3D 10 API 与 DXGI 通信,但有时您需要直接访问 DXGI,例如枚举视频适配器、呈现场景等。
Direct3D 设备
Direct3D 的大部分核心功能都可以通过 Direct3D 设备接口访问,因此我们首先需要创建一个 D3D 设备。但在此之前,我们需要有一个可见的窗口。
// Create A Window Class Structure
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = WndProc;
wc.lpszClassName = "GPORG";
wc.style = CS_HREDRAW | CS_VREDRAW;
// Register Window Class
RegisterClassEx(&wc);
// Create Window
hWnd = CreateWindowEx(0, "GPORG",
"GameProgrammer.org Direct3D 10 Tutorial",
WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, width,
height, NULL,NULL,wc.hInstance,0);
此代码注册了一个新的窗口类,并使用该类创建了一个新窗口。(此代码在 Win32 SDK 教程部分有详细解释)。现在,我们通过调用 D3D10CreateDeviceAndSwapChain()
函数来创建一个 D3D10 设备。
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory( &swapChainDesc, sizeof(swapChainDesc) );
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hWnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.Windowed = TRUE;
if( FAILED( D3D10CreateDeviceAndSwapChain( NULL,
D3D10_DRIVER_TYPE_HARDWARE, NULL, 0,
D3D10_SDK_VERSION, &swapChainDesc,
&pSwapChain, &pDevice ) ) )
{
Error("Failed to create device and swap chain.");
return false;
}
顾名思义,此函数创建了一个 Direct3D 设备和一个 DXGI 交换链。交换链负责从 D3D 设备获取数据并在屏幕上显示。要创建交换链,D3D 需要了解有关它的几个细节,您需要使用 DXGI_SWAP_CHAIN_DESC
结构来提供这些信息。查看 DXGI_SWAP_CHAIN_DESC
定义,您会发现它包含另外两个结构:DXGI_MODE_DESC
和 DXGI_SAMPLE_DESC
。
typedef struct DXGI_SWAP_CHAIN_DESC
{
DXGI_MODE_DESC BufferDesc;
DXGI_SAMPLE_DESC SampleDesc;
DXGI_USAGE BufferUsage;
UINT BufferCount;
HWND OutputWindow;
BOOL Windowed;
DXGI_SWAP_EFFECT SwapEffect;
UINT Flags;
} DXGI_SWAP_CHAIN_DESC;
DXGI_MODE_DESC
包含显示模式的描述
typedef struct DXGI_MODE_DESC
{
UINT Width;
UINT Height;
DXGI_RATIONAL RefreshRate;
DXGI_FORMAT Format;
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
DXGI_MODE_SCALING Scaling;
} DXGI_MODE_DESC;
在这里,我们仅使用了 Width
、Height
和 Format
参数,它们分别描述了我们缓冲区的宽度、高度和显示格式。DXGI_SAMPLE_DESC
描述了资源的抗锯齿参数。
typedef struct DXGI_SAMPLE_DESC
{
UINT Count;
UINT Quality;
} DXGI_SAMPLE_DESC;
Count
表示每像素采样数,第二个参数描述了图像质量级别。将 Count
设置为 1,Quality
设置为 0,我们就可以禁用抗锯齿功能。
回到 DXGI_SWAP_CHAIN_DESC
结构,我们将介绍其余参数:BufferCount
是交换链中的缓冲区数量;我们只希望在此创建前缓冲,因此将其设置为 1。通过将 BufferUsage
设置为 DXGI_USAGE_RENDER_TARGET_OUTPUT
,我们声明该缓冲区将用作渲染目标输出。OutputWindow
是我们希望用于表示渲染输出的窗口的句柄。最后一个参数 Windowed
指示应用程序是全屏模式还是窗口模式启动。(您可以在运行时通过 ALT+Enter 切换全屏模式)。
现在,让我们仔细看看 D3D10CreateDeviceAndSwapChain()
函数
HRESULT D3D10CreateDeviceAndSwapChain(
IDXGIAdapter *pAdapter,
D3D10_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
UINT SDKVersion,
DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
IDXGISwapChain **ppSwapChain,
ID3D10Device **ppDevice);
第一个参数是指向 DXGI 适配器的指针;将其设置为 NULL
,Direct3D 将为我们创建一个默认适配器。第二个参数描述了要使用的显示驱动程序的类型;最有可能的是,我们希望使用硬件驱动程序,因此将其设置为 D3D10_DRIVER_TYPE_HARDWARE
。下一个参数应为 null
,除非您想使用自定义软件光栅化器,这非常不可能。我们不使用任何特殊标志,因此将 Flags
设置为 0。SDKVersion
应始终设置为 D3D10_SDK_VERSION
。对于下一个参数,我们应传递一个指向我们刚刚创建的 DXGI_SWAP_CHAIN_DESC
结构的指针。最后两个参数是指向交换链和 D3D 设备指针,Direct3D 将为我们创建它们。
现在我们已经创建了一个 D3D 设备和一个交换链,我们需要将它们连接起来,以便交换链中的后缓冲可以被 D3D 设备用作渲染目标(您可能会想为什么 D3D10CreateDeviceAndSwapChain()
函数不自动完成此操作;答案是,这样可以为您提供更大的灵活性)。为了建立这种连接,我们需要将后缓冲从交换链获取为一个纹理,使用该纹理创建一个渲染目标,并将其作为活动渲染目标传递给 D3D 设备,这段代码将完成这一切
ID3D10Texture2D *pBackBuffer;
if( FAILED( pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ),
(LPVOID*)&pBackBuffer ) ) )
{
Error("Failed to create back buffer.");
return false;
}
if(FAILED( pDevice->CreateRenderTargetView( pBackBuffer, NULL,
&pRenderTargetView )))
{
Error("Failed to create render target view.");
return false;
}
pBackBuffer->Release();
pDevice->OMSetRenderTargets( 1, &pRenderTargetView, NULL );
初始化部分到此完成;现在我们想使用 Direct3D 来清除屏幕
pDevice->ClearRenderTargetView( pRenderTargetView, D3DXVECTOR4(0, 0, 0, 1) );
pSwapChain->Present( 0, 0 );
pDevice->ClearRenderTargetView()
函数用指定的颜色(这里是黑色,r=0, g=0, b=0, a=1)清除整个渲染目标;之后,我们使用 pSwapChain->Present()
将场景显示在屏幕上。交换链将自动为我们处理后缓冲和前缓冲的交换。就这样,我们在这里创建了第一个 Direct3D 10 应用程序。如果在屏幕上有形状,那会更有趣。您可以参考 MSDN 获取有关 Direct3D 10 的更多信息,或者等待我们在这里的下一个教程 这里。
代码
最后,我提供了应用程序的完整源代码。要使其正常工作,您需要一个 Visual Studio 中的空项目,在项目设置中将字符集设置为“使用多字节字符集”,然后添加一个包含此代码的 *cpp* 文件
#include <windows.h>
#include <d3d10.h>
#include <d3dx10.h>
#define Error(X) MessageBox(NULL, X, "Error", MB_OK)
class Application
{
public:
Application();
~Application();
bool CreateD3DWindow(int width, int height);
bool Initialize();
void Render(float deltaTime);
void MainLoop();
private:
static LRESULT CALLBACK WndProc( HWND hWnd , UINT message ,
WPARAM wParam , LPARAM lParam);
HWND hWnd;
IDXGISwapChain* pSwapChain;
ID3D10RenderTargetView* pRenderTargetView;
ID3D10Device* pDevice;
int width, height;
};
#pragma comment (lib, "d3d10.lib")
#pragma comment (lib, "d3dx10.lib")
Application::Application()
{
}
Application::~Application()
{
}
LRESULT CALLBACK Application::WndProc( HWND hWnd , UINT message ,
WPARAM wParam , LPARAM lParam)
{
switch(message){
case WM_CLOSE:
case WM_DESTROY:
PostQuitMessage(0);
break;;
}
return DefWindowProc(hWnd,message,wParam,lParam);
}
bool Application::CreateD3DWindow(int width, int height)
{
this->width = width;
this->height = height;
// Create A Window Class Structure
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = WndProc;
wc.lpszClassName = "GPORG";
wc.style = CS_HREDRAW | CS_VREDRAW;
// Register Window Class
RegisterClassEx(&wc);
// Create Window
hWnd = CreateWindowEx(0, "GPORG",
"GameProgrammer.org Direct3D 10 Tutorial",
WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT,
CW_USEDEFAULT, width, height, NULL,NULL,wc.hInstance,0);
return true;
}
bool Application::Initialize()
{
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory( &swapChainDesc, sizeof(swapChainDesc) );
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Width = width;
swapChainDesc.BufferDesc.Height = height;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = hWnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.Windowed = TRUE;
if( FAILED( D3D10CreateDeviceAndSwapChain( NULL,
D3D10_DRIVER_TYPE_HARDWARE, NULL,
0, D3D10_SDK_VERSION, &swapChainDesc,
&pSwapChain, &pDevice ) ) )
{
if( FAILED( D3D10CreateDeviceAndSwapChain( NULL,
D3D10_DRIVER_TYPE_REFERENCE, NULL,
0, D3D10_SDK_VERSION, &swapChainDesc,
&pSwapChain, &pDevice ) ) )
{
Error("Failed to create device and swap chain.");
return false;
}
}
ID3D10Texture2D *pBackBuffer;
if( FAILED( pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ),
(LPVOID*)&pBackBuffer ) ) )
{
Error("Failed to create back buffer.");
return false;
}
if(FAILED( pDevice->CreateRenderTargetView( pBackBuffer,
NULL, &pRenderTargetView )))
{
Error("Failed to create render target view.");
return false;
}
pBackBuffer->Release();
pDevice->OMSetRenderTargets( 1, &pRenderTargetView, NULL );
D3D10_VIEWPORT vp = {0, 0, width, height, 0, 1};
pDevice->RSSetViewports( 1, &vp );
return true;
}
void Application::Render(float deltaTime)
{
pDevice->ClearRenderTargetView( pRenderTargetView,
D3DXVECTOR4(0, 0, 0, 1) );
pSwapChain->Present( 0, 0 );
}
void Application::MainLoop()
{
MSG msg;
long prevTime = GetTickCount(), curTime = GetTickCount();
while(true)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(msg.message==WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else{
Render((curTime-prevTime)/1000.f);
prevTime = curTime;
curTime = GetTickCount();
}
}
}
INT WINAPI WinMain( HINSTANCE , HINSTANCE , LPSTR , INT )
{
Application app;
if(app.CreateD3DWindow(640, 480))
{
if(app.Initialize())
{
app.MainLoop();
}
}
return 0;
}