我們知道在螢屏后處理里面通過 Graphics.Blit 函式可以通過材質處理螢屏圖片, 當我們想要處理一般圖片的時候, 直接呼叫GL函式就行了, 按照習慣自己封裝一個 Blit 方法 :
public static void Blit(Texture source, Material material, RenderTexture destination, int materialPass = 0) { if(material.SetPass(materialPass)) { material.mainTexture = source; Graphics.SetRenderTarget(destination); GL.PushMatrix(); GL.LoadOrtho(); GL.Begin(GL.QUADS); { Vector3 coords = new Vector3(0, 0, 0); GL.TexCoord(coords); GL.Vertex(coords); coords = new Vector3(1, 0, 0); GL.TexCoord(coords); GL.Vertex(coords); coords = new Vector3(1, 1, 0); GL.TexCoord(coords); GL.Vertex(coords); coords = new Vector3(0, 1, 0); GL.TexCoord(coords); GL.Vertex(coords); } GL.End(); GL.PopMatrix(); } }
不需要這么麻煩, 直接Graphics.Blit(...)就行了
因為 Graphics.SetRenderTarget 方法傳入的是 RenderTexture, 渲染出來的 RenderTexture 不能直接當成 Texture2D 或 Cubemap 或 Texture3D 等來使用, 一般需要進行二次轉換. 就拿 Texture2D 來作為例子, 轉換方法貌似有那么幾種, 下來看看 :
0. 各個變數
public Material material; public Texture2D input; public Texture2D outPutTex2D; public RenderTexture renderTexture;
1. 使用指標的方式, 一般來說如果 RenderTexture 的記憶體跟 Texture2D 一樣的話, 用 Texture2D 直接指向 RenderTexture 的相關地址應該就可以了, 因為官方沒有檔案直接就測驗代碼 :
private void Start() { if(renderTexture == false) { renderTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); } renderTexture.hideFlags = HideFlags.DontSave; outPutTex2D = Texture2D.CreateExternalTexture(Screen.width, Screen.height, TextureFormat.ARGB32, false, true, renderTexture.GetNativeTexturePtr()); // ArgumentException: nativeTex can not be null }
在渲染 RenderTexture 之前獲取它的 GetNativeTexturePtr 是不行的, 報錯. 改一下, 在渲染完之后再獲取的話 :
private void Start() { if(renderTexture == false) { renderTexture = RenderTexture.GetTemporary(Screen.width, Screen.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); } renderTexture.hideFlags = HideFlags.DontSave; Blit(input, material, renderTexture); var nativeTexturePtr = renderTexture.GetNativeTexturePtr(); if(nativeTexturePtr != System.IntPtr.Zero) { outPutTex2D = Texture2D.CreateExternalTexture(Screen.width, Screen.height, TextureFormat.ARGB32, false, true, nativeTexturePtr); } }
直接就崩了, 雖然斷點看到它的 nativeTexturePtr 確實能獲取到, 不過想到 RenderTexture 在創建的時候沒有指定是哪種記憶體, 直接用指標來創建 Texture2D 應該就是會崩的吧.

因為官方檔案啥也沒寫, 估計這條路走不通...
PS : 補充一下, 就算設定了 RenderTexture 的型別也是會崩潰的.
renderTexture = new RenderTexture(W, H, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); renderTexture.dimension = UnityEngine.Rendering.TextureDimension.Tex2D; renderTexture.enableRandomWrite = true; renderTexture.wrapMode = TextureWrapMode.Clamp; renderTexture.Create(); outPutTex2D = Texture2D.CreateExternalTexture(W, H, TextureFormat.ARGB32, false, true, renderTexture.GetNativeDepthBufferPtr()); // 崩潰
2. 使用 Texture2D.ReadPixels 方法, 這個是最常見的方法 :
private void Start() { int W = (int)Screen.width; int H = (int)Screen.height; if(renderTexture == false) { renderTexture = RenderTexture.GetTemporary(W, H, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); } renderTexture.hideFlags = HideFlags.DontSave; outPutTex2D = new Texture2D(W, H, TextureFormat.ARGB32, false, true); Blit(input, material, renderTexture); var current = RenderTexture.active; RenderTexture.active = renderTexture; outPutTex2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0); outPutTex2D.Apply(); RenderTexture.active = current; }
這個最常用, 結果也是正確的沒什么好說的, 只是效率堪憂

3. 呼叫 Graphics.CopyTexture 復制圖片 :
private void Start() { int W = (int)Screen.width; int H = (int)Screen.height; if(renderTexture == false) { renderTexture = RenderTexture.GetTemporary(W, H, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); } renderTexture.hideFlags = HideFlags.DontSave; outPutTex2D = new Texture2D(W, H, TextureFormat.ARGB32, false, true); Blit(input, material, renderTexture); if((SystemInfo.copyTextureSupport & UnityEngine.Rendering.CopyTextureSupport.RTToTexture) != 0) { Graphics.CopyTexture(renderTexture, outPutTex2D); } }
這個應該就是上面的使用指標地址進行復制的封裝, 效率杠杠的. 而且也不需要對應型別.

