Unity OnRenderImage


https://blog.csdn.net/wodownload2/article/details/104455641

關於這個話題,網上的資料真的是太少了,主要都是關於在OnRenderImage實現后處理的一些基本的操作。
但是對於其原理方面講解的很少,可能外網我還沒搜到,后面如果找到的話,會再補充更新。

網上的資料:https://gameinstitute.qq.com/community/detail/112744
可以參考,但主要還是看我這篇的實驗分析即可。

本文可能有點啰嗦,主要還是多種情況考慮的結果分析。

第一部分:
1、准備一個測試腳本:TestRenderTexture.cs
將其掛在主攝像機上即可,此時腳本里面的內容為空,后面會測試多種情況。
此時在什么都沒做的情況下,只是在主攝像機上掛了一個TestRenderTexture腳本,此時我們讓攝像機看到一個球體,如下:

此時打開幀調試器:

此時,一切都和我們之前一樣,沒有任何的變化。這里我們不做任何的總結,因為實驗還未開始展開,總結是沒有任何根據的。ok,我們繼續:

2、在TestRenderTexture.cs中,做如下的操作:
2.1)在腳本中重寫OnRenderImage方法,看其變化

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
// 不做任何操作

此時屏幕黑了:

幀調試器:

此時有點變化,屏幕黑色這是其一,第二個,攝像機的畫面,畫到一個臨時TempBuffer中去了。
斷點情況:

source為那個臨時緩沖區,destination為null。
我們繼續。

3、在OnRenderImage方法中,使用Blit方法:
代碼變為:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination);
}
}


此時結果:

幀調試器:

斷點情況:

此時屏幕能看到東西了,原因是我們在OnRenderImage方法中執行了Blit操作。
這blit我們看看,source為臨時緩沖,destination為null,也就是將臨時緩沖,拷貝到null,我們知道destination為空,則是直接拷貝到屏幕。所以我們看到了屏幕有東西。同時主要到幀調試器中,多了一個ImageEffects的繪制調用,使用內置的BlitCopy着色器,將臨時緩沖拷貝到了屏幕上去。


4、創建要給rt,在start方法中賦值給攝像機

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt;
public Camera m_camera;

public void Start()
{
rt = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt.name = "xxx";
m_camera.targetTexture = rt;
}
}


此時屏幕:

屏幕為黑色,並且攝像機的targetTexture為我們創建的rt了。
幀調試器:

此時,我們將東西繪制到了我們創建的rt上去了,所以屏幕為黑色。

5、在腳本中,添加OnRenderImage方法,但是只是空方法。

此時,屏幕依然為黑色,輸出一個提示,說目標未畫任何東西。
幀調試器:

此時多了后處理,ImageEffects調用。將ImageEffects Temp拷貝到了我們的RT。
此時我們看rt:

rt為黑色,沒有任何的東西。
此時我們看斷點:

source變了,變成我們rt,目標也變了,變成了ImageEffects Temp了。
我們回憶下,我們設置了攝像機的rt,我們重寫OnRenderImage方法,但是在方法中沒有做任何的操作。
此時,幀調試器有ImageEffects調用了,並且執行了BlitCopy,是將ImageEffects Temp拷貝到我們的RT。
這一步是unity自己幫我們做的。為什么拷貝到了我們自己RT呢,因為我們設置了Camera的targetTexture。

而為什么我們支持的rt上為黑色呢?可能有點繞,但是要屏住呼吸,聽我說:
我們設置了rt給攝像機,我們重寫了OnRenderImage方法,但是里面未做任何事情,此時unity為我們做了一個操作(幀調試器可以看到做了一個Dynamic Draw的操作,執行BlitCopy),就是將ImageEffects Temp里面的內容,拷貝到了我們的rt,ok,那么我們就問了,ImageEffects Temp里面內容是啥,也許你知道了答案。
對的,此時Image Effects Temp里面的內容為空,沒畫任何的東西,所以最終的rt也為黑色。u

6、接着5,我們在OnRenderImage方法中,添加blit操作:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt;
public Camera m_camera;

public void Start()
{
rt = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt.name = "xxx";
m_camera.targetTexture = rt;
}

public void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination);
}
}


此時屏幕:

屏幕依然為黑色,這是應當的,因為我們把東西畫到rt,camera的targetTexture設置成了rt。


我們rt上,驚奇的出現了東西,不再是黑色。
我們再看幀調試器:


