我們已經(jīng)得到了所有的對象移入?yún)^(qū)域和移出區(qū)域,可以檢查它們是否為空;如果為空,就不需要進行繪制。
假定有幾個對象需要繪制,將Graphics.ClipRegion屬性設置為將要繪制的區(qū)域。任何企圖在該區(qū)域之外進行的繪制將都是無效的:繪制操作將被裁剪到特定的區(qū)域中。該功能很重要,因為我們可能需要對更新區(qū)域中的對象進行重繪,而這些對象又同該區(qū)域外的對象有所重疊。ClipRegion屬性允許我們實現(xiàn)該操作,而不需要對所有重疊了的對象進行重繪,直到繪制到最前端的一個對象為止,如圖4-5所示。在該圖中,有一堆方框和一個球重疊在一起。當屏幕更新時球被移走了,ClipRegion屬性使我們不用再渲染全部方框。
圖4-5(a)展示的是原始場景,其中包含了兩個方框,在它們的后面有一個球。(b)圖展示了球移走之后的場景。移動區(qū)域被裁剪掉了,因為這部分還沒有被繪制。這使得下方框少了一部分區(qū)域。(c)圖展示了對下方框進行繪制后的結(jié)果(未使用ClipRegion屬性)。其中方框被重繪了,但現(xiàn)在卻出現(xiàn)在上方框的前面,這并不是我們所期望的結(jié)果。為了進行修正,我們對上方框也進行了重繪,這樣所有的對象都位于它的后方了。這可能會導致一長串連鎖的額外的對象繪制操作。最后,在(d)圖中,我們對(c)圖重復進行了繪制,但這次使用了ClipRegion屬性(虛線矩形所示的區(qū)域)。這樣,下方框中只有與使用了ClipRegion屬性的區(qū)域發(fā)生重疊的部分進行了重繪。這防止了下方框跳到上方框的前面,因此不需要再做進一步的繪制。
確定了移動區(qū)域,并且設置了ClipRegion屬性集,我們渲染優(yōu)化過程的第二步(清空更新區(qū)域)就很簡單了。如果使用一個背景圖像,我們只需要將它復制到移動區(qū)域即可;如果不是,那么只需要調(diào)用Graphics.Clear方法(只有與ClipRegion屬性重疊的區(qū)域會受到影響)即可。
現(xiàn)在需要繪制游戲?qū)ο螅覀兿日业侥切┡c移動區(qū)域發(fā)生重疊的對象,并只對它們進行繪制,這樣就可以減少工作量。完全落在該區(qū)域之外的對象自從在前面繪制完成后就不需要發(fā)生任何改變,因此可以不考慮它們。這同時也節(jié)約了大量的GDI處理過程。
在本階段中,后臺緩沖區(qū)要被完全更新,并且最后要準備好顯示給用戶。然而我們也可以對這個過程進行優(yōu)化。如您所知,我們需要繪制的是移動區(qū)域,因此在Render方法的最后,我們可以只將該區(qū)域傳遞給窗體的Invalidate函數(shù)。Invalidate函數(shù)將觸發(fā)Present函數(shù),在其中我們可以從后臺緩沖區(qū)中只將該區(qū)域復制到游戲窗體。同樣,這些改進可以顯著地減少GDI的工作量,使幀率得到提高。
我們還可以將矩形區(qū)域累計到一個名為_presentRect的類變量中。Present函數(shù)用它來識別將后臺緩沖區(qū)中的哪個區(qū)域復制到窗體上。
為了更形象地顯示哪些區(qū)域被重繪了,我們在CGameEngineGDIBase.Render函數(shù)中添加一些調(diào)試代碼,在每一幀中的移動區(qū)域上渲染一個矩形邊框。默認情況下該功能被注釋掉了,但如果您想看到屏幕上發(fā)生更新的確切區(qū)域,就可以將該功能打開。要啟用該功能,請找到位于Render函數(shù)結(jié)尾處調(diào)用了DrawRectangle函數(shù)的代碼,將代碼旁邊的注釋符去掉,如程序清單4-18所示。