dx绘制2d图像_在DirectX 中进行2D渲染
http://flcstudio.blog.163.com/blog/static/756035392008115111123672/
最近,我看到很多關于DirectX8在最新的API中摒棄DirectDraw的問題。很多人回到了以前的DX7.1中。我可以理解那些在DX7.1中有很多開發經驗的人為什么這樣做,但是有很多問題卻是來自于那些剛學DX,還沒有學過以前的API的初學者。人們爭辯說很多人沒有3D硬件,因此D3D對于DirectDraw是個錯誤的選擇。我不相信那是真的,在D3D中進行2D渲染只需要做一點頂點操作,而其他的事情都可以被精簡來提高填充率。簡言之,在D3D中使用2D硬件進行2D渲染,可以做到和DirectDraw一樣好的性能,有很好的填充率。而優點是,程序員可以學習最新的API,并且在更新的硬件中獲得更好的性能。這篇文章將給出一個在DX8中進行2D渲染的框架,以便于從DirectDraw到Direct3D的轉變。在每一節里,你會看到一些你不喜歡的東西(“我是一個2D程序員,我不用關心頂點!”)。但是,請放心,只要你將這個簡單的框架實現一次,你就再也不會考慮那些了。
假設你已經有DX8 SDK,那兒有一組指南講述了如何創建一個D3D設備,如何放置渲染循環,因此我不想再在這上面花費時間。按照這篇文章的意圖,我將談論位于[DX8SDK]samplesMultimediaDirect3DTutorialsTut01_CreateDevice目錄里的指南,你可能將它放到了任何地方。在那個例子中,我將加入以下函數:
- 其他所有事情都設置完之后,這個函數被app調用,你已經創建了你的設備并且所有東西都已經初始化了。如果你跟著往下看指南里的代碼,你會看到WinMain是這樣的:
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
PostInitialize(200.0f, 200.0f); // This is my added line. The values of
// 200.0f were chosen based on the sizes
// used in the call to CreateWindow.
ShowWindow( hWnd, SW_SHOWDEFAULT );
...
void Render2D()
- 這個函數當你渲染你的場景時會被調用,指南里的Render函數現在看起來是這樣的:
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
g_pd3dDevice->BeginScene();
Render2D(); //My added line...
// End the scene
g_pd3dDevice->EndScene();
// Present the backbuffer contents to the display
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
好了,這就是我們的程序外殼,現在來準備好的內容填充它吧...
為在D3D中進行2D繪制進行設置
注意:從這兒開始我們就要談論一些和D3D相關的令人討厭的數學知識了。不要被嚇倒了---如果你愿意,你可以選擇乎略大多數細節。大多數Direct3D繪制都受三個矩陣控制:投影矩陣,世界矩陣,觀察矩陣。我們將談論的第一個矩陣是投影矩陣。你可以認為投影矩陣定義了你的攝像機的鏡頭屬性。在3D應用里,它定義了象透視方法,等等的東西。但是我們用不著透視---我們正在談論2D!!所以我們只談論正交投影。簡短得說,就是讓我們進行2D繪制而不用考慮那些附加在3D繪制中的屬性。為了創建一個正交投影矩陣,我們需要調用D3DXMatrixOrthoLH函數,它將為我們創建一個矩陣。其他的矩陣(觀察矩陣和世界矩陣)定義了攝像機的位置和世界(或一個在世界里的對象)的位置。為了我們的2D繪制,我們不需要移動攝像機,也不用想移動世界,所以我們將使用一個單位矩陣,將攝像機和世界放置在缺省的位置。我們可以用D3DXMatrixIdentity函數來創建單位矩陣。我們需要加入下面的頭文件,以使用D3DX函數。
PostInitialize函數現在是這樣子的:
void PostInitialize(float WindowWidth, float WindowHeight)
{
D3DXMATRIX Ortho2D;
D3DXMATRIX Identity;
D3DXMatrixOrthoLH(&Ortho2D, WindowWidth, WindowHeight, 0.0f, 1.0f);
D3DXMatrixIdentity(&Identity);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &Ortho2D);
g_pd3dDevice->SetTransform(D3DTS_WORLD, &Identity);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &Identity);
}
我們現在正在為2D繪制進行設置,我們需要繪制些東西。這樣設置了之后,我們的繪制區域就是從-WindowWidth/2 到 WindowWidth/2和從 -WindowHeight/2 到 WindowHeight/2。要注意的一件事是,在代碼里,寬度和高度都是用象素為單位指定的。這允許我們用象素來考慮所有的事情,但我們也能設置寬度和高度為1.0,然后允許我們用屏幕空間的百分比來指定大小,這樣就很容易的支持了多分辨率的情況。改變矩陣就能夠支持各種各樣的巧妙的事情,但為了簡單起見,我們現在將談論象素。
設置一個2D“面板”
當我進行2D繪制時,我有一個叫CDX8Panel的類,它裝封了所有我繪制2D矩形所需要的東西。簡單起見,它消除了C++說明,我已經將代碼拿了出來了。無論如何,當我們建造我們的一個繪制面板的代碼時,你將可能看到一個類的價值,或者如果你不使用C++時一個更高層的API的價值。同樣,依靠ID3DXSprite接口,我們也可得以更加悠閑。我將在這兒解釋最基本的東西,以展顯事情工作的方法,但是如果Sprite接口適合你的需要,你也可以使用它。
我的面板定義是一個簡單的2D紋理矩形,我們將會把它繪制到屏幕上。繪制一個面板非常類似于2D的blit操作。有經驗的2D程序員可能會想一個blit操作會有大量的工作,但是這些工作完成了很多允許的特效。首先,我們不得不考慮我們的矩形的幾何結構。這樣就包括了關于頂點的思想。如果你有3D硬件,硬件將非常快地處理這些頂點。如果你只有2D硬件,我們所談論的如此少的頂點也將很快的被CPU處理完成。首先,讓我們定義我們的頂點格式。將以下的代碼放置到靠近#i nclude:的地方
struct PANELVERTEX
{
FLOAT x, y, z;
DWORD color;
FLOAT u, v;
};
#define D3DFVF_PANELVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)
這個結構和靈活的頂點格式(FVF)定義了我們所談論的包含位置,顏色和一組紋理坐標的頂點。
現在我們需要一個頂點緩沖。加入下面代碼行到全局列表中。又是為了簡單,我讓它成為全局的---這可不是一個好的編碼習慣的示例。
LPDIRECT3DVERTEXBUFFER8 g_pVertices = NULL;
現在,加入下面的代碼行到PostInitialize函數(下面再說明):
float PanelWidth = 50.0f;
float PanelHeight = 100.0f;
g_pd3dDevice->CreateVertexBuffer(4 * sizeof(PANELVERTEX), D3DUSAGE_WRITEONLY, D3DFVF_PANELVERTEX, D3DPOOL_MANAGED, &g_pVertices);
PANELVERTEX* pVertices = NULL;
g_pVertices->Lock(0, 4 * sizeof(PANELVERTEX), (BYTE**)&pVertices, 0);
//Set all the colors to white
pVertices[0].color = pVertices[1].color = pVertices[2].color = pVertices[3].color = 0xffffffff;
//Set positions and texture coordinates
pVertices[0].x = pVertices[3].x = -PanelWidth / 2.0f;
pVertices[1].x = pVertices[2].x = PanelWidth / 2.0f;
pVertices[0].y = pVertices[1].y = PanelHeight / 2.0f;
pVertices[2].y = pVertices[3].y = -PanelHeight / 2.0f;
pVertices[0].z = pVertices[1].z = pVertices[2].z = pVertices[3].z = 1.0f;
pVertices[1].u = pVertices[2].u = 1.0f;
pVertices[0].u = pVertices[3].u = 0.0f;
pVertices[0].v = pVertices[1].v = 0.0f;
pVertices[2].v = pVertices[3].v = 1.0f;
g_pVertices->Unlock();
這實際上比看起來還要簡單。首先,我構造了面板的大小,我們有一些工作會用到它們。接下來,我請求設備創建一個包含用我的格式定義的四個頂點的足夠大內存的頂點緩沖。然后我鎖定緩沖以使我能夠設置頂點的值。值得注意的一點是,鎖定緩沖是一很昂貴的,所以,我將只這樣做一次。我們可以操作這些頂點而不用鎖定,不過我們將在以后討論。在這個例子中,我設置了四個對(0,0)居中的點。記住這一點,以后會有衍生而出的討論。另外,我設置了紋理坐標。SDK已經很好的說明了,所以我沒有進行討論。簡短的說就是我們進行了設置,來繪制整個紋理。這樣,現在我們已經設置好了矩形。下一步就是繪出它?
繪制面板
繪制矩形是很簡單的。增加下面的代碼行到你的Render2D函數:
g_pd3dDevice->SetVertexShader(D3DFVF_PANELVERTEX);
g_pd3dDevice->SetStreamSource(0, g_pVertices, sizeof(PANELVERTEX));
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2);
這些行告訴設備頂點如何被格式化,使用哪些頂點,以及如何使用它們。我選擇將這些當作三角形扇進行繪制,因為這樣比繪制兩個三角形更緊湊。注意,因為我們沒有和別的頂點格式或頂點緩沖的處理,我們可以移動第一行到我們的PostInitialize函數里去。我將它們放到這兒是為了強調你不得不告訴設備它將要處理的是什么。如果你不這樣做,它將假設頂點是不同格式的,并且導致崩潰。這時,你就可以編譯運行代碼了,如果一切正常,你會看到在藍色的背景里有一個黑色的矩形。這還不是很正確的,因為我們設置頂點的顏色是白色的。這個問題是設備允許了光照,這是我們不需要的。通過在PostInitialize函數里加入這行來關掉光照:
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
現在,重新編譯,設備將會使用頂點的顏色了。如果你喜歡,你可以改變頂點的顏色看看效果。到目前為止,一切順利,但是一個顯示一個白色矩形的游戲看上去是很令人厭煩的,我們還沒有觸及到blit一個位圖。所以,我們不得不加入紋理。
為面板粘貼紋理
紋理是一個能夠從文件裝入或者通過數據生成的基本的位圖。為簡單起見,我們只使用文件。將下面的變量加入到你的全局變量中:
LPDIRECT3DTXTURE8 g_pTexture = NULL;
這便是我們將要使用的紋理對象。加入這行代碼到PostInitialize函數以從文件裝入紋理。
D3DXCreateTextureFromFileEx(g_pd3dDevice, [Some Image File], 0, 0, 0, 0,
D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
D3DX_DEFAULT , 0, NULL, NULL, &g_pTexture);
你可以用你選擇的文件名替換[Some Image File]。D3DX函數可以裝入很多標準格式的文件。我們所使用的象素格式是有alpha通道的,所以我們裝入帶alpha通道的格式文件,象.dss文件。另外,我也乎略了ColorKey參數,但是你也可以指定一個ColorKey以進行透明。我會回頭來討論一點關于透明的知識。現在,我們有了一個紋理并且已經裝入了一個圖片。然后我們要告訴設備使用它。加入下面的代碼行到Render2D函數的開頭:
g_pd3dDevice->SetTexture(0, g_pTexture);
這告訴了設備用紋理來渲染三角形。這兒要特別記住的事情是考慮到簡單我沒有加入錯誤檢查。你應該進行正確的錯誤檢查,以確定在紋理被使用之前已經被實際的裝入了。一個可能的錯誤是,在很多硬件中,紋理的大小必須是2的冪,如:64X64,128X512,等等。對于最新的nVidia硬件,這個約束不再是正確的了,但是安全起見,請使用2的冪。這個限制讓很多人感到煩心,所以一會兒我會告訴你如何繞過這個限制。現在,編譯運行,你可以看到你的圖片已經映射到了三角形上了。
紋理坐標
注意紋理會被拉伸和縮短以適合矩形。你可以通過調整紋理的坐標來調整這些。例如,如果你把u=1.0那行改為u=0.5,那么只有一半的紋理被使用,而剩下的另一半不會被壓縮。所以,如果你有一個640X480的圖片,你想把它放到一個640X480的窗口中去,你應該將640X480大小的圖片放到一個1024X512大小的紋理中去然后指定紋理坐標為0.625,0.9375。你可以使用紋理中剩余的部分來放置那些會被映射到其他的面板中去的子圖片(通過相應的紋理坐標)。通常,你會想優化紋理被使用的方式,因為它們吃光了圖片內存并且在總線中移動。這看上去很象blit中的大量的工作,但是很多都會被新式的為3D進行優化的顯卡處理掉了。此外,多多考慮如何在系統中移動大塊的內存決不是一個壞的想法。但是我還是開始我的演說吧。
讓我們來看看我們走了多遠。首先,我們寫了很多代碼來blit一個簡單的位圖。但是,希望你能夠看到一些好處和機會。例如,紋理坐標自動縮放圖片以適應我們的幾何定義。這為我們做了很多工作,但是考慮到后面。如果我們設置使用一個基于百分比映射的垂直矩陣,并且,我們指定一個占據屏幕底部四分之一位置的面板(讓我們說它是UI吧),而且我們也用正確的紋理坐標來指定它的紋理,這樣,我們的UI在任何選定的窗口/屏幕大小下都會被自動的正確繪制出來。(Not exactly cold fusion),但這只是很多例子中的一個。現在我們已經讓紋理可以工作的很好了,我們回過頭來談論一下透明。
透明
象我以前所說的,加入透明的一個簡單的方法就是在調用D3DXCreateTextureFromFileEX函數里指定一個ColorKey值。另一個辦法是使用一個實際帶alpha通道的圖片。無論使用哪種方法為紋理指定透明(使用alpha通道,或者ColorKey),然后運行,你都會看不到有什么區別。這是因為alpha混合還沒有被允許。在PostInitialize中加入這些行以允許alpha混合:
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
第一行允許了混合。下兩行指定混合如何工作。這會有很多的可能性,不過這是最基本的類型。最后一行進行一些設置以致當改變頂點顏色的alpha成分時會縮放紋理值來減弱整個面板。關于可使用設置的更深層討論,請參見SDK。一旦這些行被加入進來,你會看到正確的透明。試著改變頂點的顏色來看看它將如何影響面板。
移動面板
現在我們的面板已經有了很多我們需要的視覺屬性,但它還只是粘在我們的視口中央。在游戲中,你可以想讓一些東西移動起來。一個顯而易見的方法是重新鎖定頂點,然后改變它們的位置。千萬不要這樣做!鎖定是很昂貴的操作,它包括數據的移動,并且這是不必要的。一個更好的方法是指定世界變換矩陣來移動這些點。對于很多人來說,矩陣看上去是有一點嚇人的,但是在D3DX中有一大群函數讓矩陣使用起來非常簡單。例如,為了移動面板,在Render2D函數的開頭加入下面的代碼:
D3DXMATRIX Position;
D3DXMatrixTranslation(&Position, 50.0f, 0.0f, 0.0f);
g_pd3dDevice->SetTransform(D3DTS_WORLD, &Position);
這里創建了一個可以在X方向移動面板50個象素的矩陣,然后告訴設備應用這個移動。這可以被裝封到一個象MoveTo(X,Y)的函數里去,不過我沒有實際給出這樣的代碼。前面,我說過要記住頂點是相對于原點來定義的。因為我們是這樣做的,所以,平移(移動)移動了面板的中心位置。如果你認為移動左上角或是其他的角會更加適合,請改變頂點定義的方式。你也可以通過傳遞正確的參數到MoveTo函數來創建不同的坐標系統。例如,我們的視口當前是從-100到100。如果我想將視口認為是從0到200那樣來使用MoveTo函數,我可以簡單的在我調用D3DXMatrixTranslation時從X坐標中減去100來進行更正。有很多方法可以很快的改變以使你能看到你所想要的效果,但是,作為實驗這將提供一個好的基礎。
其他的矩陣操作
有很多其他的矩陣操作可以影響面板。最有趣的可能是縮放和旋轉了。有一些D3DX函數可以很好的創建這些矩陣。我將把這些實驗留給你來做,不過這兒有一些提示。關于Z軸的旋轉將會在屏幕上旋轉。而關于X和Y軸的旋轉將看上去象是Y軸和X軸在收縮。另外,應用多個操作的方法是通過乘法,然后將結果矩陣送給設備:
D3DXMATRIX M = M1 * M2 * M3 * M4;
g_pd3dDevice->SetTransform(D3DTS_WORLD, &M);
不過,記住矩陣乘法的結果是依賴于操作數的順序的。例如,Rotation*Position將移動面板然后旋轉它。Position*Rotation將導致一個沿軌道而行的效果。如果你排列了幾個矩陣在一起,但是得到了并不期待的結果,請仔細的看看排列的順序。
當你變得更加輕松時,你可以想去試試象紋理矩陣這樣的東西,它將允許你移動紋理坐標。你也可以移動觀察矩陣來影響你的坐標系統。記得一點:鎖定是非常昂貴的,在你鎖定你的頂點緩沖之前,總是先看看象矩陣這樣的東西。
裝封
看看這兒列出的所有的代碼,為了進行blit我們走了很長的一段路,但是好事是很多都可以被裝封到一些小的函數或是類中去,這樣我們就可以一勞永逸了。請注意,這兒是使用一種非常梗概的而且沒有優化的方法來表示的。有很多方法可以將這些包裝起來以獲得最大的收益。這可能是在當前的和以后的硬件上創建2D應用程序的最佳方法,而且你也可以獲得在硬件上可以很簡單的實現那些效果的好處。這種方法也可以幫助你在3D中混合進2D元素,因為在矩陣面前,它們是一樣的。這些代碼也可以簡單的適合在OpenGL中進行2D工作,所以你甚至可以寫一個抽象裝封來支持兩種API。我的希望是這可以讓人們使用DX8來做2D工作。可能在以后的文章中我會討論更多的技巧和效果。
總結
以上是生活随笔為你收集整理的dx绘制2d图像_在DirectX 中进行2D渲染的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 洞悉物联网发展1000问之什么是智慧消防
- 下一篇: Microsoft ADO Data C