我們可以看到鎮調試,多了一個blit操作,就是我們自己寫的那個blit操作:

public void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination);
}

我們再看下,斷點情況:

這個就是對應了,我們的幀調試器中的:

我們再來回顧下:
我們設置了camera的rt,我們重寫OnRenderImage方法,我們在此方法中,執行一個Blit(source, destination)的操作。
此時source為我們rt,destination為ImageEffets Temp。然后unity又默認的為我們將ImageEffects Temp拷貝到我們設置的rt上去。

其實5中的操作,rt上是有東西,但是unity最終將ImageEffets Temp拷貝到了rt,而我們沒有對ImageEffets Temp做任何內容的填充。對比6,6多了一個將rt拷貝到ImageEffets Temp的操作。所以在6中,我們看到rt上有東西。

以上是關於在設置的rt的全部內容。並且是在start方法中設置rt的流程。

==========================================================================================

第二部分:
下面做的是,將rt移動到PreRender方法中去,看有什么變化:
1、代碼如下:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt;
public Camera m_camera;

public void Start()
{
rt = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt.name = "xxx";
}

private void OnPreRender()
{
m_camera.targetTexture = rt;
}

private void OnPostRender()
{
m_camera.targetTexture = rt;
}
}


此時:
此時屏幕為黑色,因為我們將內容繪制到rt上去。rt的內容:

rt上有值。此時幀調試器:

目標是我們rt。

2、我們重寫OnRenderImage方法

畫面為黑色,幀調試器,從ImageEffects Temp拷貝到我們rt:

source為我們的rt,目標是ImageEffects Temp。
3、我們在OnRenderImage方法中執行blit:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt;
public Camera m_camera;

public void Start()
{
rt = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt.name = "xxx";
}

private void OnPreRender()
{
m_camera.targetTexture = rt;
}

private void OnPostRender()
{
m_camera.targetTexture = rt;
}

private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination);
}
}

 

此時斷點:

可以看到這個和上面案例6是一樣的。參考案例5和6。

=======================================================================

第三部分:
1、我們使用buffer方式,設置rt:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt;
public Camera m_camera;

public void Start()
{
rt = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt.name = "xxx";
m_camera.SetTargetBuffers(rt.colorBuffer, rt.depthBuffer);
}
}

 

屏幕黑色,rt有東西。

2、重寫OnRenderImage方法:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt;
public Camera m_camera;

public void Start()
{
rt = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt.name = "xxx";
m_camera.SetTargetBuffers(rt.colorBuffer, rt.depthBuffer);
}

private void OnRenderImage(RenderTexture source, RenderTexture destination)
{

}
}

 

屏幕黑色,rt有內容:

斷點情況:

此時source為null,為什么是屏幕呢?因為我們設置了colorbuffer和depthbuffer,但未設置camera的targetTexture,所以是源頭是null,也就是屏幕。但是此時colorbuffer中是有值的,正如上面看到的。那為什么不是黑色的呢?和第一部分的,設置了targetTexture,有什么不同呢?不同的地方看幀調試器,幀調試器,最后執行的是ImageEffects Temp拷貝的操作,拷貝到了哪里?拷貝到了屏幕,那為什么不是拷貝到了我們的colorbuffer呢?因為我們沒有設置camera的targetTexture。所以最后是拷貝到了屏幕。

m_camera.SetTargetBuffers(rt.colorBuffer, rt.depthBuffer);
1
設置,也就是改變了繪制目標。等同於設置camera的targetTexture。但是不用buffer等同於rt,所以是null,這點解釋我都看不下去了,不知道怎么解釋。以后再悟。

3、在OnRenderIamge方法中做blit:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt;
public Camera m_camera;

public void Start()
{
rt = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt.name = "xxx";
m_camera.SetTargetBuffers(rt.colorBuffer, rt.depthBuffer);
}

private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination);
}
}

 

斷點情況:

source為null,就是我們colorbuffer,又colorbufer拷貝到ImageEffects Temp。最后unity默認執行一個ImageEffects拷貝到目標,而目標此時沒有設置camera的targetTexture所以是拷貝到了屏幕。所以這種做法,其實就是相當於做了以抓屏幕,抓到了colorbuffer中去,如幀調試器中看到的:

ok,到此第三部分結束。

