d3d编程:Ddraw和D3D立即模式编程手册



介绍
DirectX是微软开发并发布游戏开发软件Software包其中有部分叫做Direct3D是 3维图形立即模式绘演API所有人都说它将成为3D图形标准我决定去学习它可它实在是难以琢磨文档也写得很烂总是出错后来我逐渐习惯了这种痛苦我意识到其他人可以从我这里学会些经验这就是我写这篇文章原因

请注意:此篇文章是免费所以不要设想它和些杂志上文章样会那么有想象力或者行文简洁、准确无误但是这最起码要比微软用来哄骗老实游戏那些废话要强得多还有就是本篇文章还在继续撰写中无论如何请原谅诸如排版、语法和例子

我不想总是打那么长各种名称请注意:Direct3D写作D3DDirectDraw写作DDDX则是DirectX简写这篇文章适用于DirectX 3但极有可能也适用于更早版本

在这篇文章中例子代码是来自于我为了更容易读懂在将其加入这篇文章时些变量名已被更改了可能还存在其它明显我为此感到抱歉如果您发现了请告诉我我将努力改正它

在已发行有关DirectX书中我发现DirectDraw Programming很不错作者是Bret Timmins(M&T Books, 1996)这本书写得很好有大量很容易看懂帮助您掌握DirectDraw对于想学习DirectX编程员我推荐这本书


本文组织
本文分成若干小节在每个小节中分别讲述了Direct3D编程痛苦经历个部分

协定
本文假设您使用是DirectXC COM接口在语法上C和C接口只有细微区别 C语言需要通过lpVtbl实现功能还需要COM对象被传递到

某些变量在本文中是统如下:

LPDIRECTDRAW lpDD;
LPDIRECT3DDEVICE lpD3DDevice;
LPDIRECTDRAWSURFACE lpBackBuffer;
LPDIRECTDRAWSURFACE lpFrontBuffer;
LPDIRECTDRAWSURFACE lpZBuffer;
LPDIRECTDRAWCLIPPER lpClipper;
LPDIRECT3D lpD3D;
LPDIRECT3DEXECUTEBUFFER lpExBuf;
D3DEXECUTEDATA ExData;
LPVOID lpBufStart, lpPoer, lpInsStart;
D3DMATRIXHANDLE hModelWorldMatrix;
D3DMATRIXHANDLE hWorldViewMatrix;
D3DMATRIXHANDLE hProjectionMatrix;
修订历史
Release 1.00 (Not so soon!)
将会有对于DirectX5立即模式讨论

Release 0.50 (Coming Soon!)
将会有有关纹理管理材质管理雾化动态光源和重建表面讨论

Release 0.46
修改了处编译小

... ...

Release 0.0
在网站WebSite发布第个版本

致谢
让我高兴是人们开始通过e-mail或其它方式帮助我我要感谢他们感谢他们回应和报告他们是:

Mark Adami
Chris Babcock
James Bowman
Mark Clouden
Ray Gresko
Andrei Voden
Bryan Westhafer
Dennis Ward
Tim Wilson
有关法律责任
本文和所有和本文相关部分版权归Brian Hook所有授权Pigprince中文版版权所有授权您可以通过电子手段( 2进制形式)分发(e-mail、邮寄或存储)本文档全部或部分在没有对本文做任何改变情况下授权容许打印拷贝建议使用最新版本 所有信用和版权声明被保留如果您要在其它站点加个指向本文连接请通知 Brian Hook(E文版)或Pigprince(中文版)取得授权

需要拥有其它发布形式权利如要在公司、组织、商业产品如:书籍、报刊杂志、CD-ROM、应用软件Software等中发布请尊重作者权利(署名权取得报酬权等)

本文没有任何明确或者含蓄表达介绍说明本文是完全正确对于应用本文内容所产生任何结果本人概不负责如果您在开发较高预算工程请您不要只依赖于本文家的言

所有名称商标版权等属于该名称商标版权等法定所有者


--------------------------------------------------------------------------------

Direct3D快速浏览
DirectDraw 和 Direct3D
首先要明了个基本事实:Direct3D是微软用来处理视频硬件API--DirectDraw部分既然D3D是DDraw子接口这就意味着你在使用Direct3D前必需掌握些DDraw基础旦您清楚了这思路会变得清晰但是不要以为这就会使我们所面对事情简单起来

表面
DirectDraw提出了表面(surface)概念表面是存储在内存中幅图形IDirectDrawSurface 通常用来声明前、后绘演缓冲区、Z缓冲和f纹理映射管理

运行缓冲区
所有绘演数据和状态改变是通过运行缓冲区发送到D3D驱动模块运行缓冲是包含命令和数据大块内存它在D3D中作用被吹得天花乱坠您将在后面看到对它详细介绍


--------------------------------------------------------------------------------

令人心烦
D3D员在开发D3D时遇到个难关将是小节将介绍如何对DDraw和D3D进行 3D驱动和3D设备异同还有如何为您开发选择最佳驱动和设备思路方法

该死GUIDs
首先我要解释是GUID问题这是个DirectX员在开发DirectX时始终要面对烦人东西有时甚至感觉比教会狮子狗算算数题还难DirectX通过GUIDs(Globally Unique Identier) 来识别接口如果GUIDs文档介绍说明能清楚点那可真是件值得庆幸事!可惜没有

在使用GUIDs时您可能会遇到 2个难题--目标代码连接出错和指针应用上问题

使用INITGUID时目标代码连接出错
个难题是在试验着写个D3D立即模式时常常会遇到些诸如此类出错信息:

foo.obj : error LNK2001: unresolved external symbol _IID_IDirect3D
Debug/foo.exe : fatal error LNK1120: 1 unresolved externals
Error executing link.exe
这就是臭名昭著INITGUID bug!



发生这个连接原因是在您查寻某个接口时需要这个令人\'振奋\'GUID见下例:

lpDD->QueryInterface( IID_IDirect3D, ( LPVOID * ) &lpD3D );
IID_IDirect3D是个全局常量定要在中声明或者和DXGUID.LIB (缺省在dxsdk/sdk/lib路径下)编译连接否则将会引发连接

个应用中例子化GUIDs
为了在中例子化必需GUID(注意:您也可能在其他DirectX元件中遇到这种情况他们同样需要他们自己GUID)定要在包含()您头文件的前定义()INITGUID这可能是唯正确途径见下例:

FOO.C:

# INITGUID
# <ddraw.h>
# <d3d.h>
In file BAR.C:

// Do not # INITGUID in more than _disibledevent=>


IDirect3DDevice:这是您从由您指明要使用D3D设备驱动中创建软对象 IDirect3DDevice是用来取得你创建运行缓冲并将其传递到驱动模块中

化D3D要走笨拙11个步骤
化D3D确是件使人心烦但是如果您对要完成目标有了较全面了解公平讲还是比较清晰为了取得“准备完毕”状态信息首先要化DDraw(D3D是DDraw部分)然后再来化D3D步骤如下:

例举DDraw系统设备
选择并创建IDirectDraw对象
例举显示模式(可选)
创建IDirect3D对象
为已选择DDraw设备例子化D3D驱动
选择D3D驱动
设置坐标系级别
创建前、后缓冲区剪裁器是必需要创建
创建Z缓冲(可选)
创建 IDirect3DDevice
创建 IDirect3DViewport并和IDirect3DDevice关联
步:Step 1: 例举DDraw系统设备?/A>
应用要做件事就是例举系统中所有DDraw设备这部分工作由DirectDrawEnumerate完成 DirectDrawEnumerate执行次您传递至该对每个设备回叫您可以将回叫值存储在个表单中以便于在您或者使用者来改变要使用设备时进行处理下面
例子就是个DirectDrawEnumerate回叫:

BOOL FAR PASCAL DDEnumCallback( GUID FAR* lpGUID,
LPSTR lpDriverDesc,
LPSTR lpDriverName,
LPVOID lpContext )
{
LPDIRECTDRAW lpDD;
DDCAPS DDcaps, HELcaps;
// _disibledevent=>
/*
** 使用指明GUID试着创建DD设备
*/
( DirectDrawCreate( lpGUID, &lpDD, NULL ) != DD_OK )
{
// 失败忽略该设备
DDENUMRET_OK;
}

/*
** 取得这个DD驱动如果未成功转移至个驱动
*/
mem( &DDcaps, 0, ( DDcaps ) );
DDcaps.dwSize = ( DDcaps );
mem( &HELcaps, 0, ( HELcaps ) );
HELcaps.dwSize = ( HELcaps );
( lpDD->GetCaps( &DDcaps, &HELcaps ) != DD_OK )
{
lpDD->Release;
lpDD = 0;
DDENUMRET_OK;
}

/*
** 是有效设备现在将有关信息存储至设备列表
*/
dl->AddDevice( &DDcaps, lpGUID, lpDriverName, lpDriverDesc );
lpDD->Release;

( dl->IsFull )
DDENUMRET_CANCEL;

DDENUMRET_OK;
}
上面代码应该可以介绍说明问题了但是还有几个小窍门只有对主要设备时lpGUID值为NULL其它所有设备lpGUID都应为非空这个您可能不会立刻发现

另外要特别注意到不要直接存储lpGUID而且您设备信息表结构应该类似于:

struct DriverInfo
{
LPGUID lpGUID;
GUID guid;
// … other stuff
};

当你在表中增加个设备时代码应该如此:

struct DriverInfo *current;
( lpGUID NULL )
{
current->lpGUID = 0;
}

{
current->lpGUID = &current->guid;
memcpy( &current->guid, lpGUID, ( GUID ) );
}
这会使您免于将lpGUID指针乱指其实lpGUID并不重要真正麻烦是GUID我们应该尽可能直接存储GUID

好吧现在我们已经有了个DDraw设备列表其中个是主要设备(lpGUID为空)有0个或更多非主要设备(lpGUID为非空)

第 2步:选择并创建IDirectDraw对象
好了您现在已有了系统中DDraw设备列表但是如果只有个DDraw设备请直接到下如果系统中有几个DDraw设备您就应该让使用者来选择使用哪个设备不要试着确定或使用些复杂判断来确定合适设备应该由用户来决定哪个更适合并保存他们选择

旦您决定了使用哪个设备接下来您要创建个和DDraw设备驱动接口通过DirectDrawCreate传送个指针到您要使用设备GUID中来实现从而取得个指向IDirectDraw对象指针

( DirectDrawCreate( d_selected_dd_device.lpGUID, &lpDD, NULL ) != DD_OK )
goto fail;
第 3步:例举显示模式(可选)
如果您计划要使用满屏模式您就需要使用IDirectDraw::EnumDisplayModes 来例举系统所支持所有显示模式这个通过回叫来实现所有显示模式例举同上所述您也应该将其存储在个列表中提供给用户进行选择

第 4步:创建IDirect3D对象
说真我们等这步等了这么久了!但是我们现在还是没有直接处理D3D途径所以我们不得不在IDirectDraw对象(先前使用DirectDrawCreate创建)中查询它是否支持D3D如果其支持D3D那么同时这个操作也会个指向D3D对象指针请看下面
例子:

BOOL CreateD3D( LPDIRECTDRAW lpDD )
{
( lpDD->QueryInterface( IID_IDirect3D, ( LPVOID *) &lpD3D ) != DD_OK )
FALSE;

TRUE;
}
第 5步:例举D3D驱动
现在桀骜不逊Direct3D已被我们理顺了但还有较长路要走我们已经有了指向IDirectDraw对象(LPDIRECTDRAW)和IDirect3D对象(LPDIRECT3D)指针步我们要做是例举所有关联到DDraw设备D3D驱动步是由名字容易引起误解IDirect3D::EnumDevices思路方法实现(我们要例举是驱动而不是设备)我们将再在这个思路方法中使用回叫来查询所有D3D驱动要注意我们同时也传递了个指向应用数据指针也就是说我们将传递个指向我们想要存储器动信息存储结构指针

但是在我们取得进步的前还是要脚踏实地进行到这里稍微有点棘手我们使用回叫并不需要完整存储对D3D驱动整个描述而应该使用个小窍门技巧--回叫应只存储那些有用信息举例来讲:如有我们需要标准硬件驱动



下面是D3D例举回叫例子:


HRESULT WINAPI EnumD3DDriversCallback( LPGUID lpGuid,
LPSTR lpDeviceDescription,
LPSTR lpDeviceName,
LPD3DDEVICEDESC lpHWDesc,
LPD3DDEVICEDESC lpHELDesc,
LPVOID lpContext )
{
// D3D 驱动管理
D3DDriverList *r = ( D3DDriverList * ) lpContext;
should_keep = 0;

/*
** 这是HAL吗? 检查lpHWDesc->dwFlags我不知道此思路方法是否正确
** 但是它工作得很好而且我没有发现有任何其他思路方法可以做得更好
*/
( lpHWDesc && lpHWDesc->dwFlags != 0 )
{
// 这是个HAL!检查参数如果我们要使用它置should_keep为1
// 如:我们应该让具有16位Z缓冲硬件来完成透明纹理映射
// 注意:如果我们要进行调试就不应该使用硬件加速(表面存储在内存中)
( ( lpHWDesc->dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_PERSPECTIVE ) && ( lpHWDesc->dwDeviceZBufferBitDepth & DDBD_16 ) && !debugging )
{
should_keep = 1;
}
}
( lpHELDesc )
{
// 这是个HEL!检查参数如果我们要使用它置should_keep为1
// 如:我们只想保留RGB模式驱动
( lpHELDesc->dcmColorModel & D3DCOLOR_RGB )
should_keep = 1;
}

/*
** 如果我们需要它记录D3D驱动信息
*/
( should_keep )
{
r->Add( lpGuid, lpDeviceDescription, lpDeviceName );
( r->IsFull )
D3DENUMRET_CANCEL;
}
D3DENUMRET_OK;
}
我希望上面代码就足以介绍说明问题但我还是要在这里画蛇添足番:做为人人都可以完成编程任务我并不是在告诉您如何实现个用来存储D3D驱动我要作解释是lpHELDesc->dcmColorModel思路方法看起来应该是个域而不是个enum就是说在这里应该用您聪明才智来解决它而不要直接看下系统是否支持RGB模式

