首页 > 代码库 > 【Unity】使用RenderTexture为物体生成快照

【Unity】使用RenderTexture为物体生成快照

 

版权声明:本文为博主原创文章,未经博主允许不得转载。

作者:Jimm          邮箱:junmingz@foxmail.com

 


RenderTexture的定义和作用

RenderTexture are textures that can be rendered to.

RenderTexture(下文简称RTT)是可以被渲染的纹理,简称渲染纹理。一般来说,RTT可以应用在制作动态阴影,反射以及监视摄像机(车辆后视镜)等,另一方面可以应用到游戏截图,背景模糊等方面,用途十分广泛。以后这些技术都会慢慢分享到博客上,敬请期待!


RTT的用法

Camera摄像机)是Unity中非常重要的一个组件,其中有一个属性叫做targetTexture,在设置了targetTexture后,Camera会在渲染时将其屏幕上的图像渲染到targetTexture上。在相机渲染完成后可以读取屏幕像素内的缓存来使用。其中,相机渲染完成有三种调用方式:

1.OnPostRender()

OnPostRender is called after a camera finished rendering the scene.

OnPostRender在相机完成渲染场景时调用。这次遇到的需求是需要为物体生成快照,做法是另外创建一个相机,在另一个位置完成渲染工作,代码如下:

//快照相机public Camera shotCam;public UITexture texture;void OnPostRender(){    //设定当前RenderTexture为快照相机的targetTexture    RenderTexture rt = shotCam.targetTexture;    RenderTexture.active = rt;    Texture2D tex = new Texture2D(rt.width, rt.height);    //读取缓冲区像素信息    tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);    tex.Apply();    texture.mainTexture = tex;
Texture2D.Destroy(tex);
    tex = null;
}

 

场景中的效果如下:

技术分享

2.使用协程(yield return new WaitForEndOfFrame())

yield return new WaitForEndOfFrame()

等待当前帧结束。类似于OnPostRender(),可以使用for循环来依次对多个物体进行快照,代码如下:

public GameObject[] gos;void Start(){    StartCoroutine(RenderGoTexCR());}IEnumerator RenderGoTexCR(){    int length = textures.Length;    for (int i = 0; i < length; i++)    {        GameObject go = Instantiate(gos[i]);        go.SetActive(true);        yield return new WaitForEndOfFrame();        RenderTexture rt = shotCam.targetTexture;        RenderTexture.active = rt;        Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false);        tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);        tex.Apply();        textures[i].mainTexture = tex;        GameObject.Destroy(go);    }}

PS:yield语句要放在设置RenderTexture.active之前,因为只有在帧结束时shotCam的targetTexture才被正确渲染,才可以通过ReadPixels取得正确的图像。tex在使用过后最好使用Destroy()销毁,或者在设置mainTexture之前销毁之前的Texture,否则就会像博主一样写着写着博客发现Unity运行了一段时间就把内存吃光了技术分享

场景效果如下:

技术分享

3.使用Camera.Render()

我们发现当要对多个物体进行快照时OnPostRender()就没那么好用了,因为我们需要在相机渲染前将物体展示出来(OnPreRender()),在相机渲染后取得像素信息,对于两个函数分别处理GameObject我们是拒绝的(程序员就喜欢简单粗暴!)。那么使用协程又有什么问题呢?细心的同学会发现,每次渲染一个物体都需要等到帧结束,那么当需要渲染的物体较多时渲染时间会明显变长,这显然也不是我们想要的,那么有没有能在很短时间内完成大量物体的渲染呢?Camera.Render()给了我们福音。

Camera.Render()

手动渲染相机。废话不多说,贴代码:

public GameObject[] gos;void Start(){    for (int i = 0; i < textures.Length; i++)    {        GameObject go = Instantiate(gos[i]);        go.SetActive(true);        textures[i].mainTexture = RenderGoTex();        GameObject.Destroy(go);    }}Texture2D RenderGoTex(){    RenderTexture rt = shotCam.targetTexture;    shotCam.Render();    RenderTexture.active = rt;    Debug.Log(RenderTexture.active);    Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false);    tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);    tex.Apply();    return tex;}

Camera.Render()无需等待帧结束,它在调用时强制渲染相机,通过返回的tex进行操作即可,同样不要忘了Texture的内存问题,场景中效果如下:

技术分享

咦,怎么会发生重叠现象呢,明明在取得tex之后调用了Destroy()函数呀!这个也困扰了我一段时间,后来发现是Destroy函数的延迟问题。Destroy()函数对实际物体的销毁会延迟到当前循环更新后,在渲染前完成的,所以我们在这一帧执行for循环时,虽然每次循环都调用了Destroy()来销毁物体,但是实际上它们是在for循环结束后才一起销毁的,所以为了避免此问题,我们改用DestroyImmediate(),或者先调用gameObject.SetActive(false)后再Destroy(),后者是比较推荐的做法,原因请看官方文档

https://docs.unity3d.com/ScriptReference/Object.DestroyImmediate.html


结尾语

博主只是初入江湖的小菜,最近萌生写博客的想法,希望能将自己在学习和工作中遇到的问题以及所感所想与大家分享,同时也是对自我的总结。最后感谢大家的支持,你们的支持就是我的动力!


【Unity】使用RenderTexture为物体生成快照