国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
關于臟矩形的那些事

關于臟矩形的那些事

(2011-12-30 20:06:15)
標簽:

雜談

分類: GameProgramming
很多人對臟矩形算法有所誤解,其主要原因在于:
(1) 認為臟矩形算法是2D像素游戲時代的古老產物,而現在多數游戲都跑在3D引擎上。
(2) 臟矩形算法一般都有底層框架或引擎支持,上層應用/游戲開發(fā)者不需要考慮臟矩形問題。
對于以上兩個問題,我的回答:
(1) 在使用3D渲染2D的情況下,臟矩形算法依然有效(比如:通過OpenGL/Direct3D紋理貼圖來實現2D圖像繪制,使用臟矩形技術可以大幅降低光柵化所需的時間。其實Android Surface就是這么做的,如:臟矩形鏈可以當成紋理扣出來等。)
(2) 雖然很多引擎本身支持臟矩形,但當你需要對引擎進行修改或擴展時,必須了解該引擎對臟矩形的支持程度,以及背后的運作機制。

聲明:我這里只是提供大致思路(甚至自己都沒有寫過測試代碼,主要是沒時間...),如果所述有誤,希望指正,謝謝。

在講述臟矩形概念之前,先來看一下游戲圖形渲染的基本流程:
while(1){
    [清屏 -> 使用畫家算法把當前幀的所有物體畫到屏幕的后臺緩沖區(qū)上]
     ->
    [翻轉后臺緩沖區(qū)和屏幕緩沖區(qū)]
}
對于2D游戲或UI系統(tǒng)來說,渲染流程主要就分為以上2個階段。(這里假設游戲是全屏獨占的,對于非全屏獨占情況類似,但底層實現有較大差別)
事實上,第一階段和第二階段均可以被臟矩形算法優(yōu)化,并且臟矩形算法必須要求double buffer支持。

1. 使用臟矩形算法優(yōu)化第二階段:

翻轉前后臺緩沖區(qū),有兩種方法。一種采用把內存數據直接Blt到顯存中,另一種則是采用頁面翻轉。
之所以不采用頁面翻轉的原因在于:不是所有適配器支持頁面翻轉,且頁面翻轉需要渲染代碼直接對VGA硬件顯存讀寫,而VGA硬件總線一般會比較慢。
如果我們把物體繪制到后臺緩沖(內存),然后再通過臟矩形Blt到顯存,則可以大幅降低對VGA內存的訪問。

算法流程(假設背景不變):

設置前臺緩沖區(qū)背景(因為背景不變)
DirtyList初始化為空
while(1)
{
    //邏輯代碼:
    ....

    //渲染代碼:
    后臺緩沖區(qū)清屏,并繪制后臺緩沖區(qū)背景
    繪制后臺緩沖區(qū)物體
    Add每個物體的矩形到DirtyList,同時對DirtyList進行切割合并
    把后臺緩沖區(qū)中對應的臟矩形部分Blt到前臺緩沖
    清空DirtyList,并按按當前物體重建DirtyList
}

以上算法的缺點是,每次必須清空整個后臺緩沖區(qū)然后重新繪制背景和所有物體。
但我們已假定背景不變,其實只要重畫所有物體即可(不需要重新畫背景。注:這里并不關心物體本身是否是臟的),于是得到進一步的優(yōu)化:
設置前臺緩沖區(qū)背景
設置后臺緩沖區(qū)背景
DirtyList初始化為空
while(1)
{
    //邏輯代碼:
    ....

    //渲染代碼:
    Add每個物體的矩形到DirtyList,同時對DirtyList進行切割合并
    把這個DirtyList中的所有矩形作為后臺緩存的剪裁區(qū)域
    后臺緩沖區(qū)清屏,并繪制后臺緩沖區(qū)背景(實際上只操作了剪裁區(qū)域)
    繪制后臺緩沖區(qū)物體(實際上只操作了剪裁區(qū)域)
    把后臺緩沖區(qū)中對應的臟矩形部分Blt到前臺緩沖
    清空DirtyList,并按按當前物體重建DirtyList
}

以上算法,在Android里的實現方式如下:
public void run() {
    int frameCount = 0;
    Rect dirtyRect = new Rect(0,0,0,0);

    while(1){
        //邏輯代碼:

        //渲染代碼:
        if(frameCount++==0){
            canvas = Holder.lockCanvas(); //設置全屏刷新,相當于設置臟矩形區(qū)域為整個屏幕
        }else{
            把所有物體的矩形加入dirtyRect,得到并集 //注:Android API并不支持臟矩形鏈,但其實是可以用臟矩形鏈實現不規(guī)則的dirtyRects的
            canvas = Holder.lockCanvas(dirtyRect); //設置部分刷新
        }

        ...//清空并繪制背景
        ...//繪制所有物體
        dirtyRect.set(0,0,0,0); //清空矩形
        把所有物體的矩形加入dirtyRect,重建dirtyRect
        Holder.unlockCanvasAndPost(canvas);
    }
}