把這些功能封裝一下, 由于它的使用條件比較嚴格, 并且性能差距巨大, 這種就需要每個人都注意才行 :
public static void CopyTexture(RenderTexture from, Texture2D to) { if(from && to) { if((SystemInfo.copyTextureSupport & UnityEngine.Rendering.CopyTextureSupport.RTToTexture) != 0 && (from.width == to.width && from.height == to.height)) { Graphics.CopyTexture(from, to); } else { var current = RenderTexture.active; RenderTexture.active = from; to.ReadPixels(new Rect(0, 0, Mathf.Min(from.width, to.width), Mathf.Min(from.height, to.height)), 0, 0); to.Apply(); RenderTexture.active = current; } } }
Graphics.CopyTexture
PS : 在官方檔案里面看見一句話
Compressed texture formats add some restrictions to the CopyTexture with a region variant. For example, PVRTC formats are not supported since they are not block-based (for these formats you can only copy whole texture or whole mip level). For block-based formats (e.g. DXT, ETC), the region size and coordinates must be a multiple of compression block size (4 pixels for DXT).
If both source and destination textures are marked as "readable" (i.e. copy of data exists in system memory for reading/writing on the CPU), these functions copy it as well.
第一句是廢話, 不是對應的大小引擎都不讓你壓縮, 第二句話括號里的意思是說打開了 reading/writing 的圖片, 會在系統記憶體里面存在副本, 那么就是雙倍的記憶體占用了么? 趕緊測驗一下...

沒錯, 正是如此......雖然圖片匯入設定自動就是不打開Read/Write的, 以后還是注意一下的好...
(2019.12.10)
補充 : 說到 Read/Write 的問題, 順便就說到圖片是否可讀的問題了, 當我們使用 Graphics.CopyTexture 的時候, 邏輯是記憶體指標的修改, 所以速度賊快, 可是一般來說系統 RenderTexture 的記憶體是不可讀的, 相當于關閉了 Read/Write 的功能了, 這在哪些地方會出問題呢?
1. 保存圖片, 如果要保存圖片到硬碟, 使用 Texture2D.EncodeToJPG(); 方法的話, 得到的就是一片灰色, 至于為什么不是白色或者黑色, 之后再研究, 反正得不到在運行時看到的圖片就是了.
2. 獲取 / 設定 圖片片元顏色, Texture2D.GetPixel(...) 報錯.
補充 : 用 RenderTexture 作為 Camera.targetTexture 使用的時候, 在相機打開了深度圖渲染時一定要用帶深度的 RenderTexture, 不然會渲染出奇怪的東西...
(2020.04.28)
當我們的圖片是壓縮格式的時候, 對圖片進行GetPixel或者Copy等操作都是不可能的, 因為操作在CPU層面進行, 而壓縮圖片是在GPU進行解壓的, 如下所示:
public static Texture2D BlitUnreadableTextureToTexture2D(Texture2D from) { var readableTexture = new Texture2D(from.width, from.height, TextureFormat.ARGB32, false, true); readableTexture.alphaIsTransparency = from.alphaIsTransparency; Graphics.CopyTexture(from, readableTexture); // 報錯 return readableTexture; }
public static void BlitUnreadableTextureToTexture2D(Texture2D from) { from.GetPixel(0, 0); // 報錯 }
必須通過GPU進行獲取, 沒錯就是使用Blit RenderTexture的方式來獲取解壓像素:
public static Texture2D BlitUnreadableTextureToTexture2D(Texture2D from) { var readableTexture = new Texture2D(from.width, from.height, TextureFormat.ARGB32, false, true); readableTexture.alphaIsTransparency = from.alphaIsTransparency; var renderTexture = RenderTexture.GetTemporary(from.width, from.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); renderTexture.hideFlags = HideFlags.DontSave; var blitMaterial = new Material(AssetDatabase.LoadAssetAtPath<Shader>("Assets/Editor/EditorResources/SimpleBlitShader.shader")); Graphics.Blit(from, renderTexture, blitMaterial, 0); // 仍然不可讀 var current = RenderTexture.active; RenderTexture.active = renderTexture; readableTexture.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0); readableTexture.Apply(); RenderTexture.active = current; RenderTexture.ReleaseTemporary(renderTexture); return readableTexture; }
這里經過Blit之后的RenderTexture仍然是非可讀的, 必須經過低效率的ReadPixels方式讀入readableTexture......
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/6742.html
標籤:其他