有人還會問,如果將設置colorbuffer代碼,移動到PreRender中呢?然后在OnPostRender中設置null,這個讀者自行嘗試下,不再給出具體分析。

本文的第四部分,將是討論targetTexture和SetTargetBuffers兩種方式能不能同時存在的問題。

第四部分:
1、創建兩個rt,一個是以targetTexture方式設置到主相機,一個是以SetTargetBuffers方式設置主相機,看兩個rt上有沒有值:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt;
public RenderTexture rt2;
public Camera m_camera;

public void Start()
{
rt = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt.name = "xxx";
rt2 = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt2.name = "yyy";
m_camera.targetTexture = rt;
m_camera.SetTargetBuffers(rt2.colorBuffer, rt2.depthBuffer);
}
}

 

rt1是設置成攝像機的targetTexture,而rt2,設置成了buffer。並且rt1先。

可以看到rt2上有值,但是rt1上黑色。

2、我們把順序調換下:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt1;
public RenderTexture rt2;
public Camera m_camera;

public void Start()
{
rt1 = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt1.name = "xxx";
rt2 = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt2.name = "yyy";
m_camera.SetTargetBuffers(rt2.colorBuffer, rt2.depthBuffer);
m_camera.targetTexture = rt1;
}
}

 

我們看到rt1上有值。
也就是誰靠后,誰有值。

3、重寫OnRenderImage方法:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt1;
public RenderTexture rt2;
public Camera m_camera;

public void Start()
{
rt1 = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt1.name = "xxx";
rt2 = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt2.name = "yyy";
m_camera.SetTargetBuffers(rt2.colorBuffer, rt2.depthBuffer);
m_camera.targetTexture = rt1;
}

public void OnRenderImage(RenderTexture source, RenderTexture destination)
{
}
}


此時rt1,靠后,是有效值。

此時,沒有做任何的blit,所以:rt1和rt2都是黑色。

 

4、在OnRenderIamge方法中,添加blit:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt1;
public RenderTexture rt2;
public Camera m_camera;

public void Start()
{
rt1 = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt1.name = "xxx";
rt2 = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt2.name = "yyy";
m_camera.SetTargetBuffers(rt2.colorBuffer, rt2.depthBuffer);
m_camera.targetTexture = rt1;
}

public void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination);
}
}

 

rt1靠后,rt1是camera的targetTexture,並且經過blit操作,所以rt1上有值。斷點情況:


5、調換rt1和rt2的順序:

using UnityEngine;

public class TestRenderTexture : MonoBehaviour
{
public RenderTexture rt1;
public RenderTexture rt2;
public Camera m_camera;

public void Start()
{
rt1 = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt1.name = "xxx";
rt2 = new RenderTexture(m_camera.pixelWidth, m_camera.pixelHeight, 16); //16位的深度
rt2.name = "yyy";
m_camera.targetTexture = rt1;
m_camera.SetTargetBuffers(rt2.colorBuffer, rt2.depthBuffer);
}

public void OnRenderImage(RenderTexture source, RenderTexture destination)
{
Graphics.Blit(source, destination);
}
}

 


rt2有值:


也即是說,targetTexture和SetTargetBuffers兩者不能共存,誰靠后,就按照誰的規則。

並且這句代碼:

沒有什么用,Inspector面板上也沒有指定的targetTexture,因為rt1的指定在SetTargetBuffers之前,被沖掉了。

那么問題來了,能不能用同一個rt的顏色緩沖和深度緩沖呢?我暫時認為是不行的,因為unity怎么知道,將深度寫在那個圖的哪個通道呢?所以一般做法是,開一個rt用於顏色,再開一個rt用作深度,這樣生成兩個圖,傳遞給shader使用即可。

以上是我關於OnRenderImage函數的全部總結,希望對讀者有點幫助。
當然這里沒有涉及到任何的后處理的效果,后面會根據這個原理,再寫幾個實際例子,再次印證本文總結的規律。

總結規律如下:
1、rt和buffer不能同時存在,誰靠后設置,誰有效。
2、設置rt,重寫了OnRenderImage,則source為rt,destination為ImageEffects Temp,unity在默認增加一個blit,ImageEffects Temp到我們的rt。rt上有值。
3、設置buffer,重寫了OnRenderImage,則source為null(即屏幕),destination為ImageEffects Temp,unity在默認增加一個blit,ImageEffects Temp到屏幕。此時buffer中是有值的,這是抓屏操作。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM