版权声明:本文为博主原创文章,未经博主允许不得转载。
作者:Jimm 邮箱:[email protected]
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为物体生成快照