Christian Henschel
Software Engineer
Render screen to texture in Unity (without Pro)
If you own a Unity Pro license you are able to draw the screen content to a texture by using a RenderTexture. An alternativ way for Unity Free users is the usage of Texture2D.ReadPixels. It can be used to capture the whole screen of the application or just a smaller part of it. In this post I explain an implementation of a screenshot functionality that I used in my current project: a mobile game named Gezz.
The screenshot camera should do the following things: Capture a defined part of the screen, put it into a Texture2D instance and fire an event if the screenshot has been made, so the application can do things with it (e.g. save it to disk or share it on twitter etc.).
The ScreenshotCamera class consists of two methods: TakeScreenshot & OnPostRender.
TakeScreenshot method
The first one should be called by another script like a button callback function for instance.
public void TakeScreenshot (float startX, float startY, float endX, float endY) { oldAntiAliasingSettings = QualitySettings.antiAliasing; QualitySettings.antiAliasing = 0; captureRect = new Rect (startX, startY, endX - startX, endY - startY); noAACountdown = 2; capturing = true; }
To get a clean shot we first need to turn off AntiAliasing for the moment. The old AA-setting will be restored afterwards. To do this we store the old settings before changing AA to zero. Then the rectangle defining the screen part to be copied is instantiated. The countdown value is used to make sure that the changes to the AntiAliasing settings have taken effect. Add one for each frame to wait before the screen will be captured. Lastly a flag named capturing is set. This is important for the second method of the ScreenshotCamera behaviour.
OnPostRender
The OnPostRender method is called every frame after the camera has finished rendering the screen. If the flag capturing is set true it will decrement the countdown until it reaches zero. If this is the case, the screen will be captured. For this a Texture2D instance is created using the dimensions of the previously created rectangle. Then the ReadPixels call gets the pixels from the screen and fills the texture. The capturing flag is then set to false again and the old AntiAliasing settings are restored. Now the ScreenshotCamera fires an event to let all waiting scripts know that the screen has been captured and the texture is ready to be used.
void OnPostRender () { if (capturing) { noAACountdown--; if (noAACountdown > 0) return; screenshot = new Texture2D (Mathf.RoundToInt (captureRect.width), Mathf.RoundToInt (captureRect.height), TextureFormat.ARGB32, false); screenshot.ReadPixels (captureRect, 0, 0, false); screenshot.Apply (); capturing = false; QualitySettings.antiAliasing = oldAntiAliasingSettings; if (ScreenReadyEvent != null) ScreenReadyEvent (); } }
The ScreenshotCamera behaviour
And this is the complete script. You can simply copy it and attach it to a camera. Btw: This also works for mobile games.
using UnityEngine; using System.Collections; public class ScreenshotCamera : MonoBehaviour { public delegate void ScreenReadyEventDelegate (); public event ScreenReadyEventDelegate ScreenReadyEvent; public Texture2D screenshot { get; private set; } private bool capturing = false; private Rect captureRect; private int oldAntiAliasingSettings; private int noAACountdown; public void TakeScreenshot (float startX, float startY, float endX, float endY) { oldAntiAliasingSettings = QualitySettings.antiAliasing; QualitySettings.antiAliasing = 0; captureRect = new Rect (startX, startY, endX - startX, endY - startY); noAACountdown = 2; capturing = true; } void OnPostRender () { if (capturing) { noAACountdown--; if (noAACountdown > 0) return; screenshot = new Texture2D (Mathf.RoundToInt (captureRect.width), Mathf.RoundToInt (captureRect.height), TextureFormat.ARGB32, false); screenshot.ReadPixels (captureRect, 0, 0, false); screenshot.Apply (); capturing = false; QualitySettings.antiAliasing = oldAntiAliasingSettings; if (ScreenReadyEvent != null) ScreenReadyEvent (); } } }
Very smart idea.
Thank you
Thank you for this.
I found out that this method crashed Unity if used continuously (like a security camera feed). What I had to do was put a yield statement to make sure ReadPixels was called so that all cameras where done rendering.
IEnumerator OnPostRender() {
…
yield return new WaitForEndOfFrame();
screenshot.ReadPixels (captureRect, 0, 0, false);
…
}
Another issue what that with the above fix ReadPixels was only grabbing the main camera screen regardless of camera depth or where the script was attached to. I had to save Camera.main to a variable, disable it before the yield, then re-enable it under apply(). (The use of the variable is because once if you do Camera.main.enabled =false; that camera no longer is the main camera and you lose the Camera.main reference to it.) Not sure if you need to do this for all cameras if you have more than two.
Hope this helps others!
Hey Jorge, the code was written in Uniy 4.x I believe and it is very likely that some things changed during new version releases. So thank you for the update! 🙂
i need to freeze the camera image, with a touch of a buttom, for mobile, is possible?
Hi! Why don’t you just set Timescale.time = 0 and freeze the gameplay that way?
Timescale does not work with the camera image, im using the real camera image from the vr, inneed to freeze that image