zoukankan      html  css  js  c++  java
  • 【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


    结尾语

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


  • 相关阅读:
    历史版本xcode的下载
    mac上安装hg
    xcode不能抓帧
    window buffer alignment
    highp 和 mediump
    AFBC mali
    AO composition
    gpu memory wait
    L2 cache//bifrost --- cortex-A55
    效果样式
  • 原文地址:https://www.cnblogs.com/Jimm/p/5951362.html
Copyright © 2011-2022 走看看