注1:一般來說,游戲的背景不會一成不變,但很少每一幀都在變,在這種我們只要在背景變化前重設臟矩形區(qū)域為整個屏幕區(qū)域即可。
注2:把所有物體的矩形加入dirtyRect,重建dirtyRect 這一步也可以放到下一幀的邏輯代碼前面。(因為只有在邏輯代碼里修改物體狀態(tài)才會導致臟矩形改變)
注3:Android里Holder.lockCanvas(dirtyRect)的意思是說,后面的繪圖操作使用dirtyRect作為clip區(qū)域,并且在最后swap前后臺緩沖時只刷新dirtyRect區(qū)域,并且刷新完了之后下一幀lock到的Canvas上的buffer數據除了dirtyRect以外都是上一幀畫完時的數據。(dirtyRect因為本來就要重畫,所以這塊區(qū)域Android不會保存)


2. 使用臟矩形算法優(yōu)化第一階段:
在(1.)中的算法必須要重畫所有物體,并且每幀的臟區(qū)的總和涉及了至少所有的物體(節(jié)省了第二階段的時間以及第一階段中繪制背景的時間)。而事實上游戲中的物體大多都是不變的,比如:TileGame中的樹木、房子等,
在這種情況下,我們希望這些物體只繪制到后臺緩存一次,以后除非物體狀態(tài)被改變(或者與該物體重疊的區(qū)域上的物體狀態(tài)被改變),否則就不再把這些物體再次繪制到后臺緩存上。
由于一個物體是否要繪制到后臺緩存取決于邏輯代碼(物體狀態(tài)),比如:游戲的上層邏輯 (游戲開發(fā)者)調用引擎的API接口去控制物體的位置、大小、動畫幀索引,那么游戲的下層邏輯(引擎)就需要在這些API的實現里標記這些物體是臟的(MarkDirty)。
MarkDirty()函數:
    i.   標記該物體的是臟的(mDirty=true),
    ii.  把該物體的矩形加入DirtyList
    iii. 檢查是否有重疊的物體并標記這些重疊物體是臟的(但不需要care這些重疊區(qū)域,也不要擴大臟矩形區(qū)域)
    iv.  對DirtyList進行切割合并。
注意:物體狀態(tài)的改變主要分兩種:一種只是Sprite本身圖片改變,如:按鈕被按下(image),另一種是位置、大小或可見性等屬性改變(resize),這兩種最好分開處理。

public void run() {
    初始化1個背景單位到ObjectList,并call MarkDirty()  //背景可以作為一個物體處理
    初始化9個單位到ObjectList,并call MarkDirty()    //ObjectList是Z-Order排序的
    Rect dirtyRect = new Rect(0,0,0,0);

    while(1)
    {
        //邏輯代碼(內部可能處理InputEvent):
        for(all Object in ObjectList){
            thisObject.Update();
                //Update函數內部:
                //  if(某個物體只是image需要改變){
                //      thisObject.MarkDirty();
                //  }else if(某個物體resize了){
                //      thisObject.MarkDirty();
                //      thisObject.scale = xxx;
                //      thisObject.position = xxx;
                //      thisObject.image = xxx;
                //      thisObject.MarkDirty();
                //  }else{
                //      物體狀態(tài)沒有改變,什么都不做
                //  }
               
        }


        //渲染代碼:
        dirtyRect.set(0,0,0,0) 同時合并把DirtyList -> dirtyRect //注:Android API并不支持臟矩形鏈,但其實是可以用臟矩形鏈實現不規(guī)則的dirtyRects的
        canvas = Holder.lockCanvas(dirtyRect); //設置部分刷新
        for(all Object in ObjectList && the Object is dirty){
            繪制物體到后臺緩沖區(qū)
        }
        Holder.unlockCanvasAndPost(canvas);
        清空DirtyList,所有物體的mDirty標記設為false
    }
}

注1:這種做法使得DirtyList同時依賴于渲染層和邏輯層,因此引擎需要對其進行封裝,使其對開發(fā)者不可見。
注2:對thisObject.MarkDirty函數可作進一步優(yōu)化(具體指"iii.檢查是否有重疊的物體并標記這些物體是臟的"這一步):
    if(thisObject.mHasAlpha==true){
        ...//按照原來的做法,檢查是否有重疊的物體并標記這些物體是臟的()
    }else{
        只有在ObjectList中thisObject之后的與thisObject矩形相交的object才需要設置dirty標記,在ObjectList中thisObject之前的那些object不需要設置dirty(后面的object說明后渲染,疊在上面)
    }


但這樣的優(yōu)化在resize時會出錯!因為resize時,原物體位置可能變化,空出的部分必須被下面的物體重新畫,改進的方法,是設置一個MarkDirtyFull的函數:
MarkDirtyFull和MarkDirty的區(qū)別在于:
MarkDirty會判斷object.mHasAlpha是否等于true,如果等于true就調用MarkDirtyFull(檢查是否有重疊的物體并標記這些物體是臟的不管是否有alpha)
如果object.mHasAlpha等于false,按照上面的做法,這樣可以避免在resize時出錯。

