1. 程式人生 > >深度排序與alpha混合

深度排序與alpha混合

顏色 其它 mas 註釋 像素點 使用 -a 交叉 通道

原文:

https://blogs.msdn.microsoft.com/shawnhar/2009/02/18/depth-sorting-alpha-blended-objects/

翻譯:李現民

最後修改:2012-07-03

“為什麽我的透明物體的繪制順序是錯誤的,或者為什麽它們的一部分不見了?”

當繪制一個3D場景的時候,將圖形按深度排序非常重要,只有這樣靠近攝像機的物體才能被繪制在(離攝像機)更遠的物體的上面。我們不會希望遠方的山脈被繪制在近在眼前的建築物的上面!

當前得到廣泛應用的深度排序技術有三種:

  1. 深度緩沖(又叫 z-buffering)

  2. 畫家算法

  3. 背面剔除

不幸的是,每種技術都有它的局限性。為了獲得好的繪制結果,大多數遊戲需要依賴於以上三種技術的組合。

1 深度緩沖

深度緩沖是一種簡單而有效的辦法,並且當你只繪制不透明物體時,其繪制結果非常完美,但該方法無法處理透明的物體!

這是因為深度緩沖算法僅僅跟蹤記錄了到目前為止所繪制的最近像素點,對不透明物體而言這足夠了。舉例說來,如果我們需要繪制兩個三角形,A和B:

如果我們按照先B後A的順序繪制,則深度緩沖會發現來自於A的新像素點比之前繪制的來自於B的像素點要近,因此直接覆蓋繪制就可以了。如果我們按照相反的 順序繪制(先A後B),則深度緩沖會發現來自於B的像素點比已經繪制的來自於於A的像素點要遠,因此將會直接丟棄它們。無論哪種情況下我們都會得到正確的 結果:A在上面,而B在後面被隱藏。

但如果物體(幾何體)是透明的怎麽辦?也就是物體B部分可以見時(透過物體A的半透明三角面片)。如果我們按照先B後A的順序繪制,仍然會得到正確的結 果,但反之就會出錯了。在第二種情況下,深度緩沖會首先從B得到一個像素點,然後發現已經繪制了某個來自於A的更近的像素點,但卻不知道如何處理這種情 況。它僅有兩種選擇是:繪制B上的像素點(結果將是錯誤的,因為這會將更遠處的B混合到更近的A之上,但alpha blending的順序是不可交換的)或者直接將B整個丟棄。這很不好!

結論:深度緩沖對不透明物體是完美的,但對透明物體卻沒什麽用。

2 畫家算法

既然深度緩沖算法無法以錯誤的順序正確繪制透明物體,那麽一定存在一個簡單的修正辦法,對吧?只要我們總是保證以正確的順序繪制就可以了!我們首先將場景 中的所有物體排序,這樣我們就可以先繪制遠處的物體,然後在其上繪制更近一些的物體,這樣就可以保證前面示例中的B物體總是在A物體之前繪制。

不幸的是,這說起來容易做起來難。在很多情況下將對象排序是不夠的。例如,A和B相互交叉的情況該怎麽辦?

這種情況很可能發生:比如說A是一個玻璃杯而B是一個放在裏面的玻璃珠。現在根本無法以正確的方式對它們進行排序,因為A的一部分比B更近,但另一部分卻更遠。

我們甚至不需要使用兩個單獨的物體重現這個問題。組成玻璃杯的那些三角面片怎麽處理呢?為了使結果看起來是正確的,我們需要在繪制玻璃杯的正面之前先繪制其背面。因此僅僅將物體進行排序還不夠:我們真正需要的是排序每一個三角面片。

困難在於,將每個三角面片都排序的代價是極其昂貴的!而且即使我們可以承受這種代價,這也不足以保證在所有情況下都可以得到正確的繪制結果。比如兩個透明的三角形相互交叉的情況如何處理?

沒有辦法對這些三角形排序,因為我們需要將B的上半部分繪制在A的前面,同時將其下半部分繪制在A的後面。唯一的解決辦法是在檢測這種情況發生時將這些三角形在它們相交的地方拆分,但這種做法的代價過於高昂了。

結論:畫家算法需要你對排序的粒度作出權衡。如果僅僅對少量大型物體進行排序,則算法會非常快但精確度不高;反之,如果對大量小型物體進行排序(極限情況是對三角面片排序),則算法會很慢但會更加精確。

3 背面剔除

人們通常不認為背面剔除是一種排序技術,但事實上它是的確是一種重要的(排序)方法。它的局限性在於僅僅適用於凸面體。

考慮一個簡單的凸面體,比如一個球體或一個立方體。無論你從哪個角度觀察它,每一個屏幕像素都會被精確的覆蓋兩次:一次被物體的前面覆蓋,另一次是被它的 背面覆蓋。如果使用背面剔除丟棄物體背面的三角面片,那麽就只剩下前面的了。哈哈!如果每一個屏幕像素只被覆蓋一次,那你自動就會獲得完美的alpha blending結果,而不需要任何排序。

當然,大多數遊戲不會僅僅繪制球體或立方體:),所以背面剔除本身並不是一個完整的解決方案。

結論:背面剔除對凸面體是完美的,但對於其它的就無能為力了。

4 我該如何讓遊戲看起來更好一些?

最常用的方法是:

  1. 設置 DepthBufferEnable 與 DepthBufferWriteEnable 為 true

  2. 繪制所有的不透明物體(幾何體)

  3. 保持 DepthBufferEnable=true,但修改 DepthBufferWriteEnable=false

  4. 將物體按它與攝像機之間的距離進行排序,然後以從後向前的順序繪制

這種方法依賴於前述三種排序技術的組合:

  • 不透明物體使用深度緩沖排序

  • 透明物體與不透明物體仍然會被深度緩沖處理(所以你永遠不會透過一個不透明物體看到一個透明物體)

  • 畫家算法按物體的相對關系對透明物體排序(如果兩個透明物體相交的話會引起排序錯誤)

  • 依賴背面剔除對單個透明物體上的所有三角面片進行排序(如果透明物體不是凸面體則會引起排序錯誤)

結果並非完美,但卻非常有效並易於實現,而且對大多數遊戲而言已經足夠好了。

有很多方法可以用於改進排序的精確度:

避免alpha blending!你 的不透明物體越多,排序就越容易,也越精確。你真的需要在每個地方都使用alpha blending嘛?如果你的關卡設計需要在玻璃窗上再加一層,那麽是否可以考慮修改設計以便實現起來更加容易呢?如果你正在使用alpha blending實現諸如樹木之類的裁剪(cut-out)圖形,是否可以考慮使用alpha test替代?就是簡單地考慮接受/拒絕兩種情況,這樣被接受的像素點由於是不透明的,因而仍然可以使用深度緩沖排序。

放松,不要緊張。也許排序錯誤實際上並不那麽糟糕呢?也許你可以試著調整顯卡(使alpha通道更加柔和,更加半透明化一些)使排序錯誤看起來並不那麽明顯。在我們的3D粒子采樣中就使用了這種方法,我們並沒有嘗試對單個煙霧中的粒子排序,而是挑選了一個粒子紋理使它看起來是OK的。如果你將煙霧紋理換成更加不透明的,那麽排序錯誤就會變得比較明顯了。

如果你的alpha混合模型不是凸面體,也許你可以試著將它們改的更加“凸”一些呢?即使不是完美的凸面體,只要它們越接近凸面體,排序錯誤就會越少。考慮將復雜的模型拆分成可獨立排序的多個部件。比如一個人體模型無論如何都不是凸面體,但如果你把它拆分成軀幹、頭、手臂等,那麽每一部分都可以近似認為是凸面體了。

如果你的紋理遮罩(texture masks)基本上是用於開/關裁剪(cut-outs)的,只是邊緣部分有一些透明的像素用於反走樣,你可以使用雙pass繪制技術:

  • Pass 1:繪制不透明部分:關閉alpha blending,並且alpha test只接受100%不透明的區域,深度緩沖開啟(補充:深度寫入開啟)

  • Pass 2:繪制邊緣部分:開啟alpha blending,並且alpha test只接受alpha < 1的像素,深度緩沖開啟,深度寫入關閉

以將物體繪制兩次為代價,這種方法為紋理中間不透明部分提供了100%正確的深度緩沖排序,以及相對精確的半透明邊緣部分排序。這是一種很好的方法,既對紋理裁剪的邊緣部分做了一些反走樣,同時也利用了深度緩沖的優點避免了對單個樹木或草葉進行額外的排序。我們在廣告牌采樣中使用了這種技術:請參考Billboard.fx中的註釋與effect passes部分。

使用z prepass。當你需要淡出一個正常狀態下不透明的物體而又不想透過它自己的近端部分看到它的遠端部分時,這是一種非常好的技術。假如從右邊觀察一個人 體。如果它是玻璃做的,那麽你會期望透過它的右手臂看到軀幹和左手臂。但是如果在整個淡出過程中它是一個實體人的話(不透明,也許是幽靈,或者正在傳送, 又或者被殺死後正在重生),你會期望只看到透明的右手臂部分,以及它後面的背景,而不會同時看到軀幹與左手臂。要達到這種效果需要:

  • 設置 ColorWriteChannels=None,並啟用深度緩沖

  • 繪制物體到深度緩沖(但不影響顏色緩沖)

  • 設置 ColorWriteChannels=All, DepthBufferFunction=Equal,並啟用alpha blending

  • 重繪物體,這時只有物體的最近端才會被混合到顏色緩沖中

深度排序與alpha混合