还有就是我所知道:这个回叫总是在lpHELDesc和lpHWDesc指针中得到非空这使您不可能知晓这个驱动到底是硬件还是软件Software我知道获得这个信息思路方法是检查lpHWDesc中dwFlags参数这是由经验整理总结出来我尚未发现更好解决办法

我早期有关代替存储lpGUID而存储GUID解释也在这里了

第 6步:选择D3D驱动
那么好吧我们现在应该已经有了LPDIRECTDRAW、LPDIRECT3D和个用于选择D3D驱动列表再次重复:我们应该要用户来选择要使用哪个驱动!在我们开始认为我们知道哪个更好时我们就已经开始给自己找麻烦了那不是个好主意不要去做使我们变得过大经典做法是弹出个包含所用可以使用D3D驱动对话窗让用户来选择并存储他们选择这就象吃饼干样简单在本文后面将有有关这种做法解释如存储结构:d_selected_driver这个结构通常由来定义我将提供给您它通常包括如:驱动名、描述、能力和GUID等表项

旦用户选择了个驱动在我们创建可以在上面绘演 3角形上帝---D3D设备的前我们还要做些穷极无聊

第 7步:设置坐标系级别
这里并没有过多处理只是选择是运行在窗口模式还是在满屏模式要注意是有些DDraw设备不支持窗口模式(想起了3Dfx Interactive Voodoo芯片)如果要转换到满屏模式则在这里就要用到您在前面例举显示模式的

Anyway, you should have a chunk of code that does something like this: 总的:我们应该由大块代码来完成些我们要做事情如下:

( fullscreen )
{
( lpDD->SetCooperativeLevel( hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK )
0; // 引发
/*
** 设置满屏显示模式
*/
( lpDD->SetDisplayMode( width, height, bpp ) != DD_OK )
0;
}

{
( lpDD->SetCooperativeLevel( hWnd, DDSCL_NORMAL ) != DD_OK )
0; // 引发
}
第 8步:创建前、后缓冲区 (包括剪裁器)
我们现在要创建是绘演缓冲这包括个前缓冲和个后缓冲我将在后面谈到纹理表面

个您要创建缓冲是前缓冲---也被称为首要显示外表然后您还要创建后缓冲而且如果您应用试运行在窗口模式您就还需要创建个剪裁器并把它关联到应用窗口上

下面代码是用来创建满屏模式绘演缓冲例子:

LPDIRECTDRAWSURFACE lpFrontBuffer, lpBackBuffer;

BOOL CreateFullScreenSurfaces( LPDIRECTDRAW lpDD )
{
DDSURFACEDESC ddsd;
DDSCAPS ddscaps;

mem( &ddsd, 0, ( ddsd );

ddsd.dwSize = ( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_3DDEVICE | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;

( lpDD->CreateSurface( &ddsd, &lpFrontBuffer, NULL ) != DD_OK )
{
goto fail;
}
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
( lpFrontBuffer->GetAttachedSurface( &ddscaps, &lpBackBuffer ) != DD_OK )
{
goto fail;
}
TRUE;
fail:
RELEASE( lpFrontBuffer );
FALSE;
}
下面代码是用来创建窗口模式绘演缓冲例子:

BOOL CreateWindowedSurfaces( LPDIRECTDRAW lpDD )
{
DDSURFACEDESC ddsd;
DDSCAPS ddscaps;

mem( &ddsd, 0, ( ddsd );

ddsd.dwSize = ( ddsd );
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;



( lpDD->CreateSurface( &ddsd, &lpFrontBuffer, NULL ) != DD_OK )
{
goto fail;
}

ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS;
ddsd.dwWidth = window_width;
ddsd.dwHeight = window_height;

ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_3DDEVICE;
/*
** 在调试时表面要创建在系统内存中
** 所旦锁定了表面时调试器将不工作
*/
( debugging || !using_hardware )
ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;

ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;

// 创建后缓冲
( lpDD->CreateSurface( &ddsd, &lpBackBuffer , NULL ) != DD_OK )
{
goto fail;
}

// 创建剪裁器并关联到窗口
(lpDD->CreateClipper( 0, &lpClipper, NULL ) != DD_OK )
{
goto fail;
}

( lpClipper->SetHWnd( 0, hWnd ) != DD_OK )
{
goto fail;
}

( lpFrontBuffer->SetClipper( lpClipper ) != DD_OK )
{
goto fail;
}

// 释放剪裁器在我们使用SetClipper设置时会自动获得操作
( lpClipper->Release != DD_OK )
goto fail;

TRUE;
fail:
RELEASE( lpFrontBuffer );
RELEASE( lpBackBuffer );
RELEASE( lpClipper );
FALSE;
}

第 9步:创建Z缓冲
目前我们只是创建了前、后缓冲区如果我们还要在中进行更深缓冲表面移动那么就还要创建Z缓冲如果您不需要您可以忽略这小节和在本文中所有和Z缓冲有关内容

创建Z缓冲是很麻烦如果您要使用它定要在创建 IDirect3DDevice(在后面描述)的前创建Z缓冲点很重要!否则会引发某些稀奇古怪我所用思路方法见下面代码在满屏方式或窗口方式绘演中都工作得很好

/*
** CreateZBuffer
*/
BOOL CreateZBuffer( void )
{
DDSURFACEDESC ddsd;

mem( &ddsd, 0, ( ddsd ) );
ddsd.dwSize = ( ddsd );

/*
** create the Z-buffer
*/
mem( &ddsd, 0 ,(DDSURFACEDESC));
ddsd.dwSize = ( ddsd );
ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS | DDSD_ZBUFFERBITDEPTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER;
ddsd.dwWidth = screen_width;
ddsd.dwHeight = screen_height;

( !debugging || !using_hardware )
ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;

ddsd.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;

/*
** choose a 16-bit Z-buffer depth, at least that\'s what I do
*/
( !( d_selected_driver.d_desc.dwDeviceZBufferBitDepth & DDBD_16 ) )
{
FALSE;
}
ddsd.dwZBufferBitDepth = 16;

( lpDD->CreateSurface( &ddsd, &lpZBuffer, NULL ) != DD_OK )
FALSE;

/*
** attach the Z-buffer to the back buffer
*/
(lpBackBuffer->AddAttachedSurface( lpZBuffer ) != DD_OK )
{
RELEASE( lpZbuffer );
FALSE;
}

TRUE;
}
第十步:创建 IDirect3DDevice
呜呼终于到这里了!我们可以创建D3D设备还可以试着在屏幕上画些东西了在能够做这些的前我们要创建缓冲区原因是D3D设备是由IDirectDrawSurface 创建不要问我为什么我也不知道下面大段代码是用来由用户选定D3D设备驱动并存储起来GUID来创建IDirect3DDevice

BOOL CreateD3DDevice( void )
{
HRESULT hresult;

hresult = lpBackBuffer->QueryInterface( d3d_driver.guid, ( LPVOID * ) &lpD3DDevice );
( hresult != DD_OK )
FALSE;
TRUE;
}

第十步:创建IDirect3DViewport并关联到D3D设备
最后步是:创建IDirect3DViewport并关联到在上步中创建D3D设备这里诀窍是在给dvMinZ和dvMaxZ赋值时要搞清楚Z顺序些原因D3D例子中并未对这些值进行赋值它好象也可以工作下面是代码:


BOOL CreateViewport( void )
{
/*
** Create and add viewport
*/
( lpD3D->CreateViewport( &lpViewport, NULL ) != DD_OK )
{
FALSE;
}
( lpD3DDevice->AddViewport( lpViewport ) != DD_OK )
{
RELEASE( lpViewport );
FALSE;
}

/*
** up the viewport for a reasonable viewing area
*/
D3DVIEWPORT viewdata;
DWORD largest_side;

mem( &viewdata, 0, ( viewdata ) );

/*
** this compensates for aspect ratio
*/
( display_width > display_height )
largest_side = display_width;

largest_side = display_height;

viewdata.dwSize = ( viewdata );
viewdata.dwX = viewdata.dwY = 0;
viewdata.dwWidth = display_width;
viewdata.dwHeight = display_height;
viewdata.dvScaleX = largest_side / 2.0F;
viewdata.dvScaleY = largest_side / 2.0F;
viewdata.dvMaxX = ( float ) ( viewdata.dwWidth / ( 2.0F * viewdata.dvScaleX ) );
viewdata.dvMaxY = ( float ) ( viewdata.dwHeight / ( 2.0F * viewdata.dvScaleY ) );
viewdata.dvMinZ = 1.0F;
viewdata.dvMaxZ = 1000.0F; // choose something appropriate here!

(lpViewport->SetViewport( &viewdata ) != DD_OK )
{
RELEASE( lpViewport );
FALSE;
}
TRUE;
}



第十 2步:化完成
现在我们终于有了D3D设备我已经疲惫不堪了您还成吗?估计也和我样了吧!进行到这里我们已看到了曙光

目前你应该已经有了lpViewport,lpD3DDevice, lpD3D, lpDD, lpClipper,lpZBuffer, lpBackBuffer, 和lpFrontBuffer等指针给您带来作为苦和甜作为您个绘演我建议您:用某种颜色填充后缓冲(提示∶IDirectDrawSurface::Blt通过DDBLT_COPYFILL)然后显示它在本文其它部分是有关DX噩梦延续


--------------------------------------------------------------------------------

糊里糊涂缓存Cache管理
DirectDraw全是有关缓冲管理内容包括表面、和颜色、深度和纹理数据等缓冲管理在这节中对颜色缓冲和Z缓冲进行了些探讨有关纹理部分内容放在让人心碎材质影射部分

缓冲操作:Flipping和Blitting
把您绘演场景显示在屏幕上有两种思路方法flipping和blitting如果您应用工作在满屏方式下您应该使用整页弹出思路方法--flip;如果是在窗口模式下是在前、后缓冲间切换思路方法--Blitting见下例:


( fullscreen )
{
( lpFrontBuffer->Flip( NULL, 1 ) != DD_OK )
// something bad happened;
}

{
RECT src_rect, dst_rect;
POINT pt;

/*
** src_rect is relative to offscreen buffer
*/
GetClientRect( hWnd, &src_rect );

/*
** dst_rect is relative to screen space so needs translation
*/
pt.x = pt.y = 0;
ClientToScreen( hWnd, &pt );
dst_rect = src_rect;
dst_rect.left pt.x;
dst_rect.right pt.x;
dst_rect.top pt.y;
dst_rect.bottom pt.y;

/*
** perform the blit from backbuffer to primary, using
** src_rect and dst_rect
*/
( lpFrontBuffer->Blt( &dst_rect, lpBackBuffer, &src_rect, DDBLT_WAIT, 0 ) != DD_OK )
{
// something bad happened
}
}
清除缓冲区
在某些情况下需要清除表面并将其置为某个颜色下面例子足以介绍说明:


void ClearSurface( LPDIRECTDRAWSURFACE lpDDS, float r, float g, float b )
{
RECT dst;
DDBLTFX ddbltfx;
DWORD fillcolor;
DDSURFACEDESC ddsd;

/*
** compute the fill color
*/
fillcolor = MakeSurfaceRGB( lpDDS, r, g, b );

/*
** get the surface desc
*/
ddsd.dwSize = (ddsd);
lpDDS->GetSurfaceDesc(&ddsd);

mem(&ddbltfx, 0, (ddbltfx));
ddbltfx.dwSize = (DDBLTFX);
ddbltfx.dwFillColor = fillcolor;
dst.left = dst.top = 0;
dst.right = ddsd.dwWidth;
dst.bottom = ddsd.dwHeight;

( lpBackBuffer->Blt( &dst, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx ) != DD_OK )
{
// something bad happened
}
}
这里有个计算填充颜色小窍门技巧不知道是什么原因填充颜色不能以独立模式设定于个桢缓冲中所以在每个应用中您不得不指明如何将个RGB颜色值影射到桢缓冲中这可以有两种思路方法来完成:直接读取有颜色表面RGB值或者写个RGB888颜色值到桢缓冲再读回来

思路方法也许是最‘正确’最起码听起来比后面但是可悲是在这里还是需要些十分乏味步骤基本上您进行以上步骤后可以得到对表面描述和表面上RGB值有了这个描述您就可以决定在RGB888和桢缓冲模式变换值得大小不幸是这样出来结果可能不会在必须指定颜色时候和色度值协同工作

那么我们来看第 2种思路方法:read-back思路方法可能更加可取虽然使用这种思路方法可能使运行慢得象老牛拉破车但您也要想到计算桢缓冲颜色并不是经常要进行

在例子代码中MakeSurfaceRGB是从DirectX SDK中‘偷来使用Win32 APISetPixel实现向桢缓冲写个RGB值然后使用DirectDraw对桢缓冲直接处理能力把它读回来

/*
** this assumes that R, G, and B are passed as floats in the range [0,1]
*/
DWORD MakeSurfaceRGB( LPDIRECTDRAWSURFACE lpDDS, float r, float g, float b )
{
unsigned long dw = 0;
COLORREF cref = RGB( r * 255, g * 255, b * 255 );
COLORREF tmpCref;
DDSURFACEDESC ddsd;
HDC hdc = NULL;

/*
** Get a DC from the surface
*/
( lpDDS->GetDC( &hdc ) != DD_OK )
// something bad happened
0;

/*
** save pixel in surface then store a pixel o the surface
*/
tmpCref = GetPixel( hdc, 0, 0 );
SetPixel( hdc, 0, 0, cref );
lpDDS->ReleaseDC( hdc );

mem( &ddsd, 0, ( ddsd ) );
ddsd.dwSize = ( ddsd );

/*
** lock the back buffer so that we can read back the value
** we just wrote with SetPixel
*/
( lpDDS->Lock( NULL, &ddsd, DDLOCK_WAIT, NULL ) != DD_OK )
{
// something bad happened
// should probably restore the color we wrote out
// earlier, but I\'m too lazy to write that code
0;
}

/*
** read back the color
*/
dw = * ( DWORD * ) ddsd.lpSurface;

/*
** mask off high bits the bit count is not 32
*/
( ddsd.ddpfPixelFormat.dwRGBBitCount != 32 )
dw &= ( ( 1 << ddsd.ddpfPixelFormat.dwRGBBitCount ) - 1 );


/*
** unlock the back buffer
*/
lpDDS->Unlock( NULL );

/*
** restore the pixel we overwrote
*/
( lpDDS->GetDC( &hdc ) DD_OK )
{


SetPixel( hdc, 0, 0, tmpCref );
lpDDS->ReleaseDC( hdc );
}

dw;
}

--------------------------------------------------------------------------------

使人生厌表面管理
Direct3D和DirectDraw讨论是紧紧围绕着表面概念展开它实际上包含大量系统思路方法和存放在现实内存中显示数据举例来讲有:前缓冲、后缓冲、Z缓冲和材质

Direct3D应用是运行在多任务环境中那么正在使用部分内存完全有可能\"丢失\"也就是说被其它应用使用了
既然我们Direct3D有如此古怪特性我们定要检查是否发生这种情况并把它恢复

Checking for Lost Surfaces
(To Be Continued)
Restoring Lost Surfaces
(To Be Continued)
--------------------------------------------------------------------------------

使人发疯状态管理
和Direct3D通信实现几乎总是通过运行缓冲实现应用在其中存储命令(操作码op-codes)和数据然后将整个运行缓冲传递到D3D驱动由驱动解析运行缓冲内容转换驱动运行实际指令并运行

不管在任何图形支持库中画 3角形可能是最基础能力了但也是在D3D帮助中最少提到部分提供例子代码也没有清楚介绍说明是怎样做并且在帮助文件中几乎没有提到如何建立运行缓冲

运行缓冲快速浏览
运行缓冲是块内存它存储了系列DWORD变量组成命令和紧随其后顶点序列他将被传递到D3D驱动并被D3D驱动解析、执行

下面举例介绍说明在 3角形绘演中运行缓冲结构:

数据 长度
+------------------------+----------+ <- lpBufStart (起始地址)
| 定点数据 | 可变 |
+------------------------+----------+ <- lpInsStart
| 操作码 | 固定 |
+------------------------+----------+
| 操作码参数 | (可变) |
+------------------------+----------+
\\ * * \\
/ ( 可 变 数 量 操 作 码 ) /
\\ * * \\
+------------------------+----------+
| (QWORD UNALIGNER) | (固定) | (conditionally inserted)
+------------------------+----------+
| OP_TRIANGLE_LIST | 固定 |
+------------------------+----------+
| 3角形数据 | 可变 |
+------------------------+----------+
+ OP_EXIT | 固定 +
+------------------------+----------+

这个格式由串顶点和紧随其后操作码组成(包括 3角形信息)最后是个结束符OP_EXIT操作码要注意其它格式也是有可能重要是要在IDirect3DExecuteBuffer::SetExecuteData思路方法时设置D3DEXECUTEDATA

顶点数据
顶点数据描述了位置法线和顶点颜色信息(可选)在D3D中有 3种区别定点分别是: D3DVERTEXD3DLVERTEX和D3DTLVERTEX
D3DVERTEX存储是位置(模型坐标)法线和材质坐标信息; D3DLVERTEX存储是位置(模型坐标)漫反射颜色镜反射颜色和材质坐标信息; D3DTLVERTEX存储是位置(屏幕坐标) 漫反射颜色镜反射颜色和材质坐标信息

D3D提供了个用来向运行缓冲拷贝数据VERTEX_DATA这里要注意是虽然VERTEX_DATA宏使用了( D3DVERTEX )声明但是看起来所有D3D顶点结构大小都是相同 可以得出结论:VERTEX_DATA宏可以用于所有 3种D3D顶点结构

处理顶点
然后您需要给定点数据增加指令来“处理定点”意思就是:“给数据增加它应该做动作指令”这个指令就
是:OP_PROCESS_VERTICES仅更着它是PROCESSVERTICES_DATA至于OP_xxx指令只是简单把D3D指令加入到运行缓冲中PROCESSVERTICES_DATA宏是用来添加操作码参数顶点处理形式取决于所使用定点类型

顶点类型 处理形式
D3DVERTEX D3DPROCESSVERTICES_TRANSFORMLIGHT
D3DLVERTEX D3DPROCESSVERTICES_TRANSFORM
D3DTLVERTEX D3DPROCESSVERTICES_COPY


QWORD unalignment
3角形数据需要在QWORD(8位)边界进行结合因此针对 3角形数据指令需要被分开代码如下所示:

( QWORD_ALIGNED( lpPoer ) ) {
OP_NOP( lpPoer );
}
OP_TRIANGLE_LIST( 1, lpPoer );
警告:您必须在执行OP_NOP指令前面加个判断否则编译将出错!这是OP_NOP宏执行千万不要将判断移走

3角形数据
3角形数据是使用OP_TRIANGLE_LIST在n个 3角形数据的后插入在个表中在运行缓冲开始部分 3角形是由 3个顶点数据索引指明注意:采用正确顶点顺序否则你 3角形将把它背面呈现出来把3D图形整个弄拧

OP_EXIT
OP_EXIT指令告诉D3D什么时候停止处理数据并关闭运行缓冲

创建运行缓冲
现在我们已经知道了有关运行缓冲核心秘密现在我们试着来创建这应该还算是简单比较困难部分是计算运行缓冲要消耗多少内存这样将不得不计算所有被存放在缓冲中操作码和数据所占内存大小这实际容易出错部分

还要注意些驱动只支持有限运行缓冲大小这种有限空间大小可能是运行缓冲全部大小或者只是在运行缓冲中顶点最大数值驱动描述标志可以告诉您在当前驱动模式下运行缓冲是否有字节大小限制或定点限制

// make sure driver species a max buffer size
( d_selected_driver.d_desc.dwFlags & D3DDD_MAXBUFFERSIZE )
{
// max buffer size 0 then it\'s unlimited
( d_selected_driver.d_desc.dwMaxBufferSize )
d_max_execute_buffer_size = d_selected_driver.d_desc.dwMaxBufferSize;

d_max_execute_buffer_size = MY_DEFAULT_BUFFER_SIZE;
}

{
d_max_execute_buffer_size = MY_DEFAULT_BUFFER_SIZE;
}
// make sure driver specied a max vertex count
( d_selected_driver.d_desc.dwFlags & D3DDD_MAXVERTEXCOUNT )
{


// max vertex count 0 then it\'s unlimited
( d_selected_driver.d_desc.dwMaxVertexCount )
d_max_vertex_count = d_selected_driver.d_desc.dwMaxVertexCount;

d_max_vertex_count = MY_DEFAULT_VERTEX_COUNT;
}

{
d_max_vertex_count = MY_DEFAULT_VERTEX_COUNT;
}
上面代码很直接介绍说明了问题:如果驱动返回在顶点数目或缓冲大小上有限制是复杂而且容易出现则我们使用该如果没有则使用我们缺省思路方法并确定在当前驱动模式下没有上述两种限制我发现很直接思路方法是创建个运行缓冲并在中所有使用运行缓冲部分直使用它而不要在每次要进行绘演时创建个运行缓冲我不知道这样做对还是不对但是这样可以减少发生可能

计算运行缓冲大小
计算我们需要运行缓冲大小是复杂而且容易出错请注意如下代码:

( D3DINSTRUCTION) * num_opcodes +
( D3DVERTEX ) * num_vertices +
( D3DTRIANGLE ) * num_triangles +
( all parameters to opcodes );
不用我多说上面有多狗屎您可能也看出来了!

举例来讲假定要绘演10个独立 3角形这就意味着你需要下述操作码: OP_EXITOP_PROCESS_VERTICES和OP_TRIANGLE_LIST这样就需要30个定点和10个 3角形存储空间还潜在需要强制QWORD unalignment时OP_NOP操作码空间还有D3DPROCESSVERTICES和OP_PROCESS_VERTICES 参数空间

size = ( D3DINSTRUCTION ) * 4 // OP_EXIT, OP_PROCESSVERTICES, OP_TRIANGELIST, OP_NOP
( D3DPROCESSVERTICES ) +
( D3DTRIANGLE ) * 10 +
( D3DSTATE ) * 0 +
( D3DVERTEX ) * 30;
在这里并没有很完整思路所以请原谅我不能在这里提供有帮助代码我把D3DSTATE指令代码放在这里作为结束] (我是完全不知所云!真是不明白by 译者)

分配运行缓冲
现在我们对运行缓冲进行分配下面进行指定大小运行缓冲分配注意:如果我们试着分配大于驱动(和我们软件Software)可提供空间会失败

LPDIRECT3DEXECUTEBUFFER AllocateExecuteBuffer( size )
{
D3DEXECUTEBUFFERDESC debDesc;
LPDIRECT3DEXECUTEBUFFER exBuf;

// create a D3DEXECUTEBUFFERDESC
mem( &debDesc, 0, ( debDesc ) );
debDesc.dwSize = ( debDesc );
debDesc.dwFlags = D3DDEB_BUFSIZE;
debDesc.dwBufferSize = size;

( size > d_max_buffer_size ) 0;

// create the buffer
( lpD3DDevice->CreateExecuteBuffer( &debDesc, &exBuf, NULL ) != DD_OK )
{
0;
}
exBuf;
}
填充运行缓冲
现在我们知道了如何进行运行缓冲分配下面就是如何使用些指令进行填充我先要再次这些即该死又难看代码请求您原谅但不是为我我发誓这就是D3D设计

锁定运行缓冲
在能修改运行缓冲的前我们还要锁定它就是我们要告诉驱动我们要开始填充了还要依照顺序不能把他弄乱
用IDirect3DExecuteBuffer::Lock思路方法完成此项功能下面代码介绍说明了如何锁定运行缓冲

D3DEXECUTEBUFFERDESC debDesc;

mem( &debDesc, 0, ( debDesc ) );
debDesc.dwSize = ( debDesc );
( lpExBuf->Lock( &debDesc ) != DD_OK )
fail;
运行了IDirect3DExecuteBuffer::Lock思路方法后在debDesc结构中成员lpData指针被置为运行缓冲地址我们使用 3个神奇指针变量(在后面介绍说明)存储这个地址

lpPoer = lpBufStart = lpInsStart = debDesc.lpData;
我已经提到了使用mem来使运行缓冲没有冗余, 但是我认为好象不是很有用因此我没有用它它不会对产生什么坏影响所以如果你想使用它就用例子如下:

mem( debDesc.lpData, 0, d_max_execute_buffer_size );
3个神奇指针变量
在创建运行缓冲时您需要管理如下 3个指针:缓冲起始地址、指令区起始地址和缓冲结束地址般来讲在您锁定运行缓冲时将起始地址存储在lpBufStart中在以后地址运算中您将会使用它lpInsStart也只是在你插入第个操作码时设定最后lpPoer指针是指向当前地址漫游指针用在写入运行缓冲时当你对运行缓冲写入完毕该指针将指向您所写入运行缓冲指令和数据结束地址

这 3个指针在以后计算中是必须如在计算矩阵偏移量指令偏移量和计算整个运行缓冲大小时

The Direct3D Helper Macros
填充运行缓冲包括写入数据、操作码和操作码参数您可以自己写个宏或来对运行缓冲进行填充或者使用由DirectX SDK提供例子中代码(/dxsdk/sdk/samples/misc/d3dmacs.h)使用D3DMACS.H中得宏时要小心他写不是很完美(微软贯作风 By 译者)

填充运行缓冲例子
在这里假定你已经所定了运行缓冲并且已对上面所谈到 3个神奇指针进行了赋值 -- 指向debDesc.lpData下面代码向运行缓冲填充了进行单个 3角形绘演数据

void FillBuffer( D3DVERTEX *vertices, num_vertices,
D3DTRIANGLE triangles, num_tris )
{
i;

VERTEX_DATA( vertices, num_vertices, lpPoer );
lpInsStart = lpPoer;
OP_PROCESS_VERTICES( 1, lpPoer );
PROCESSVERTICES_DATA( D3DPROCESSVERTICES_TRANSFORMLIGHT, 0, num_vertices, lpPoer );
// triangle data must be QWORD aligned, so we need to make sure
// that the OP_TRIANGLE_LIST is unaligned! Note that you MUST have
// the braces {} around the OP_NOP since the macro in D3DMACS.H will
// fail you remove them.
( QWORD_ALIGNED( lpPoer ) ) {
OP_NOP( lpPoer );
}


OP_TRIANGLE_LIST( num_tris, lpPoer );
for ( i = 0; i < num_tris; i ) {
LPD3DTRIANGLE tri = ( LPD3DTRIANGLE ) lpPoer;

tri->v1 = tris[i].v1;
tri->v2 = tris[i].v2;
tri->v3 = tris[i].v3;
tri->wFlags = D3DTRIFLAG_EDGEENABLETRIANGLE;
tri;

lpPoer = ( LPVOID ) tri;
}
OP_EXIT( lpPoer );
}
将以上代码段改为使用D3DTLVERTEX或者D3DLVERTEX顶点类型只要简单改掉在声明部分中D3DVERTEX并把D3DPROCESSVERTICES_TRANSFORMLIGHT分别改为D3DPROCESSVERTICES_COPY或 D3DPROCESSVERTICES_TRANSFORM

执行运行缓冲
在向运行缓冲填充了有意义数据后下面最后步是运行其中指令

对运行缓冲进行解锁
执行运行缓冲步是对运行缓冲进行解锁通知驱动已准备好

lpExBuf->Unlock;
设置运行缓冲数据
现在运行缓冲已经被解锁我们还要使用IDirect3DExecuteBuffer::SetExecuteData 思路方法来通知驱动所有我们已创建指令

D3DEXECUTEDATA d3dExData;
mem(&d3dExData, 0, (D3DEXECUTEDATA));
d3dExData.dwSize = (D3DEXECUTEDATA);
d3dExData.dwVertexCount = num_vertices;
d3dExData.dwInstructionOff = (ULONG)((char*)lpInsStart - (char*)lpBufStart);
d3dExData.dwInstructionLength = (ULONG)((char*)lpPoer - (char*)lpInsStart);
lpExBuf->SetExecuteData(&d3dExData);
上述代码创建了D3DEXECUTEDATA结构置其冗余为0并适当设定它成员其中dwInstructionOff成员是从运行缓冲开始到第个指令数据字节偏移量 dwInstructionLength成员是指令数据字节长度然后IDirect3DExecuteBuffer::SetExecuteData设置这些数据

然后我们将得到个运行缓冲可以被执行状态

执行缓冲内容
执行当前运行缓冲是比较简单只是简单IDirect3DDevice::Execute思路方法并把当前运行缓冲传递给它需要注意是被IDirect3DDevice::Execute 思路方法定要被IDirect3DDevice::BeginScene和IDirect3DDevice::EndScene思路方法括起来就是说IDirect3DDevice::Execute放在IDirect3DDevice::BeginScene/IDirect3DDevice::EndScene 的间不能放在这个外面

( lpD3DDevice->Execute( lpExBuf, lpViewport, D3DEXECUTE_CLIPPED ) != DD_OK )
fail;
检查运行时返回值是十分重要在运行这个思路方法时你可能遇到无数bug注意D3DEXECUTE_CLIPPED只有使用D3DVERTEX或D3DLVERTEX顶点类型才可使用(在使用上述两种顶点情况下如果你知道要进行绘演不需要剪裁算法将其指定为 D3DEXECUTE_UNCLIPPED可以获得小点性能提高)如果使用D3DTLVERTEX就必须自己写剪裁部分代码并指明D3DEXECUTE_UNCLIPPED

清除运行缓冲
清除运行缓冲代码如下:

lpExBuf->Release;
lpExBuf = 0;
另外种选择是使用D3DMACS.H中提供RELEASE宏

RELEASE( lpExBuf );

--------------------------------------------------------------------------------

纯粹该死多边形绘演
既然所有绘演是由运行缓冲执行在进行处理的前你应该对运行缓冲有理解如果您还没有 请参考乱 7 8糟运行缓冲

绘演单独 3角形
绘演带状 3角形组
绘演扇面状 3角形组
绘演凸多边形
(上述定义可参考M$DirectX SDK help中\"Direct3D Immediate Mode Overview\"By 译者)
--------------------------------------------------------------------------------

使人发疯状态管理
状态管理由在中控制绘演输出动作组成如:选择当前纹理、当前阴影模式、雾化模式、雾化颜色等绝大部分状态很少改变如阴影模式或雾化颜色

状态改变可以是全局或局部也可能 2者兼顾全局状态改变通常用来影响所有绘演操作而局部状态改变使用来控制部分特殊绘演任务改变全局状态例子如:选择纹理filter模式这往往是您审美情趣和性能表现等原因想要使用者控制部分而对于局部状态通常是纹理选择物体或网格通常有其特有影射纹理

改变全局状态
全局状态改变通常是通过创建个单独用来管理状态运行缓冲来实现在这个运行缓冲中将没有顶点数据只有指令序列在下面例子中通过创建个单独运行缓冲并执行它实现了改变Z缓冲状态

D3DEXECUTEBUFFERDESC debDesc;
D3DEXECUTEDATA d3dExData;
LPDIRECT3DEXECUTEBUFFER lpD3DExCmdBuf = NULL;
LPVOID lpBuffer, lpInsStart;
size_t size = 0;

/*
** create an execute buffer of the required size
*/
size = 0;
size (D3DSTATE) * 25; // bigger than I need, but it doesn\'t matter
mem(&debDesc, 0, (D3DEXECUTEBUFFERDESC));
debDesc.dwSize = (D3DEXECUTEBUFFERDESC);
debDesc.dwFlags = D3DDEB_BUFSIZE;
debDesc.dwBufferSize = size;

( lpD3DDevice->CreateExecuteBuffer( &debDesc, &lpD3DExCmdBuf, NULL ) != DD_OK )
{
fail;
}
/*
** lock the execute buffer
*/
( lpD3DExCmdBuf->Lock( &debDesc ) != DD_OK )
{
fail;
}

/*
** zero out execute buffer memory
*/
mem( debDesc.lpData, 0, size );

lpInsStart = debDesc.lpData;
lpBuffer = lpInsStart;



/*
** the render state
*/
OP_STATE_RENDER( 3, lpBuffer);
STATE_DATA( D3DRENDERSTATE_ZENABLE, TRUE, lpBuffer );
STATE_DATA( D3DRENDERSTATE_ZFUNC, D3DCMP_LESSEQUAL, lpBuffer );
STATE_DATA( D3DRENDERSTATE_ZWRITEENABLE, TRUE, lpBuffer );
OP_EXIT( lpBuffer );

/*
** unlock the buffer
*/
( lpD3DExCmdBuf->Unlock != DD_OK )
{
fail;
}

/*
** the execute data and execute the buffer
*/
mem( &d3dExData, 0, (D3DEXECUTEDATA) );
d3dExData.dwSize = (D3DEXECUTEDATA);
d3dExData.dwInstructionOff = (ULONG) 0;
d3dExData.dwInstructionLength = (ULONG) ( (char*)lpBuffer - (char*)lpInsStart );

( lpD3DExCmdBuf->SetExecuteData( &d3dExData ) != DD_OK )
{
fail;
}

( lpD3DDevice->Execute( lpD3DExCmdBuf, lpViewport, D3DEXECUTE_UNCLIPPED ) != DD_OK )
{
fail;
}
RELEASE( lpD3DExCmdBuf );
就向您已经看到要使用了这么多代码来完成这个动作不过这里关键点是减少不得不做创建、填充和执行状态管理运行缓冲等等动作时间详细讨论见软件Software/硬件状态体系, 在上面代码介绍说明了在改变状态时得到了什么

改变局部状态
改变局部状态和改变全局状态很相象但是它是直接插入到 3角形/顶点运行缓冲中作为命令流部分举例来讲在绘演多边形时可以通过如上所述手段控制当前纹理

软/硬状态机制
我采用了软/硬范例机制来控制D3D状态管理在这种机制下状态改变被认为是‘软’举例来讲被调入了内存并实际存在后它们并不是立刻运行起来这是他们冻结了或者说是锁定了当前状态这就需要进行状态更新才可以进行进动作

所以软状态就是处于“傲慢”并实际存在当前状态(但不要指望它会响应什么)而硬状态才是D3D实际控制下状态软状态向硬状态转变是在要开始进行绘演时由些清楚明了命令来完成

为什么这么大惊小怪?就象我说过那样D3D状态改变是十分麻烦而且开销很大我使用了这样思路方法创建个全局状态运行缓冲它只是在状态需要被冻结时才被刷新和执行这样做可以使状态运行缓冲创建/填充/执行/释放循环开销降为最低

可见实现软/硬状态机制实现是设计些管理软状态比如我们可以用个全局结构来容纳我们认为相关D3D状态于是我们就会需要些独立来控制Z缓冲见下例:

void APISetZFunc( D3DCMPFUNC cmp )
{
app_state.d_zfunc = cmp;
}
使用了软/硬状态机制可以任意改变它状态不必担心改变状态时性能改变需要注意是在状态被冻结时性能有可能受影响不过般在绘演动作前进行不会有什么问题

旦软状态管理设计好了我们只需要用取得全局状态并插入到运行缓冲中就可以有效将状态冻结这看起来几乎和全局状态改变例子效果相同而实际上主要区别点在于有更多状态变量被更新并且它们值来自和全局状态见下例:

/*
** the render state
*/
OP_STATE_RENDER( NUM_STATE_VARIABLES, lpBuffer);
STATE_DATA( D3DRENDERSTATE_ZENABLE, app_state.d_zenable, lpBuffer );
STATE_DATA( D3DRENDERSTATE_ZFUNC, app_state.d_zfunc, lpBuffer );
STATE_DATA( D3DRENDERSTATE_ZWRITEENABLE, app_state.d_zwriteenable, lpBuffer );
// etc. etc.
OP_EXIT( lpBuffer );

--------------------------------------------------------------------------------

纯属添乱转换矩阵
D3D中定义了 3个区别转换矩阵转换矩阵是在IDirect3DDevice::Execute思路方法时进行了设置 D3DPROCESSVERTICES_TRANSFORM或
D3DPROCESSVERTICES_TRANSFORMLIGHT后使用

这 3个矩阵分别为:modelworld,worldview和projection modelworld矩阵用来将模型或物体坐标转换为虚拟3D世界(就是在计算机屏幕中那个世界)坐标系统; worldview矩阵用来转换虚拟3D坐标到视角坐标(3D to 2Dby 译者);最后是projection矩阵转换视角坐标到屏幕坐标这个动作是通过依照比例影射到屏幕视口变换实现

创建矩阵
矩阵是使用IDirect3DDevice::CreateMatrix思路方法创建创建完毕后返回个由D3DMATRIXHANDLE类型表示矩阵句柄

D3DMATRIXHANDLE hMatrix;
( lpD3DDevice->CreateMatrix( &hMatrix ) != DD_OK )
fail;
这个矩阵句柄可以简单表示D3D中任何类型矩阵

设置矩阵
下面进行矩阵设置这项工作是通过向D3DMATRIX结构赋值并IDirect3DDevice::SetMatrix D3DMATRIX结构并不是想像中那样每个元素都有明确命名正相反是使用维或 2维浮点数

D3DMATRIX m;

// the following code s \"m\" to an identity matrix
m._11 = 1.0F;
m._12 = 0.0F;
m._13 = 0.0F;
m._14 = 0.0F;
m._21 = 0.0F;
m._22 = 1.0F;
m._23 = 0.0F;
m._24 = 0.0F;
m._31 = 0.0F;
m._32 = 0.0F;
m._33 = 1.0F;
m._34 = 0.0F;
m._41 = 0.0F;
m._42 = 0.0F;
m._43 = 0.0F;
m._44 = 1.0F;
( lpD3DDevice->SetMatrix( hMatrix, &m ) != DD_OK )
fail;
设置全局矩阵
这 3个全局矩阵(modelworldworldview和projection)是在创建状态运行缓冲时使用适当操作马进行设置如下代码所示:

OP_STATE_TRANSFORM( 3, lpBuffer );
STATE_DATA( D3DTRANSFORMSTATE_WORLD, hModelWorldMatrix, lpBuffer );
STATE_DATA( D3DTRANSFORMSTATE_VIEW, hWorldViewMatrix, lpBuffer );
STATE_DATA( D3DTRANSFORMSTATE_PROJECTION, hProjectionMatrix, lpBuffer );
除非您需要多个矩阵再绘演时进行变换否则您只需要设置这 3个转换矩阵它们在IDirect3DDevice::SetMatrix思路方法时将被自动
设置



For more information _disibledevent=> ( hMatrix )
{
lpD3DDevice->DeleteMatrix( hMatrix );
hMatrix = NULL;
}
直接向运行缓冲插入矩阵
直接向运行缓冲插入矩阵也是有可能我在编程时没有用到这种情况也没有自己亲手试所以也没有发言权

D3D矩阵介绍说明
D3D矩阵和顶点变换系统分别由4X4矩阵和1X4向量D3D坐标系统是左手坐标系并且假定使用是行向量同样D3DMATRIX矩阵也是行向量组成

在这点上是和OpenGL系统相反在OpenGL系统中使用是列向量(矩阵由各个空间排列组成)而且OpenGL系统使用是右手坐标系并使用列向量

我还没有发现D3Dprojection据真是如何工作所以我只好使用了DX文档内容


--------------------------------------------------------------------------------

让人心碎材质影射
(To Be Continued)
--------------------------------------------------------------------------------

您将诅咒调试
调试D3D十分象是在吃碎玻璃片!为了使这种玻璃片变得好吃点在这节中我们探讨些调试D3D基本规律

规律 #1: 检查返回值
这是最重要条规律定要检查您使用个DirectX返回值甚至看起来绝对没有毛病都有可能有问题!举例来讲有些驱动是不能工作在些显示分辨率下 1152X864就是个例子如够您使用了这个分辨率则不会有什么明显原因就会失败检查返回值可以大大节省你浪费在调试中时间

规律 #2: 使用调试库(Debugging Lib)
个原因您就因该使用调试库:如果您使用VC他们将把调试信息出错状态信息打印在Message窗口内(borland悲哀!by 译者)不用您手工去找

规律 #3: 在每个您能找到硬件设备上测试
并不是象SGIOpenGL那样D3D在某个硬件设备上可以运行并不意味着就可以在其它设备上运行因此你要在尽可能多硬件设备上测试你D3D应用

规律 #4: 使用OutputDebugString来帮助测试
也许OutputDebugString是世界上最好它可以把调试信息输出到系统调试信息中如果你使用是MSVC编译器你就会感到他诸多好处(又是Borland悲哀! by 译者)

规律 #5: 支持软加速和窗口模式绘演
D3D调试简直就是场恶梦但这个规律也许可以使你脱离梦魇要注意是让你在窗口模式下工作只可以使你可以进行单步跟踪使调试变得容易些;第 2是关闭硬件加速只使用软件Software加速而且在尽可能情况下使用系统内存进行绘演这可以使你免于由于Win16锁定而导致down机(见规律 #6)

规律 #6: 记住锁定对调试有害
正像在规律 #5中所说可能在执行个缓冲或表面时有可能不经意导致Win16锁定 Win16锁定般会使系统挂起而在调试时发生这种状况往往是你认为是代码有问题而代码则可能是正确为了避免这发生您在调试时应该尽可能使用软件Software加速和系统内存表面(见规律 #5)并且千万不要把条使用断点放在Lock/Unlock中间行中

规律 #7: 在尽可能多硬件设备上验证你D3DD3D电汇眼规范标准是够不错了(几乎没有)所以在每个你可D3D加速器上策是你是十分重要如果需要加上软件Software加速驱动但是在大多数情况下这种思路方法被证明是不可行即使是最基本功能也会大打折扣

规律 #8: 如果可能就是用双机调试
把两台机器连接起来台机器上运行在另台上运行调试这样做有很多优点比如可以调试locks/unlocks对的间代码还有在Down机情况下保持稳定能力不过这就意味着你需要两台机器(钱哪!)而且十分
 

Tags:  ddraw.h ddraw ddraw.dll d3d编程

延伸阅读

最新评论

发表评论