-
-
Save mickdekkers/5c3c62539c057010d4497f9865060e20 to your computer and use it in GitHub Desktop.
using UnityEditor; | |
using UnityEngine; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Text.RegularExpressions; | |
// Object rendering code based on Dave Carlile's "Create a GameObject Image Using Render Textures" post | |
// Link: http://crappycoding.com/2014/12/create-gameobject-image-using-render-textures/ | |
/// <summary> | |
/// Takes snapshot images of prefabs and GameObject instances, and provides methods to save them as PNG files. | |
/// </summary> | |
public class SnapshotCamera : MonoBehaviour { | |
// This disables the "never assigned" warning. | |
// These fields will be assigned by the factory. | |
#pragma warning disable 0649 | |
/// <summary> | |
/// The Camera used internally by the SnapshotCamera. | |
/// </summary> | |
private Camera cam; | |
/// <summary> | |
/// The layer on which the SnapshotCamera takes snapshots. | |
/// </summary> | |
private int layer; | |
#pragma warning restore 0649 | |
/// <summary> | |
/// The default position offset applied to objects when none is specified. | |
/// </summary> | |
public Vector3 defaultPositionOffset = new Vector3(0, 0, 1); | |
/// <summary> | |
/// The default rotation applied to objects when none is specified. | |
/// </summary> | |
public Vector3 defaultRotation = new Vector3(345.8529f, 313.8297f, 14.28433f); | |
/// <summary> | |
/// The default scale applied to objects when none is specified. | |
/// </summary> | |
public Vector3 defaultScale = new Vector3(1, 1, 1); | |
// This private constructor serves to ensure only the factory can produce new instances. | |
private SnapshotCamera () { } | |
/// <summary> | |
/// Factory method which sets up and configures a new SnapshotCamera, then returns it. | |
/// </summary> | |
/// <param name="layer">The name of the layer on which to take snapshots.</param> | |
/// <param name="name">The name that will be given to the new GameObject the SnapshotCamera will be attached to.</param> | |
/// <returns>A new SnapshotCamera, ready for use.</returns> | |
public static SnapshotCamera MakeSnapshotCamera (string layer, string name = "Snapshot Camera") | |
{ | |
return MakeSnapshotCamera(LayerMask.NameToLayer(layer), name); | |
} | |
/// <summary> | |
/// Factory method which sets up and configures a new SnapshotCamera, then returns it. | |
/// </summary> | |
/// <param name="layer">The layer number of the layer on which to take snapshots.</param> | |
/// <param name="name">The name that will be given to the new GameObject the SnapshotCamera will be attached to.</param> | |
/// <returns>A new SnapshotCamera, ready for use.</returns> | |
public static SnapshotCamera MakeSnapshotCamera (int layer = 5, string name = "Snapshot Camera") | |
{ | |
if (layer < 0 || layer > 31) | |
throw new ArgumentOutOfRangeException("layer", "layer argument must specify a valid layer between 0 and 31"); | |
// Create a new GameObject to hold the camera | |
GameObject snapshotCameraGO = new GameObject(name); | |
// Add a Camera component to the GameObject | |
Camera cam = snapshotCameraGO.AddComponent<Camera>(); | |
// Configure the Camera | |
cam.cullingMask = 1 << layer; | |
cam.orthographic = true; | |
cam.orthographicSize = 1; | |
cam.clearFlags = CameraClearFlags.SolidColor; | |
cam.backgroundColor = Color.clear; | |
cam.nearClipPlane = 0.1f; | |
cam.enabled = false; | |
// Add a SnapshotCamera component to the GameObject | |
SnapshotCamera snapshotCamera = snapshotCameraGO.AddComponent<SnapshotCamera>(); | |
// Set the SnapshotCamera's cam and layer fields | |
snapshotCamera.cam = cam; | |
snapshotCamera.layer = layer; | |
// Return the SnapshotCamera | |
return snapshotCamera; | |
} | |
#region PNG saving | |
/// <summary> | |
/// Sanitizes a filename string by replacing illegal characters with underscores. | |
/// </summary> | |
/// <param name="dirty">The unsanitized filename string.</param> | |
/// <returns>A sanitized filename string with illegal characters replaced with underscores.</returns> | |
private static string SanitizeFilename (string dirty) | |
{ | |
string invalidFileNameChars = Regex.Escape(new string(Path.GetInvalidFileNameChars())); | |
string invalidRegStr = string.Format(@"([{0}]*\.+$)|([{0}]+)", invalidFileNameChars); | |
return Regex.Replace(dirty, invalidRegStr, "_"); | |
} | |
/// <summary> | |
/// Saves a byte array of PNG data as a PNG file. | |
/// </summary> | |
/// <param name="bytes">The PNG data to write to a file.</param> | |
/// <param name="filename">The name of the file. This will be the current timestamp if not specified.</param> | |
/// <param name="directory">The directory in which to save the file. This will be the game/Snapshots directory if not specified.</param> | |
/// <returns>A FileInfo pointing to the created PNG file</returns> | |
public static FileInfo SavePNG (byte[] bytes, string filename = "", string directory = "") | |
{ | |
directory = directory != "" ? Directory.CreateDirectory(directory).FullName : Directory.CreateDirectory(Path.Combine(Application.dataPath, "../Snapshots")).FullName; | |
filename = filename != "" ? SanitizeFilename(filename) + ".png" : System.DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss-ffff") + ".png"; | |
string filepath = Path.Combine(directory, filename); | |
File.WriteAllBytes(filepath, bytes); | |
return new FileInfo(filepath); | |
} | |
/// <summary> | |
/// Saves a Texture2D as a PNG file. | |
/// </summary> | |
/// <param name="tex">The Texture2D to write to a file.</param> | |
/// <param name="filename">The name of the file. This will be the current timestamp if not specified.</param> | |
/// <param name="directory">The directory in which to save the file. This will be the game/Snapshots directory if not specified.</param> | |
/// <returns>A FileInfo pointing to the created PNG file</returns> | |
public static FileInfo SavePNG (Texture2D tex, string filename = "", string directory = "") | |
{ | |
return SavePNG(tex.EncodeToPNG(), filename, directory); | |
} | |
#endregion | |
#region Object preparation | |
/// <summary> | |
/// This stores the a state (layers, position, rotation, and scale) of a GameObject, and provides a method to restore it. | |
/// </summary> | |
private struct GameObjectStateSnapshot | |
{ | |
private GameObject gameObject; | |
private Vector3 position; | |
private Quaternion rotation; | |
private Vector3 scale; | |
private Dictionary<GameObject, int> layers; | |
/// <summary> | |
/// Store the current state (layers, position, rotation, and scale) of a GameObject | |
/// </summary> | |
/// <param name="gameObject">The GameObject whose state to store.</param> | |
public GameObjectStateSnapshot (GameObject gameObject) | |
{ | |
this.gameObject = gameObject; | |
this.position = gameObject.transform.position; | |
this.rotation = gameObject.transform.rotation; | |
this.scale = gameObject.transform.localScale; | |
this.layers = new Dictionary<GameObject, int>(); | |
foreach (Transform t in gameObject.GetComponentsInChildren<Transform>(true)) | |
{ | |
this.layers.Add(t.gameObject, t.gameObject.layer); | |
} | |
} | |
/// <summary> | |
/// Restore the gameObject to the state stored in this GameObjectStateSnapshot. | |
/// </summary> | |
public void Restore () | |
{ | |
this.gameObject.transform.position = this.position; | |
this.gameObject.transform.rotation = this.rotation; | |
this.gameObject.transform.localScale = this.scale; | |
foreach (KeyValuePair<GameObject, int> entry in this.layers) | |
{ | |
entry.Key.layer = entry.Value; | |
} | |
} | |
} | |
/// <summary> | |
/// Set the layers of the GameObject and all its children to the SnapshotCamera's snapshot layer so the SnapshotCamera can see it. | |
/// </summary> | |
/// <param name="gameObject">The GameObject apply the layer modifications to.</param> | |
private void SetLayersRecursively (GameObject gameObject) | |
{ | |
foreach (Transform transform in gameObject.GetComponentsInChildren<Transform>(true)) | |
transform.gameObject.layer = layer; | |
} | |
/// <summary> | |
/// Prepares an instantiated GameObject for taking a snapshot by setting its layers and applying the specified position offset, rotation, and scale to it. | |
/// </summary> | |
/// <param name="prefab">The instantiated GameObject to prepare.</param> | |
/// <param name="positionOffset">The position offset relative to the SnapshotCamera to apply to the gameObject.</param> | |
/// <param name="rotation">The rotation to apply to the gameObject.</param> | |
/// <param name="scale">The scale to apply to the gameObject.</param> | |
/// <returns>A GameObjectStateSnapshot containing the state of the gameObject prior to modifying its layers, position, rotation, and scale.</returns> | |
private GameObjectStateSnapshot PrepareObject (GameObject gameObject, Vector3 positionOffset, Quaternion rotation, Vector3 scale) | |
{ | |
GameObjectStateSnapshot goss = new GameObjectStateSnapshot(gameObject); | |
gameObject.transform.position = transform.position + positionOffset; | |
gameObject.transform.rotation = rotation; | |
gameObject.transform.localScale = scale; | |
SetLayersRecursively(gameObject); | |
return goss; | |
} | |
/// <summary> | |
/// Prepares a prefab for taking a snapshot by creating an instance, setting its layers and applying the specified position offset, rotation, and scale to it. | |
/// </summary> | |
/// <param name="prefab">The prefab to prepare.</param> | |
/// <param name="positionOffset">The position offset relative to the SnapshotCamera to apply to the prefab.</param> | |
/// <param name="rotation">The rotation to apply to the prefab.</param> | |
/// <param name="scale">The scale to apply to the prefab.</param> | |
/// <returns>A prefab instance ready for taking a snapshot.</returns> | |
private GameObject PreparePrefab (GameObject prefab, Vector3 positionOffset, Quaternion rotation, Vector3 scale) | |
{ | |
GameObject gameObject = GameObject.Instantiate(prefab, transform.position + positionOffset, rotation) as GameObject; | |
gameObject.transform.localScale = scale; | |
SetLayersRecursively(gameObject); | |
return gameObject; | |
} | |
#endregion | |
#region TakeObjectSnapshot | |
/// <summary> | |
/// Takes a snapshot of an instantiated GameObject and returns it as a Texture2D. | |
/// | |
/// Uses a completely transparent background and | |
/// applies the default position offset, rotation and scale to the gameObject while taking the snapshot, and restores them afterwards. | |
/// </summary> | |
/// <param name="gameObject">The instantiated GameObject to snapshot.</param> | |
/// <param name="width">The width of the snapshot image.</param> | |
/// <param name="height">The height of the snapshot image.</param> | |
/// <returns>A Texture2D containing the captured snapshot.</returns> | |
public Texture2D TakeObjectSnapshot (GameObject gameObject, int width = 128, int height = 128) | |
{ | |
return TakeObjectSnapshot(gameObject, Color.clear, defaultPositionOffset, Quaternion.Euler(defaultRotation), defaultScale, width, height); | |
} | |
/// <summary> | |
/// Takes a snapshot of an instantiated GameObject and returns it as a Texture2D. | |
/// | |
/// Applies the default position offset, rotation and scale to the gameObject while taking the snapshot, and restores them afterwards. | |
/// </summary> | |
/// <param name="gameObject">The instantiated GameObject to snapshot.</param> | |
/// <param name="backgroundColor">The background color of the snapshot. Can be transparent.</param> | |
/// <param name="width">The width of the snapshot image.</param> | |
/// <param name="height">The height of the snapshot image.</param> | |
/// <returns>A Texture2D containing the captured snapshot.</returns> | |
public Texture2D TakeObjectSnapshot (GameObject gameObject, Color backgroundColor, int width = 128, int height = 128) | |
{ | |
return TakeObjectSnapshot (gameObject, backgroundColor, defaultPositionOffset, Quaternion.Euler(defaultRotation), defaultScale, width, height); | |
} | |
/// <summary> | |
/// Takes a snapshot of an instantiated GameObject and returns it as a Texture2D. | |
/// | |
/// Uses a completely transparent background. | |
/// </summary> | |
/// <param name="gameObject">The instantiated GameObject to snapshot.</param> | |
/// <param name="positionOffset">The position offset relative to the SnapshotCamera that will be applied to the gameObject while taking the snapshot. Its position will be restored after taking the snapshot.</param> | |
/// <param name="rotation">The rotation that will be applied to the gameObject while taking the snapshot. Its rotation will be restored after taking the snapshot.</param> | |
/// <param name="scale">The scale that will be applied to the gameObject while taking the snapshot. Its scale will be restored after taking the snapshot.</param> | |
/// <param name="width">The width of the snapshot image.</param> | |
/// <param name="height">The height of the snapshot image.</param> | |
/// <returns>A Texture2D containing the captured snapshot.</returns> | |
public Texture2D TakeObjectSnapshot (GameObject gameObject, Vector3 positionOffset, Quaternion rotation, Vector3 scale, int width = 128, int height = 128) | |
{ | |
return TakeObjectSnapshot(gameObject, Color.clear, positionOffset, rotation, scale, width, height); | |
} | |
/// <summary> | |
/// Takes a snapshot of an instantiated GameObject and returns it as a Texture2D. | |
/// </summary> | |
/// <param name="gameObject">The instantiated GameObject to snapshot.</param> | |
/// <param name="backgroundColor">The background color of the snapshot. Can be transparent.</param> | |
/// <param name="positionOffset">The position offset relative to the SnapshotCamera that will be applied to the gameObject while taking the snapshot. Its position will be restored after taking the snapshot.</param> | |
/// <param name="rotation">The rotation that will be applied to the gameObject while taking the snapshot. Its rotation will be restored after taking the snapshot.</param> | |
/// <param name="scale">The scale that will be applied to the gameObject while taking the snapshot. Its scale will be restored after taking the snapshot.</param> | |
/// <param name="width">The width of the snapshot image.</param> | |
/// <param name="height">The height of the snapshot image.</param> | |
/// <returns>A Texture2D containing the captured snapshot.</returns> | |
public Texture2D TakeObjectSnapshot (GameObject gameObject, Color backgroundColor, Vector3 positionOffset, Quaternion rotation, Vector3 scale, int width = 128, int height = 128) | |
{ | |
if (gameObject == null) | |
throw new ArgumentNullException("gameObject"); | |
else if (gameObject.scene.name == null) | |
throw new ArgumentException("gameObject parameter must be an instantiated GameObject! If you want to use a prefab directly, use TakePrefabSnapshot instead.", "gameObject"); | |
// Prepare the gameObject and save its current state so we can restore it later | |
GameObjectStateSnapshot previousState = PrepareObject(gameObject, positionOffset, rotation, scale); | |
// Take a snapshot | |
Texture2D snapshot = TakeSnapshot(backgroundColor, width, height); | |
// Restore the gameObject to its previous state | |
previousState.Restore(); | |
// Return the snapshot | |
return snapshot; | |
} | |
#endregion | |
#region TakePrefabSnapshot | |
/// <summary> | |
/// Takes a snapshot of a prefab and returns it as a Texture2D. | |
/// | |
/// Uses a completely transparent background and | |
/// applies the default position offset, rotation and scale to the prefab while taking the snapshot. | |
/// </summary> | |
/// <param name="prefab">The prefab to snapshot.</param> | |
/// <param name="width">The width of the snapshot image.</param> | |
/// <param name="height">The height of the snapshot image.</param> | |
/// <returns>A Texture2D containing the captured snapshot.</returns> | |
public Texture2D TakePrefabSnapshot (GameObject prefab, int width = 128, int height = 128) | |
{ | |
return TakePrefabSnapshot(prefab, Color.clear, defaultPositionOffset, Quaternion.Euler(defaultRotation), defaultScale, width, height); | |
} | |
/// <summary> | |
/// Takes a snapshot of a prefab and returns it as a Texture2D. | |
/// | |
/// Applies the default position offset, rotation and scale to the prefab while taking the snapshot. | |
/// </summary> | |
/// <param name="prefab">The prefab to snapshot.</param> | |
/// <param name="backgroundColor">The background color of the snapshot. Can be transparent.</param> | |
/// <param name="width">The width of the snapshot image.</param> | |
/// <param name="height">The height of the snapshot image.</param> | |
/// <returns>A Texture2D containing the captured snapshot.</returns> | |
public Texture2D TakePrefabSnapshot (GameObject prefab, Color backgroundColor, int width = 128, int height = 128) | |
{ | |
return TakePrefabSnapshot(prefab, backgroundColor, defaultPositionOffset, Quaternion.Euler(defaultRotation), defaultScale, width, height); | |
} | |
/// <summary> | |
/// Takes a snapshot of a prefab and returns it as a Texture2D. | |
/// | |
/// Uses a completely transparent background. | |
/// </summary> | |
/// <param name="prefab">The prefab to snapshot.</param> | |
/// <param name="positionOffset">The position offset relative to the SnapshotCamera that will be applied to the prefab while taking the snapshot.</param> | |
/// <param name="rotation">The rotation that will be applied to the prefab while taking the snapshot.</param> | |
/// <param name="scale">The scale that will be applied to the prefab while taking the snapshot.</param> | |
/// <param name="width">The width of the snapshot image.</param> | |
/// <param name="height">The height of the snapshot image.</param> | |
/// <returns>A Texture2D containing the captured snapshot.</returns> | |
public Texture2D TakePrefabSnapshot (GameObject prefab, Vector3 positionOffset, Quaternion rotation, Vector3 scale, int width = 128, int height = 128) | |
{ | |
return TakePrefabSnapshot(prefab, Color.clear, positionOffset, rotation, scale, width, height); | |
} | |
/// <summary> | |
/// Takes a snapshot of a prefab and returns it as a Texture2D. | |
/// </summary> | |
/// <param name="prefab">The prefab to snapshot.</param> | |
/// <param name="backgroundColor">The background color of the snapshot. Can be transparent.</param> | |
/// <param name="positionOffset">The position offset relative to the SnapshotCamera that will be applied to the prefab while taking the snapshot.</param> | |
/// <param name="rotation">The rotation that will be applied to the prefab while taking the snapshot.</param> | |
/// <param name="scale">The scale that will be applied to the prefab while taking the snapshot.</param> | |
/// <param name="width">The width of the snapshot image.</param> | |
/// <param name="height">The height of the snapshot image.</param> | |
/// <returns>A Texture2D containing the captured snapshot.</returns> | |
public Texture2D TakePrefabSnapshot (GameObject prefab, Color backgroundColor, Vector3 positionOffset, Quaternion rotation, Vector3 scale, int width = 128, int height = 128) | |
{ | |
if (prefab == null) | |
throw new ArgumentNullException("prefab"); | |
else if (prefab.scene.name != null) | |
throw new ArgumentException("prefab parameter must be a prefab! If you want to use an instance, use TakeObjectSnapshot instead.", "prefab"); | |
// Prepare an instance of the prefab | |
GameObject instance = PreparePrefab(prefab, positionOffset, rotation, scale); | |
// Take a snapshot | |
Texture2D snapshot = TakeSnapshot(backgroundColor, width, height); | |
// Destroy the instance we created | |
DestroyImmediate(instance); | |
// Return the snapshot | |
return snapshot; | |
} | |
#endregion | |
/// <summary> | |
/// Takes a snapshot of whatever is in front of the camera and within the camera's culling mask and returns it as a Texture2D. | |
/// </summary> | |
/// <param name="backgroundColor">The background color to apply to the camera before taking the snapshot.</param> | |
/// <param name="width">The width of the snapshot image.</param> | |
/// <param name="height">The height of the snapshot image.</param> | |
/// <returns>A Texture2D containing the captured snapshot.</returns> | |
private Texture2D TakeSnapshot(Color backgroundColor, int width, int height) | |
{ | |
// Set the background color of the camera | |
cam.backgroundColor = backgroundColor; | |
// Get a temporary render texture and render the camera | |
cam.targetTexture = RenderTexture.GetTemporary(width, height, 24); | |
cam.Render(); | |
// Activate the temporary render texture | |
RenderTexture previouslyActiveRenderTexture = RenderTexture.active; | |
RenderTexture.active = cam.targetTexture; | |
// Extract the image into a new texture without mipmaps | |
Texture2D texture = new Texture2D(cam.targetTexture.width, cam.targetTexture.height, TextureFormat.ARGB32, false); | |
texture.ReadPixels(new Rect(0, 0, cam.targetTexture.width, cam.targetTexture.height), 0, 0); | |
texture.Apply(false); | |
// Reactivate the previously active render texture | |
RenderTexture.active = previouslyActiveRenderTexture; | |
// Clean up after ourselves | |
cam.targetTexture = null; | |
RenderTexture.ReleaseTemporary(cam.targetTexture); | |
// Return the texture | |
return texture; | |
} | |
} |
haha, so many code comments
Can I use this commercially?
@davwen Sure! I forgot to add a license to the gist initially but consider it MIT licensed :)
Thanks for this ! Helped me out a lot on a school project
Hello, thanks for providing this example! Currently, if I take a picture of the prefab and set a resolution of like 1440x1080, I get a very small prefab in the center of the big picture. Is there a way to have it zoom in on the prefab?
Excellent, forked it for some customization 👯
Hey guy! How to use it?
Hi, thx for the code. For a 1x1x1 its really nice, but when i change one side to for example 5, it still takes a snapshot of a 1 x 1 x 1 cube. Somebody know why?
Hey guy! How to use it?
Any luck or hints? Quite the newbie at unity
Very simple Implementation to help others use this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SnapshotItem : MonoBehaviour
{
private SnapshotCamera snapshotCamera;
public GameObject gameObjectToSnapshot;
void Start()
{
snapshotCamera = SnapshotCamera.MakeSnapshotCamera(0);
Texture2D snapshot = snapshotCamera.TakeObjectSnapshot(gameObjectToSnapshot);
SnapshotCamera.SavePNG(snapshot);
}
}
Hey guy! How to use it?
- Unzip, copy contents in Assests folder to your project's folder "Snapshot" etc. You may need to adjust materials to use your Render Pipeline.
- Open sample scene "Testscene" inside, you can add your own prefabs.. You may need to change Input to use new Input System, like this
// SnapshotCameraTest.cs
using UnityEngine.InputSystem;
update function:
if (Keyboard.current.spaceKey.wasPressedThisFrame)
//if (Input.GetKeyUp(KeyCode.Space))
- Create a layer name it "SnapshotLayer", change Camera, the test cubes and your prefabs to this layer.
- Run scene, take snapshot of test cube by hitting Space. Snapshots are saved in Project-folder/Snapshots.
Hope this helps
well done ty
Your implementation is great but it has an issue and is that the orthographicSize variable from Camera doesn't fit the Renderer bounds.