Skip to content

Instantly share code, notes, and snippets.

@bitbutter
Last active April 30, 2024 03:57
Show Gist options
  • Star 58 You must be signed in to star a gist
  • Fork 20 You must be signed in to fork a gist
  • Save bitbutter/302da1c840b7c93bc789 to your computer and use it in GitHub Desktop.
Save bitbutter/302da1c840b7c93bc789 to your computer and use it in GitHub Desktop.
Rendering screenshots from Unity3d with transparent backgrounds
using UnityEngine;
using System.Collections;
using System.IO;
/*
Usage:
1. Attach this script to your chosen camera's game object.
2. Set that camera's Clear Flags field to Solid Color.
3. Use the inspector to set frameRate and framesToCapture
4. Choose your desired resolution in Unity's Game window (must be less than or equal to your screen resolution)
5. Turn on "Maximise on Play"
6. Play your scene. Screenshots will be saved to YourUnityProject/Screenshots by default.
*/
public class TransparentBackgroundScreenshotRecorder : MonoBehaviour {
#region public fields
[Tooltip("A folder will be created with this base name in your project root")]
public string folderBaseName = "Screenshots";
[Tooltip("How many frames should be captured per second of game time")]
public int frameRate = 24;
[Tooltip("How many frames should be captured before quitting")]
public int framesToCapture = 24;
#endregion
#region private fields
private string folderName = "";
private GameObject whiteCamGameObject;
private Camera whiteCam;
private GameObject blackCamGameObject;
private Camera blackCam;
private Camera mainCam;
private int videoFrame = 0; // how many frames we've rendered
private float originalTimescaleTime;
private bool done=false;
private int screenWidth;
private int screenHeight;
private Texture2D textureBlack;
private Texture2D textureWhite;
private Texture2D textureTransparentBackground;
#endregion
void Awake () {
mainCam = gameObject.GetComponent<Camera>();
CreateBlackAndWhiteCameras ();
CreateNewFolderForScreenshots ();
CacheAndInitialiseFields ();
Time.captureFramerate = frameRate;
}
void LateUpdate () {
if(!done){
StartCoroutine(CaptureFrame());
} else {
Debug.Log("Complete! "+videoFrame+" videoframes rendered. File names are 0 indexed)");
Debug.Break();
}
}
IEnumerator CaptureFrame (){
yield return new WaitForEndOfFrame();
if(videoFrame < framesToCapture) {
RenderCamToTexture(blackCam,textureBlack);
RenderCamToTexture(whiteCam,textureWhite);
CalculateOutputTexture ();
SavePng ();
videoFrame++;
Debug.Log("Rendered frame " +videoFrame);
videoFrame++;
} else {
done=true;
StopCoroutine("CaptureFrame");
}
}
void RenderCamToTexture (Camera cam, Texture2D tex){
cam.enabled=true;
cam.Render();
WriteScreenImageToTexture(tex);
cam.enabled=false;
}
void CreateBlackAndWhiteCameras (){
whiteCamGameObject = (GameObject) new GameObject();
whiteCamGameObject.name="White Background Camera";
whiteCam=whiteCamGameObject.AddComponent<Camera>();
whiteCam.CopyFrom(mainCam);
whiteCam.backgroundColor=Color.white;
whiteCamGameObject.transform.SetParent(gameObject.transform, true);
blackCamGameObject = (GameObject) new GameObject();
blackCamGameObject.name="Black Background Camera";
blackCam=blackCamGameObject.AddComponent<Camera>();
blackCam.CopyFrom(mainCam);
blackCam.backgroundColor=Color.black;
blackCamGameObject.transform.SetParent(gameObject.transform, true);
}
void CreateNewFolderForScreenshots (){
// Find a folder name that doesn't exist yet. Append number if necessary.
folderName = folderBaseName;
int count = 1;
while (System.IO.Directory.Exists (folderName)) {
folderName = folderBaseName + count;
count++;
}
System.IO.Directory.CreateDirectory (folderName); // Create the folder
}
void WriteScreenImageToTexture (Texture2D tex){
tex.ReadPixels (new Rect (0, 0, screenWidth, screenHeight), 0, 0);
tex.Apply ();
}
void CalculateOutputTexture (){
Color color;
for (int y = 0; y < textureTransparentBackground.height; ++y) {
// each row
for (int x = 0; x < textureTransparentBackground.width; ++x) {
// each column
float alpha = textureWhite.GetPixel (x, y).r - textureBlack.GetPixel (x, y).r;
alpha = 1.0f - alpha;
if (alpha == 0) {
color = Color.clear;
}
else {
color = textureBlack.GetPixel (x, y) / alpha;
}
color.a = alpha;
textureTransparentBackground.SetPixel (x, y, color);
}
}
}
void SavePng (){
string name = string.Format ("{0}/{1:D04} shot.png", folderName, videoFrame);
var pngShot = textureTransparentBackground.EncodeToPNG ();
File.WriteAllBytes (name, pngShot);
}
void CacheAndInitialiseFields (){
originalTimescaleTime = Time.timeScale;
screenWidth = Screen.width;
screenHeight = Screen.height;
textureBlack = new Texture2D (screenWidth, screenHeight, TextureFormat.RGB24, false);
textureWhite = new Texture2D (screenWidth, screenHeight, TextureFormat.RGB24, false);
textureTransparentBackground = new Texture2D (screenWidth, screenHeight, TextureFormat.ARGB32, false);
}
}
@thehoodDE
Copy link

Hi bitbutter,
thank you for sharing your code! It does exactly what I need.
Am I free to copy and modify it and even use it for commercial projects? That would be great!

Cheers!

@gstarch
Copy link

gstarch commented Feb 15, 2019

Worked perfectly for me on Unity 2018.3.

Thanks a mil! Gerry

@ozogonkiem
Copy link

Hi, it works almost perfectly. Is it possible to make it work with post-processing stack?

@mamadDev
Copy link

Worked perfectly for me On Unity 2018.4.6f1.

And I manage to incorporate with post processing stack v2 effects

@UPSIDEDOWNMR
Copy link

I really like the script. I am having one issue with it not rendering the odd number when it outputs frames. For example I get frame_000.png and the next is frame_002.png. It seems to ignore frame_001.png and so on. Is there a solution to this or is it a setting in unity I am missing?
Thanks

@ciarandavies7191
Copy link

Remove "videoFrame++;" line 70

@FlynnRingrose
Copy link

Hello. It would seem this script is unable to render post-process effects. Would this be something that could be enabled?

Cheers.

@ChrisSkyr
Copy link

is it working fo urp?

@haosizheng
Copy link

test in urp2022. the bg is black not tranparent

@haosizheng
Copy link

hi I found the problem in URP and resolve it:
the prolem is using cam.enable can not switch to next camera/overlap the previous camera, the "render screen" stay still in this case(URP).
so I turn to use a RenderTexture, to confirm render the needed screen via create and destory thr RenderTexture. here is the code, free to use:
https://gist.github.com/haosizheng/ee23b75dac7fb12f02cae55959dad59f#file-transparentbackgroundscreenshotrecorder-cs

@GoldenAdrien
Copy link

Worked perfectly for me On Unity 2018.4.6f1.

And I manage to incorporate with post processing stack v2 effects

Just out of curiosity, how did you manage to do that? I have been trying to figure it out for a while.

@KubaKrol
Copy link

KubaKrol commented Mar 2, 2023

perfect

@stuw-u
Copy link

stuw-u commented Jun 16, 2023

Black on URP :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment