這個問題的來源是我發現在unity在移動平臺上為了得到深度圖,通常需要一個單獨的pass,例如我們用到的一些后處理特效需要用刀場景深度信息,我們通過把camera的flag設置為depth tex 打開,unity就會在每幀用一個單獨的pass繪制深度圖,這其實很浪費,我們隊全場景的頂點提交了兩次,增加了大量drawcall。為了說明解決這個問題的思路,我們從framebuffer說起。
0. 關于framebuffer
我們知道把場景渲染出來的過程,最終就是把圖像繪制到一個FrameBuffer上的過程,現代gpu支持多個framebuffer,顯示器屏幕的framebuffer是默認的fbo,當然除了顯示器屏幕我們還可以渲染到其他自己定義的fbo來實現渲染到貼圖等等。在unity中可以放置多個多個攝像機,這些攝像機在不同情況下渲染的目的fbo也是不一樣的。
我們需要知道一個fbo包括很多位面,通常至少包括兩個color buffer(前和后雙緩存用來繪制和展示),一個depth buffer
我們可以在不同的framebuffer之間拷貝(gpu上的互拷),包括渲染拷貝color或depth,至于gpu到cpu的拷貝我們可以把framebuffer拷貝回cpu內存,但是只限于color位面(截屏),很多底層不支持depth的cpu回拷
默認被激活的fbo就是屏幕的那個fbo,當然也可以切換激活到自定義的fbo來實現向自定義fbo的繪制。
默認的屏幕那個fbo里面的buffer不能被更改,例如gles中它不有gles提供,二是由egl提供,但是自定義的fbo就可以更改里面的buffer
1. unity的camera的fbo的定向規則:
① 對于一個單個最簡單的攝像機,它的渲染的fbo就直接是屏幕的fbo
② 對于多個簡單攝像機,也是按照攝像機順序先后流到屏幕fbo
③ 對于帶有屏幕后處理特效的單個攝像機,渲染先流到一個自定義fbo,然后一層層的經過后處理,每層后處理都流向一個新的自定義fbo,直到最后一層后處理自動留到屏幕fbo,
④ 對個多個帶有屏幕后處理特效的攝像機,按照攝像機的順序逐個渲染,對于第一個攝像機,先渲染到一個自定義fbo,然后一層層的經過后處理,每層后處理都流向一個新的自定義fbo,對于后面的攝像機,顯然渲染到上一個的攝像機的最后后處理出來的那個自定義fbo,也一層層后處理下去,直到最后一個攝像機的最后一層后處理,會流向屏幕fbo
2. 改變攝像機的fbo流向
有很多方法和很多理由需要改變攝像機的fbo,例如渲染到rendertexture,例如我們最開始的那個問題
① 直接創建一個rendertarget,然后賦給攝像機的target,這樣這個攝像機(包括加過后處理特效)的最終流向就到了這個rendertexture,因為本質上rendertexture就是一個自定義的fbo
② 通過camera.settarget(color,depth).,可以分別設置color和depth的buffer,這在底層其實是新建了一個fbo,然后把它激活,但是你不能把color定向到屏幕,而depth定向到別處,因為color和depth的buffer必須待在一個fbo上,而屏幕的fbo中的buffer又不能被更改。
對于被設置了settarget而重定向了buffer的camera的渲染的流向規則比較奇特是這樣的:
① 如果沒有屏幕特效,它直接渲染到自定義的fbo上,屏幕上不可見
② 如果有屏幕特效,它直接渲染到自定義的fbo上,然后一層層的后處理,知道最后一層后處理直接流向屏幕,可以說在這種情況下和我們的預期有些不符,因為這種情況下我們的自定義的rt上沒有屏幕特效,而屏幕上出現了
③如果有多個攝像機,每個都被定向到自定義的fbo,每個都有后處理特效,那么第一個攝像機先渲染到定向的那個fbo,然后每層特效后渲染到一個新的自定義的fbo,如果不是最后一個攝像機,最后一層特效將重新流向定向的那個fbo,而對于最后一個攝像機,最終流向屏幕fbo。也就是這種情況下定向的fbo少了最后一個攝像機的特效,而屏幕出現了完整的結果
3. 如何從屏幕獲取depth tex?
回到這個問題。我們自然的會想到為什么unity不直接使用上一幀剛剛在屏幕上繪制好的那個framebuffer中的depth?答案肯定是完全可以,unity沒有這樣做可能是出于為了得到一個它可控的深度圖,例如如果我們在渲染場景時自定義了一些z的操作,那么這個z的深度圖可能不準,但是事實上對于不透明物體我們幾乎不會干這種事。于是我打算從屏幕直接獲取剛剛寫過的那個depth,而強制unity不用這個浪費的單獨的depth pass。
如何做到?最簡單的是直接利用fbo的互拷貝的從屏幕的fbo拷貝過來到一個自定義的fbo,然而使用blit這種操作并不能穩定的獲取屏幕的z,查閱一些資料也有說從屏幕fbo拷貝z在很多設備并不穩定。那么我反過來實現,我自定義兩個buffer,一個color,一個depth,然后把unity的camera的渲染定向到自己的這兩個buffer(為什么不只重定向depth?前面說到color和depth不能一個出于屏幕,一個出于自定義)。這樣每幀繪制完透明物體后,我把depth的buffer拷貝出來就得到屏幕的z,可以給程序其他地方用了,當然最后當繪制完畢還要把color blit給屏幕,這樣屏幕才看得見圖像。這種做法相比多了一個從把color從自定義buffer blit到屏幕的操作,但是顯然相比重新來一遍深度pass性能要高很多。
當然這里有個坑,那就是如果你的攝像機上有后處理特效,那么就不要自己最后blit color回去了,至于為什么可以參考上面2.2和2.3的規則
版權聲明:本文為CSDN博主「leonwei」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。