本文中大部分例子將按照Opengles的實現來解釋
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之間拷貝要考慮到一定的帶寬瓶頸。
什么是FrameBufferObject?
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渲染使用
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.其他的一些問題
https://blog.csdn.net/leonwei/article/details/54972653
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這個接口吧,它會自動為你處理這個事情
可以看到雖然RenderTexture這個技術是個普遍使用的技術,但是用好它還是要理解他的底層原理和避免一些使用的問題。
歡迎大家就這個問題進行更多討論
