DirectX學習手記(二) (轉)

worldblog發表於2007-12-14
DirectX學習手記(二) (轉)[@more@]

學習手記(二) happyfire 2002/8/11 此回說說怎樣用DirectDraw向表面上貼圖,包括建立離屏表面,設定調色盤,載入點陣圖到表面,透明色,頁面丟失等。 二. 用DirectDraw貼圖 先讓我們回憶一下上一回的內容。為了初始化DirectDraw我們首先建立了一個DirectDraw,然後設定了協作(全屏+獨佔),設定顯示模式,然後建立主表面,提取後臺緩衝表面指標。至此可以在後臺表面上進行操作,然後flip到前臺顯示出來。最後結束前釋放所有的directdraw物件。好了,現在說說怎樣向後臺表面貼圖,即讓螢幕顯示圖片。 第一步:建立離屏表面 離屏表面是你永遠看不到的表面(所謂離屏),它通常被用來存放點陣圖。通常的做法是把離屏表面上的點陣圖用Blt的方法貼到後臺表面,後臺表面再flip為前臺表面。看看它的建立方法: //宣告用來存放影像的離屏表面(宣告為全域性變數) LPDIRECTDRAWSURFACE lpDDSPic ; //在InitDDraw()中建立儲存影像的離屏表面 ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH ; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN ; ddsd.dwHeight = 480 ; ddsd.dwWidth = 640 ; if ( lpDD->CreateSurface(&ddsd,&lpDDSPic,NULL)!=DD_OK ) return FALSE ; 首先要宣告一個表面,然後填充DDSURFACEDESC表面描述結構體。dwFlags填DDSD_CAPS表示ddsCaps域有效, DDSD_HEIGHT和DDSD_WIDTH表示要指定頁面的大小。ddsCaps.dwCaps填DDSCAPS_OFFSCREENPLAIN表示建立的是離屏表面。dwHeight和dwWidth填頁面的大小,單位是畫素。然後就可以用DirectDraw物件的CreateSurface 方法建立lpDDSPic了。注意CreateSurface的第三個引數必須填NULL,該引數將允許與今後的 集合特性相相容。整個建立過程和建立主表面非常相似,只是ddsd的填充不同。這裡稍微談一下離屏表面的大小問題。在 DirectX 5.0 以前的版本中,離屏表面的寬度的最大值不能超出於主表面的寬度.而從 DirectX 5.0 版開始,你可以建立任何寬度的離屏頁面,只要你的視訊記憶體和足夠大.預設情況下,離屏表面是被建立在視訊記憶體中的,但如果視訊記憶體不夠用,它就會被置於記憶體中,當然你可以指明只在視訊記憶體中建立表面(可在 DDSCAPS 結構的 dwCaps 成員中包含進 DDSCAPS_SYSTEMMEMORY 或 DDSCAPS_VOMEMORY 標誌符,以明確的表明你希望將表面置於何處),如果這樣的話,當表面太大而超出視訊記憶體的容納範圍時,就會建立失敗。另外,以上面的方法建立的離屏表面的畫素格式(可以簡單理解為表示幾位的顏色)是和主表面相同的,當然也可以建立一個畫素格式與主表面不同的離屏表面.然而,在這種情況下,該離屏表面將被限制於記憶體中。具體的做法有點煩,各位感興趣的可以查一下手冊,:) 。 第二步:讀入調色盤並設定上我們的主表面被設定為基於調色盤(8位色深度)。一個基於調色盤的表面是一些數字的集合, 其中的每一個數字代表一個畫素.每一個數字的值都對應於一個色彩表(color table)中的項,這個表告訴 DirectDraw 對這個畫素使用什麼樣的顏色. 這個表就是調色盤(Palette)。使用調色盤是為了儘量降低對視訊記憶體的需求,它用一個顏色(Color Index)來代表各個畫素點的顏色,而不是直接用紅,綠,藍三基色的亮度值來確定每個畫素點的顏色。調色盤包含了若干顏色索引和該索引所對應的真實顏色值。調色盤的顏色索引主要採用4或8兩種位深度。要在表面上正常顯示影像就必須將表面的調色盤設定為該影像的調色盤。如下面的程式碼片斷: //先宣告調色盤物件(其實是指標型別,就這麼說吧,上面提到的各個物件都是這樣的) LPDIRECTDRAWPALETTE lpDDPal ; //從點陣圖設定調色盤 lpDDPal = DDLoadPalette(lpDD, szBitmap); if (lpDDPal) lpDDSPrimary->SetPalette(lpDDPal); else return FALSE ; DDLoadPalette是DDutil.cpp裡面的,happyfire也不大懂,作為初學者拿來用就是了。 第三步:將點陣圖讀入已建立好的離屏表面中 這一步我們用DirectDraw s裡面的一個函式DDLoaitmap來實現,具體用法是: lpDDSPic = DDLoadBitmap ( lpDD, szBitmap, 0, 0 ) ; //szBitmap是位件名,例如".picbackground.bmp" 使用了DDLoadBitmap這一步變得非常簡單,但是要把Ddutil.cpp和Ddutil.h加到工程中才行。你可以看看 Ddutil.cpp中該函式的實現,它了的API LoadImage,並且用作為引數傳進來的directdraw物件建立了一個表面並返回了它的指標。 第四步:使用blit的方法,將離屏表面上的圖片傳送到後臺緩衝 何謂blit?Bit block traner(位塊傳送),即將記憶體中的資料塊從一處傳送到另一處。DirectDraw 提供了兩個方法,Blt和BltFast。Blt功能強大,BltFast速度較快。這裡我們先不討論Blt,只用BltFast。看如何實現: RECT rcRect ; rcRect.left= 0; rcRect.top= 0 ; rcRect.right= 640; rcRect.bottom=480; lpddsBack->BltFast( 0, 0, lpddsPic, &rcRect, DDBLTFAST_SRCCOLORKEY|DDBLTFAST_WAIT); RECT是windows的矩形結構(所以要包含windows.h),rcRect這個矩形用來確定源表面(這裡是離屏表面)上的哪一塊區域要被傳送到目標表面(這裡是後臺緩衝表面)上去。BltFast的前兩個引數是兩個D值,表示目標表面上的一個點的座標x和y,它決定了傳送過去的圖形在目標表面上所處的位置。你可以改變x,y和rcRect看看效果。最後一個引數是傳送型別: DDBLTFAST_DESTCOLORKEY 指定進行一次帶透明的位塊傳送,使用目標表面的關鍵色(color key)。 DDBLTFAST_NOCOLORKEY 指定進行一次普通的複製,不帶透明成分。 DDBLTFAST_SRCCOLORKEY 指定進行一次帶透明的位塊傳送,使用源表面的關鍵色。 DDBLTFAST_WAIT 如果位塊傳送器正忙,延遲 DDERR_WASSTILLDRAWING 訊息的傳送,直到位塊傳送器準備好或發生其它錯誤時才返回。 看看這個color key,這可是很有用的!所謂關鍵色,即我們說的透明色,如果在源表面上指定了一個顏色為關鍵色,那麼在blit操作中,將視具有這種顏色的區域為透明,不會被傳送到目標頁面上。這樣的話,雖然傳送過去的是一個矩形,但矩形上是關鍵色的部分是不會傳送的,從而看上去是有輪廓的圖形,比如一個精靈。 當然必須先為一個表面設定關鍵色才行。在InitDDraw中我們用DDSetColorKey ( lpDDSPic, RGB(255,0,255) ) ;為建立好的離屏表面lpDDSPic設定了紫紅色的關鍵色。你可以在圖片中畫一個精靈,然後把所有非精靈的部分用紫紅色填充。然後將BltFast的最後一個引數設為DDBLTFAST_SRCCOLORKEY.這樣傳送過去的就是一個精靈的樣子了。 用DDBLTFAST_DESTCOLORKEY可以為目標表面設定關鍵色,有所不同的是,目標表面上顏色只有為關鍵色才能被覆蓋,即染色。 最後在說說目標表面和原表面的區別。其實它們都是相對的。呼叫BltFast的為目標表面,作為引數的為源表面。一般的用法,後臺表面呼叫BltFast,引數為離屏表面。 貼圖的問題就說這麼多吧,其實這裡面的內容還是挺多的。還有非調色盤模式的16位,24位,32位RGB格式,16位RGB對應於不同的還有555,565兩種模式。但我們是初學者嘛,這些東西...:) 有待研究。(如果你真的立志於作遊戲,呵呵,準備學吧,以後還要和MMX的指令什麼的打交道啊) 最後要說的是第四步的操作要放在MainL中,這樣讓它不停的Blt再Flip,如果你在程式中設計按了某個鍵貼不同的圖並改變貼圖的位置,就可以做出精靈動畫了,基本的原理就是這樣的。 三.淺談表面丟失的處理 如果你的程式從全屏模式下用Alt+Tab切出去,再切回來,你可能發現圖片不見了。因為當代表頁面記憶體的 DirectDrawSurface物件被不得已的釋放時,與該物件相關聯的頁面記憶體也會被釋放。當一個DirectDrawSurface物件丟失其頁面記憶體的時候,它的許多函式將返回DDERR_SURFACELOST,並且不進行任何其它操作。先看一下這個片斷: while (1){ hRst = lpDDSPrimary->Flip(NULL,0) ; if ( hRst==DD_OK ) break ; if ( hRst==DDERR_SURFACELOST ){ if ( RestoreAllDDS()!=DD_OK ) break ; } if (hRst != DDERR_WASSTILLDRAWING){ break; } } 上回flip中沒提到flip有失敗的可能,上面的片斷是解決的方法。如果flip返回DDERR_WASSTILLDRAWING,那麼是由於上一次flip 操作尚未完成(flip指令已發出,但directX還沒有完成操作),我們可以做的只能是讓while迴圈下去,直到 hRst != DDERR_WASSTILLDRAWING。如果flip返回DD_OK,那表示flip操作完成,可以break出去了。如果flip返回 DDERR_SURFACELOST,表示發生了表面失效,這時我們需要自己處理它。看看我的RestoreAlDDS()函式是怎麼工作的: HRESULT RestoreAllDDS( void ) { HRESULT hRst ; hRst = lpDDSPrimary->Restore(); if (hRst) hRst = lpDDSBack->Restore () ; if (hRst) hRst = lpDDSPic->Restore() ; DDReLoadBitmap ( lpDDSPic,szBitmap ) ; return hRst ; } 在這個函式里面,我們先後呼叫了主表面,後臺表面,離屏表面的Restore方法。Restore方法可以為這些丟失了記憶體的頁面重新分配記憶體,並且將這些記憶體與DirectDrawSurface物件聯絡上。但重建記憶體並不會使以前存在於該頁面上的圖象重新顯現出來,因此在呼叫Restore函式重建之後,必須親手重新繪製所有的圖象。在這裡我用了DDReLoadBitmap函式,這個函式也是 Ddutil.cpp裡面的。需要說明的是,如果用DDLoadBitmap代替DDReLoadBitmap是不行的!你可以看看這兩個函式的實現,比較一下。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993089/,如需轉載,請註明出處,否則將追究法律責任。

相關文章