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中是有值的,這是抓屏操作。