淺談Unity中 RenderTexture 實現原理(轉)

本文中大部分例子將按照Opengles的實現來解釋ide

1.RenderTexture是什麼

 

     在U3D中有一種特殊的Texture類型,叫作RenderTexture,它本質上一句話是將一個FrameBufferObjecrt鏈接到一個server-side的Texture對象。函數

     什麼是server-sider的texture?優化

     在渲染過程當中,貼圖最開始是存在cpu這邊的內存中的,這個貼圖咱們一般稱爲client-side的texture,它最終要被送到gpu的存儲裏,gpu才能使用它進行渲染,送到gpu裏的那一份被稱爲server-side的texture。這個tex在cpu和gpu之間拷貝要考慮到必定的帶寬瓶頸。spa

     什麼是FrameBufferObject?server

     FrameBuffer就是gpu裏渲染結果的目的地,咱們繪製的全部結果(包括color depth stencil等)都最終存在這個這裏,有一個默認的FBO它直接連着咱們的顯示器窗口區域,就是把咱們的繪製物體繪製到顯示器的窗口區域。可是現代gpu一般能夠建立不少其餘的FBO,這些FBO不鏈接窗口區域,這種咱們建立的FBO的存在目的就是容許咱們將渲染結果保存在gpu的一塊存儲區域,待以後使用,這是一個很是有用的東西。對象

    當渲染的結果被渲染到一個FBO上後,就有不少種方法獲得這些結果,咱們能想一想的使用方式就是把這個結果做爲一個Texture的形式獲得,一般有這樣幾種方式獲得這個貼圖:接口

    a) 將這個FBO上的結果傳回CPU這邊的貼圖,在gles中的實現通常是ReadPixels()這樣的函數,這個函數是將當前設爲可讀的FBO拷貝到cpu這邊的一個存儲buffer,沒錯若是當前設爲可讀的FBO是那個默認FBO,那這個函數就是在截屏,若是是你本身建立的FBO,那就把剛剛繪製到上面的結果從gpu存儲拿回內存。內存

   b)   將這個FBO上的結果拷貝到一個gpu上的texture,在gles中的實現通常是CopyTexImage2D(),它通常是將可讀的FBO的一部分拷貝到存在於gpu上的一個texture對象中,直接考到server-sider就意味着能夠立刻被gpu渲染使用ci

  c)  將這個fbo直接關聯一個gpu上的texture對象,這樣就等於在繪製時就直接繪製到這個texure上,這樣也省去了拷貝時間,gles中通常是使用FramebufferTexture2D()這樣的接口。資源

  

  那麼unity的RenderTexture正是這種c)方式的一種實現,它定義了在server-side的一個tex對象,而後將渲染直接繪製到這個tex上。

  這有什麼用?

  咱們能夠將場景的一些渲染結果渲染到一個tex上,這個tex能夠被繼續使用。例如,汽車的後視鏡,後視鏡就能夠貼一個rendertex,它是從這個視角的攝像機渲染而來。

  咱們還能夠利用這個進行一些圖像處理操做,傳統的圖像處理在cpu中一個for循環,一次處理一個像素,若是咱們渲染一個四方形,而後把原圖當成tex傳入進去,寫一個fragment shader,將渲染的結果渲染到一個rendertex上,那麼rendertex上的東西就是圖像處理的結果,unity中的一些圖像後處理效果(如模糊,hdr等)就是這樣作的。

 

