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中是有值的,这是抓屏操作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
2020-04-19 AABB碰撞检测
2020-04-19 Unity接入多个SDK的通用接口开发与资源管理(三)
2020-04-19 Unity接入多个SDK的通用接口开发与资源管理(二)
2020-04-19 Unity接入多个SDK的通用接口开发与资源管理(一)