转自 http://blog.csdn.net/eclaix/article/details/6372791
3D引擎与底层图形接口。
在真正的开发过程中,我们应该很少有机会直接使用底层的图形接口(DirectX或OpenGL ).因为图形接口直接处理顶点这种最基本数据。直接使用他们编程不是很方便。而3D引擎负责将底层的图形接口进行抽象,形成容易编程的逻辑接口。
但遗憾的是如果对底层接口不了解,似乎也不能发挥3D引擎真正的功效。^_^
毕竟引擎也只是提供一种手段,来方便我们使用图形接口而已。
So,如果希望得到比较高级的图形效果,那么对图形接口的了解,是必要地。
因为在游戏领域DirectX已经逐渐超越了OpenGL,坐上了第一把交椅,所以选择DirectX作为说明对象。现在DirectX主要有三个版本。Ver9, Ver10, Ver11。可以说DirectX9是一个Base,Ver10和11是它的强化版(个人理解)。而且很多基本概念都都可以从DirectX9上理解,所以这次介绍的对象是DirectX9。Ver9, Ver10, Ver11之间的区别,特别是它们较新的渲染管线和Shader会在以后的文章中介绍。(感兴趣的同学可以查阅最新的DirectX图形部分文档。)
另外,DirectX包括很多部分:Direct3D, DirectSound, DirectxInput等。图形部分特指Direct3D。
初级篇大概分成5个部分。
1. 基本概念以及D3D设备
2. D3D资源, 顶点, 变换
3. 光和材质
4. 纹理(texture)及表面(surface)
5. X文件与mesh
而且根据每个部分的内容,都会附上一段
Tutorial代码,帮助理解。(Tutorial全部来自DirectX Sample。因为它们是官方例子,而且可以充分代表相关部分的内容。最重要的是免去了自己动手的麻烦。。。^O^)
另外,如果希望进行DirectX相关编程,需要安装DirectX的SDK。注意不是运行文件。
可以从这个地址下载:
http://msdn.microsoft.com/en-us/directx/aa937788
-------------------------------------------------------------------
那么, 第一部分: 基本概念以及D3D设备
1. DirectX3D概要介绍
下边这张图来自D3D手册. 它表现了windows应用与D3D,GDI,和图形硬件之间的关系. 可以看到, D3D直接或者通过Hal设备来操作图形硬件的设备驱动接口(DDI). 也就是说Win32应用需要通过D3D接口来使用图形硬件的各种功能.
HAL是Hardware abstraction layer的缩写. 顾名思义, 它是硬件的抽象层. 具体怎么抽象我们不需要关注, 只要意识到因为它的存在, D3D可以方便的工作在多种硬件上.
GDI是Graphics Device Interface的缩写. 是微软制作的图形设备接口. 平时画各种窗体时使用的是这一套接口. – 注意, 非游戏专用.
2. D3D设备的介绍, 申请, 释放. COM对象.
根据上一节的内容, 我们可以了解D3D实际是对图形设备的抽象, (或者说是使用图形设备的一种手段) .
D3D手册中是这样定义D3D设备的: 一个D3D设备是D3D的一个渲染组件, 它封装并存储了渲染状态. 一个D3D设备主要执行变换, 光照计算和 将图像光栅化到表面.( rasterizes an image to a surface).
另外, D3D设备有2个种类:
Hal Device. 这种设备直接使用硬件加速. 也是平时我们使用的设备.
Reference Device. 这种设备不使用硬件加速. 只有在debug的时候使用.(比如你想使用一个特性, 但是显卡又不支持,那么只好使用这个软件模拟的东西.) 而且, 需要注意的是, 这个东西慢的要死..
刚开始接触D3D的朋友一定对这段说明感到困惑. 没有关系, 因为这个算是D3D设备的定义,直接来自手册, 无法理解是正常的. 只需要知道, 设备有两种类型: HAL 和 REF , 就可以继续进行了.
那么我们第一步就来了解如何申请, 释放这个图形设备.
申请主要分两个步骤. 根据版本号来创建Direct3D对象.
然后, 根据参数创建具体的Direct3D渲染设备.
申请D3D图形设备:
// 声明管理指针
IDirect3D9 * g_pD3D = NULL; // D3D对象
IDirect3DDevice9 * g_pd3dDevice = NULL; // 我们的D3D渲染设备
// 首先, 创建一个D3D对象. 可以看到, 需要传递版本号作为参数.
g_pD3D = Direct3DCreate9( D3D_SDK_VERSION )
// 之后, 设置D3D设备的参数. 之后创建设备的时候会使用它.
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );
d3dpp.Windowed = TRUE; // 窗口模式
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; // 一种提交back buffer的方式. 效率最高
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; // 默认back buffer格式. 与当前显示模式一致
// 之后, 我们创建一个渲染设备.
g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, // 使用默认显卡
D3DDEVTYPE_HAL, // HAL设备. 使用硬件加速.
hWnd, // 窗体句柄. 渲染出来的图像显示在这个窗体里.
D3DCREATE_SOFTWARE_VERTEXPROCESSING, // 选择软件处理顶点. 一般情况下, 我们都需要使用硬件来处理顶点. 因为速度更快. 只有不支持这个功能的情况下才用这个参数.
&d3dpp, // 刚才配置的参数.
&g_pd3dDevice ) ) // 输出. 生成的D3D渲染设备.
--------------------- 分割 --------------------------
释放D3D图形设备:
if( g_pd3dDevice != NULL )
g_pd3dDevice->Release();
if( g_pD3D != NULL )
g_pD3D->Release();
以上就是最小限的申请, 释放操作. 第一次接触的话, 可能对有些做法有些疑问.
比如g_pD3D通过函数返回值得到, 而g_pd3dDevice为什么变成了返回值方式?
释放的时候, 不是使用delete而是release()方法?
原因是因为, D3D对象都是COM对象.
而Direct3DCreate9()这个函数, 并非COM方法, 传递进去的版本号, 也是为了生成必要的CLSID.
而在使用g_pD3D->CreateDevice()方法时, 已经变成对COM对象的操作. 需要遵守COM协议. COM协议的一个重要机制, 就是返回值必须是HRESULT类型. 用来标明成功&失败信息.
而且COM对象释放时, 不能直接Delete, 必须调用自身的release()方法.
另外, 所有的COM接口都具有前缀大写字母 I , 通过这一点, 可以方便的辨认.
关于COM的讨论已经超出这篇文章的范围, 具体需要查阅相关资料.
3. 渲染过程, Buffer交换.
这一节中主要讨论D3D的渲染过程.
一个最小限度的渲染函数如下.
其中有4个动作. Clear(). BeginScene(). EndScene(). Present().
VOID Render()
{
// 将backbuffer设置为蓝色.
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 0, 255 ), 1.0f, 0 );
// 开始这个scene的渲染.
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
// 各种渲染指令在这部分追加.
// 结束这个scene.
g_pd3dDevice->EndScene();
}
// 提交这个back buffer.
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
对于其中的back buffer 和 提交(present)动作, 有必要说明.
假设你的显示器分辨率为 1280*720. 那么也就是说存在 1280*720 = 921 600个象素点.(720p 为HD格式) 那么, ARGB(4字节)的格式下, 需要 921600 * 4 = 3 686 400 字节的buffer来更新一桢图像. (知道为什么显存一定要大了把.^_^)
然后, 一桢图像的数据提交给硬件以后, 一般是不可以修改的. 那么, 接下来的一桢还是要继续描绘的, 所以, 这样用于显示的buffer, 至少要2个buffer. 假设它们为BufferA和BufferB. 当BufferA绘制完以后, 提交给硬件. 然后继续绘制BufferB. 当第BufferB绘制完成后, 提交; BufferA从硬件中解放, 接下来的绘制在BufferA中进行. 渲染就是这个动作的不断重复.
其中, 提交给硬件的就是Front Buffer. 而在绘制中的就是Back Buffer.
每次导致Back Buffer和Front Buffer交换的动作, 就是提交(present())函数来实现的.
如果提交了一个桢数据, 而不通过present()函数来切换(swap)它的话, 那么用户将一直看到这副图像, 也就是成了静止画.
为了保证用户看到连续的图像, 必须保证每秒至少切换24次(电影标准). 也就是我们通常会接触到桢率这个概念:FPS(frame per second). 它的数值代表了每秒切换的次数. 大家都知道这个值是越高越好. 通常的HD标准的话, 要求是每秒切换60次. 也就是60fps. 这样人物的动作更细腻, 也更逼真.
那么, 为了达到1秒切换60次,平均留给每一桢的处理时间只有1/60秒. 是非常短的时间. 所以实时(real time)3D游戏,对计算机的性能, 还有程序的实现都有很高的要求.
作为程序开发者的我们, 写出低效率的程序, 是不被允许的^_^.
4. 简单游戏循环
根据以上说明, 整理一个最简单的游戏循环.
---------------------------
设置系统状态.
构建场景.
For()
{
1. 处理用户输入. – 处理用户命令.
2. 根据消耗的时间(timeSinceLastFrame)更新系统状态. – 比如有一个等待1s执行的脚本命令, 需要判断它是否需要执行.
3. 根据消耗的时间(timeSinceLastFrame)更新game unit状态. – 各个游戏单元的位置, 动画状态等更新.
4. 各种状态更新完毕以后, 更新显示用数据, 提交给硬件进行显示. (BackBuffer渲染后-> present())
}
清除场景
释放各种资源
---------------------------
5. 例子代码.
因为本节主要说明D3D设备的申请, 释放 和 一个最简单的渲染过程.
所以选取了D3D 例子的 Tutorial 1: CreateDevice 来作为我们的例子程序. 大家可以直接从DirectX Sample Browser来获取.
//-----------------------------------------------------------------------------
// File: CreateDevice.cpp
// 这段代码只是申请释放D3D设备, 并在每桢提交back buffer.
//-----------------------------------------------------------------------------
#include <d3d9.h> // 必要的头文件
// 设备的指针.
IDirect3D9 * g_pD3D = NULL; // D3D对象
IDirect3DDevice9 * g_pd3dDevice = NULL; // 我们的D3D渲染设备
// Name: InitD3D() Desc: Initializes Direct3D
HRESULT InitD3D( HWND hWnd )
{
// Create the D3D object, which is needed to create the D3DDevice.
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
// Set up the structure used to create the D3DDevice.
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof( d3dpp ) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
// Create the Direct3D device. Here we are using the default adapter (most
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
// Device state would normally be set here
return S_OK;
}
// Name: Cleanup() Desc: Releases all previously initialized objects
VOID Cleanup()
{
if( g_pd3dDevice != NULL )
g_pd3dDevice->Release();
if( g_pD3D != NULL )
g_pD3D->Release();
}
// Name: Render() Desc: Draws the scene
VOID Render()
{
if( NULL == g_pd3dDevice )
return;
// Clear the backbuffer to a blue color
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 0, 255 ), 1.0f, 0 );
// Begin the scene
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
// Rendering of scene objects can happen here
// End the scene
g_pd3dDevice->EndScene();
}
// Present the backbuffer contents to the display
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
// Name: MsgProc() Desc: The window's message handler
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
Cleanup();
PostQuitMessage( 0 );
return 0;
case WM_PAINT:
Render();
ValidateRect( hWnd, NULL );
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
// Name: wWinMain() Desc: The application's entry point
INT WINAPI wWinMain( HINSTANCE hInst, HINSTANCE, LPWSTR, INT )
{
// Register the window class
WNDCLASSEX wc =
{
sizeof( WNDCLASSEX ), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle( NULL ), NULL, NULL, NULL, NULL,
L"D3D Tutorial", NULL
};
RegisterClassEx( &wc );
// Create the application's window
HWND hWnd = CreateWindow( L"D3D Tutorial", L"D3D Tutorial 01: CreateDevice",
WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,
NULL, NULL, wc.hInstance, NULL );
// Initialize Direct3D
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
// Show the window
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );
// Enter the message loop
MSG msg;
while( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
UnregisterClass( L"D3D Tutorial", wc.hInstance );
return 0;
}