2.渲染到RenderTexture的幾種方式

 

 1.在assets裏建立一個RenderTexture,而後將其附給一個攝像機,這樣這個攝像機實時渲染的結果就都在這個tex上了。

 2.有的時候咱們想人爲的控制每一次渲染,你能夠將這個攝像機disable掉,而後手動的調用一次render。

 3. 有的時候咱們想用一個特殊的shader去渲染這個RenderTexture,那能夠調用cam的RenderWithShader這個函數,它將使用你指定的shader去渲染場景,這時候場景物體上原有的shader都將被自動替換成這個shader,而參數會按名字傳遞。這有什麼用?好比我想獲得當前場景某個視角的黑白圖,那你就能夠寫個渲染黑白圖的shader,調用這個函數。(這裏還有一個replacement shader的概念,很少說,看下unity文檔)

 4. 咱們還能夠不用本身在assets下建立rendertexture,直接使用Graphics.Blit(src, target, mat)這個函數來渲染到render texture上,這裏的的target就是你要繪製的render texrture,src是這個mat中須要使用的_mainTex,能夠是普通tex2d,也能夠是另外一個rendertex,這個函數的本質是,繪製一個四方塊,而後用mat這個材質,用src作maintex,而後先clear爲black,而後渲染到target上。這個是一個快速的用於圖像處理的方式。咱們能夠看到UNITY的不少後處理的一效果就是一連串的Graphics.Blit操做來完成一重重對圖像的處理,若是在cpu上作那幾乎是會卡死的。

    

3.從rendertex獲取結果

 

 大部分狀況咱們渲染到rt就是爲了將其做爲tex繼續給其餘mat使用。這時候咱們只需把那個mat上調用settexture傳入這個rt就行,這徹底是在gpu上的操做。

但有的時候咱們想把它拷貝回cpu這邊的內存,好比你想保存成圖像,你想看看這個圖什麼樣,由於直接拿着rt你並不能獲得它的每一個pixel的信息,由於他沒有內存這一側的信息。Texture2d之因此有,是由於對於選擇了read/write屬性的tex2D,它會保留一個內存這邊的鏡像。這種考回就是1部分寫的a)方式,把rt從gpu拷貝回內存,注意這個操做不是效率很高。copy回的方法一般是這樣的

            Texture2D uvtexRead = new Texture2D()

            RennderTexture currentActiveRT = RenderTexture.active;
            // Set the supplied RenderTexture as the active one
            RenderTexture.active = uvTex;
            uvtexRead.ReadPixels(new Rect(0, 0, uvTexReadWidth, uvTexReadWidth), 0, 0);
            RenderTexture.active = currentActiveRT;

上面這段代碼就是等於先把當前的fbo設爲可讀的對象,而後調用相關操做將其讀回內存。

4.其餘的一些問題

 

1.rendertexture的格式,rt的格式和普通的tex2D的格式並非一回事,咱們查閱文檔,看到rt的格式支持的有不少種,最基本的ARGB32是確定支持的,不少機器支持ARRBHALF或者ARGBFLOAT這樣的格式,這種浮點格式是頗有用的,想象一下你想把場景的uv信息保存在一張圖上,你要保存的就不是256的顏色,而是一個個浮點數。可是使用前必定要查詢當前的gpu支持這種格式

2.若是你想從RenderTexture拷貝回到內存,那麼rt和拷貝回來的tex的格式必須匹配,且必須是rgba32或者RGBA24這種基本類型,你把float拷貝回來應該是不行的

3.rendertexture的分配和銷燬,若是你頻繁的要new一個rt出來,那麼不要直接new,而是使用RenderTexture提供的GetTemporary和ReleaseTemporary,它將在內部維護一個池,反覆重用一些大小格式同樣的rt資源,由於讓gpu爲你分配一個新的tex實際上是要耗時間的。更重要的這裏還會調用DiscardContents

4 DiscardContents()這個rendertex的接口很是重要,好的習慣是你應該儘可能在每次往一個已經有內容的rt上繪製以前老是調用它的這個DiscardContents函數,大體獲得的優化是,在一些基於tile的gpu上,rt和一些tile的內存之間要存在着各類同步, 若是你準備往一個已經有內容的rt上繪製,將觸發到這種同步,而這個函數告訴gpu這塊rt的內容不用管他了,我反正是要從新繪製,這樣就避免了這個同步而產生的巨大開銷。總之仍是儘可能用GetTemporray這個接口吧,它會自動爲你處理這個事情

相關文章
相關標籤/搜索