【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;
}
//这里删除的时机有问题,会导致不显示相机渲染的图像问题
//谢谢一位好心读者提醒,改正后的代码在下方
 

 

 修正后的代码:

public Camera shotCam;
public UITexture texture;
private Texture2D tex = null;
void OnPostRender()
{
//在每次相机渲染完成时再删除上一帧的texture
if(tex != null) { Destroy(tex); } //设定当前RenderTexture为快照相机的targetTexture RenderTexture rt = shotCam.targetTexture; RenderTexture.active = rt; tex = new Texture2D(rt.width, rt.height); //读取缓冲区像素信息 tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); tex.Apply(); texture.mainTexture = tex; }

 

场景中的效果如下:

QQ截图20161012000908

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运行了一段时间就把内存吃光了哭泣的脸

场景效果如下:

QQ截图20161012012735

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的内存问题,场景中效果如下:

QQ截图20161012012830

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

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


结尾语

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


posted @ 2016-10-12 01:57  Jimm  阅读(30572)  评论(1编辑  收藏  举报