public void run() {
    初始化1個背景單位到ObjectList,并call MarkDirty()  //背景可以作為一個單位處理
    初始化9個單位到ObjectList,并call MarkDirty()    //ObjectList是Z-Order排序的
    Rect dirtyRect = new Rect(0,0,0,0);

    while(1)
    {
        //邏輯代碼(內部可能處理InputEvent):
        for(all Object in ObjectList){
            thisObject.Update();
                //Update函數內部:
                //  if(某個物體只是image需要改變){
                //      thisObject.MarkDirty();
                //  }else if(某個物體resize了){
                //      thisObject.MarkDirtyFull(); //Resize時必須MarkDirtyFull
                //      thisObject.scale = xxx;
                //      thisObject.position = xxx;
                //      thisObject.image = xxx;
                //      thisObject.MarkDirty();
                //  }else{
                //      物體狀態(tài)沒有改變,什么都不做
                //  }
        }


        //渲染代碼:
        dirtyRect.set(0,0,0,0) 同時合并把DirtyList -> dirtyRect //注:Android API并不支持臟矩形鏈,但其實是可以用臟矩形鏈實現不規(guī)則的dirtyRects的
        canvas = Holder.lockCanvas(dirtyRect); //設置部分刷新
        for(all Object in ObjectList && the Object is dirty){
            繪制物體到后臺緩沖區(qū)
        }
        Holder.unlockCanvasAndPost(canvas);
        清空DirtyList,所有物體的mDirty標記設為false
    }
}

注3:對thisObject.MarkDirtyFull函數可作進一步優(yōu)化:
    遍歷在ObjectList中thisObject之前的object,
        如果之前的object中有一個mHasAlpha=false,并且完全包含thisObject,那么只需要標記dirty到這個object為止,不再標記更底層的object
        否則就標記ObjectList中thisObject之前的所有object
    遍歷在ObjectList中thisObject之后的object,
        全部標記dirty,只要有交集


3. Android中的臟矩形
Android Surface自帶臟矩形刷新的API,以SurfaceView為例就是:
SurfaceHolder.java
      Start editing the pixels in the surface.  The returned Canvas can be used
      to draw into the surface's bitmap.  A null is returned if the surface has
      not been created or otherwise can not be edited.  You will usually need
      to implement {@link Callback#surfaceCreated Callback.surfaceCreated}
      to find out when the Surface is available for use.
    
      <p>The content of the Surface is never preserved between unlockCanvas() and
      lockCanvas(), for this reason, every pixel within the Surface area
      must be written. The only exception to this rule is when a dirty
      rectangle is specified, in which case, non dirty pixels will be
      preserved.
    
      <p>If you call this repeatedly when the Surface is not ready (before
      {@link Callback#surfaceCreated Callback.surfaceCreated} or after
      {@link Callback#surfaceDestroyed Callback.surfaceDestroyed}), your calls
      will be throttled to a slow rate in order to avoid consuming CPU.
    
      <p>If null is not returned, this function internally holds a lock until
      the corresponding {@link #unlockCanvasAndPost} call, preventing
      {@link SurfaceView} from creating, destroying, or modifying the surface
      while it is being drawn.  This can be more convenience than accessing
      the Surface directly, as you do not need to do special synchronization
      with a drawing thread in {@link Callback#surfaceDestroyed
      Callback.surfaceDestroyed}.
    
      @return Canvas Use to draw into the surface.


    public Canvas lockCanvas();


   
      Just like {@link #lockCanvas()} but allows to specify a dirty rectangle.
      Every
      pixel within that rectangle must be written; however pixels outside
      the dirty rectangle will be preserved by the next call to lockCanvas().
    
      @see android.view.SurfaceHolder#lockCanvas
    
      @param dirty Area of the Surface that will be modified.
      @return Canvas Use to draw into the surface.


    public Canvas lockCanvas(Rect dirty);


由此可見,Android中的臟矩形接口只能優(yōu)化渲染流程的第二步,并且不支持臟矩形鏈(至少Java接口是不支持臟矩形鏈的。但Native層可以,比如:使用OpenGL模板緩存區(qū)或打開裁剪測試來實現)。如果需要優(yōu)化渲染流程的第一步,則需要在Android Surface View之上設計一個引擎。

SurfaceHolder.lockCanvas(); //得到一塊畫布作為back buffer,該back buffer不一定是前一幀的back buffer
SurfaceHolder.lockCanvas(new Rect(0,0,0,0)); //得到一塊畫布作為back buffer,就是前一幀back buffer
SurfaceHolder.lockCanvas(new Rect(100,100,100,100)); //得到一塊畫布作為back buffer,該back buffer中Rect(100,100,100,100)區(qū)域外的內容是前一幀back buffer

本站僅提供存儲服務,所有內容均由用戶發(fā)布,如發(fā)現有害或侵權內容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
無忌版的《攝影構圖學》(第二章)作者:原上草
ai快捷鍵大全
Android 如何清空 Canvas 清屏只需三句話
OpenGL中3DMAX模型的應用 (轉)
人體美學中的黃金分割
像素畫教程全集!!{PS教程7}
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服