Unity screenshot error: capturing the editor too - c#

I'm trying to create some screenshots but ScreenCapture.CaptureScreenshot actually captures the entire editor and not just the Game view.
public class ScreenShotTaker : MonoBehaviour
{
public KeyCode takeScreenshotKey = KeyCode.S;
public int screenshotCount = 0;
private void Update()
{
if (Input.GetKeyDown(takeScreenshotKey))
{
ScreenCapture.CaptureScreenshot("Screenshots/"
+ "_" + screenshotCount + "_"+ Screen.width + "X" + Screen.height + "" + ".png");
Debug.Log("Screenshot taken.");
}
}
}
What could be the issue? How to take a decent, game view only screenshot that includes the UI?
Note, the UI thing, I found other methods online to take a screenshot (using RenderTextures) but those didn't include the UI. In my other, "real" project I do have UI as well, I just opened this tester project to see if the screenshot issue persists here too.

This is a bug and I suggest you stay away from it for while until ScreenCapture.CaptureScreenshot is mature enough. This function was added in Unity 2017.2 beta so this is the right time to file for a bug report from the Editor. To make it worse, it saves only black and blank image on my computer.
As for taking screenshots, there other ways to do this without RenderTextures, that will also include the UI in the screenshot too.
You can read pixels from the screen with Texture2D.ReadPixels then save it with File.WriteAllBytes.
public KeyCode takeScreenshotKey = KeyCode.S;
public int screenshotCount = 0;
private void Update()
{
if (Input.GetKeyDown(takeScreenshotKey))
{
StartCoroutine(captureScreenshot());
}
}
IEnumerator captureScreenshot()
{
yield return new WaitForEndOfFrame();
string path = "Screenshots/"
+ "_" + screenshotCount + "_" + Screen.width + "X" + Screen.height + "" + ".png";
Texture2D screenImage = new Texture2D(Screen.width, Screen.height);
//Get Image from screen
screenImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0);
screenImage.Apply();
//Convert to png
byte[] imageBytes = screenImage.EncodeToPNG();
//Save image to file
System.IO.File.WriteAllBytes(path, imageBytes);
}

Related

how to speed up code that computes render-to-render optical flow and saves it as separate PNG files?

I'm running the C# code below, which computes optical flow maps and saves them as PNGs during gameplay, using Unity with a VR headset that forces an upper limit of 90 FPS. Without this code, the project runs smoothly at 90 FPS. To run this code on top of the same project consistently above 80 FPS, I had to use WaitForSeconds(0.2f) in the coroutine but the ideal scenario would be to compute and save the optical flow map for every frame of the game, or at least with a lower delay about 0.01 seconds. I'm already using AsyncGPUReadback and WriteAsync.
Main Question: How can I speed up this code further?
Side Question: Any way I can dump the calculated optical flow maps as consecutive rows in a CSV file so that it would write on a single file rather than creating a separate PNG for each map? Or would this be even slower?
using System.Collections;
using UnityEngine;
using System.IO;
using UnityEngine.Rendering;
namespace OpticalFlowAlternative
{
public class OpticalFlow : MonoBehaviour {
protected enum Pass {
Flow = 0,
DownSample = 1,
BlurH = 2,
BlurV = 3,
Visualize = 4
};
public RenderTexture Flow { get { return resultBuffer; } }
[SerializeField] protected Material flowMaterial;
protected RenderTexture prevFrame, flowBuffer, resultBuffer, renderTexture, rt;
public string customOutputFolderPath = "";
private string filepathforflow;
private int imageCount = 0;
int targetTextureWidth, targetTextureHeight;
private EyeTrackingV2 eyeTracking;
protected void Start () {
eyeTracking = GameObject.Find("XR Rig").GetComponent<EyeTrackingV2>();
targetTextureWidth = Screen.width / 16;
targetTextureHeight = Screen.height / 16;
flowMaterial.SetFloat("_Ratio", 1f * Screen.height / Screen.width);
renderTexture = new RenderTexture(targetTextureWidth, targetTextureHeight, 0);
rt = new RenderTexture(Screen.width, Screen.height, 0);
StartCoroutine("StartCapture");
}
protected void LateUpdate()
{
eyeTracking.flowCount = imageCount;
}
protected void OnDestroy ()
{
if(prevFrame != null)
{
prevFrame.Release();
prevFrame = null;
flowBuffer.Release();
flowBuffer = null;
rt.Release();
rt = null;
renderTexture.Release();
renderTexture = null;
}
}
IEnumerator StartCapture()
{
while (true)
{
yield return new WaitForSeconds(0.2f);
ScreenCapture.CaptureScreenshotIntoRenderTexture(rt);
//compensating for image flip
Graphics.Blit(rt, renderTexture, new Vector2(1, -1), new Vector2(0, 1));
if (prevFrame == null)
{
Setup(targetTextureWidth, targetTextureHeight);
Graphics.Blit(renderTexture, prevFrame);
}
flowMaterial.SetTexture("_PrevTex", prevFrame);
//calculating motion flow frame here
Graphics.Blit(renderTexture, flowBuffer, flowMaterial, (int)Pass.Flow);
Graphics.Blit(renderTexture, prevFrame);
AsyncGPUReadback.Request(flowBuffer, 0, TextureFormat.ARGB32, OnCompleteReadback);
}
}
void OnCompleteReadback(AsyncGPUReadbackRequest request)
{
if (request.hasError)
return;
var tex = new Texture2D(targetTextureWidth, targetTextureHeight, TextureFormat.ARGB32, false);
tex.LoadRawTextureData(request.GetData<uint>());
tex.Apply();
WriteTextureAsync(tex);
}
async void WriteTextureAsync(Texture2D tex)
{
imageCount++;
filepathforflow = customOutputFolderPath + imageCount + ".png";
var stream = new FileStream(filepathforflow, FileMode.OpenOrCreate);
var bytes = tex.EncodeToPNG();
await stream.WriteAsync(bytes, 0, bytes.Length);
}
protected void Setup(int width, int height)
{
prevFrame = new RenderTexture(width, height, 0);
prevFrame.format = RenderTextureFormat.ARGBFloat;
prevFrame.wrapMode = TextureWrapMode.Repeat;
prevFrame.Create();
flowBuffer = new RenderTexture(width, height, 0);
flowBuffer.format = RenderTextureFormat.ARGBFloat;
flowBuffer.wrapMode = TextureWrapMode.Repeat;
flowBuffer.Create();
}
}
}
First though here is to use CommandBuffers, with them you can perform no-copy readback of the screen, apply your calculations and store them in separate buffers (textures).
Then you can request readbacks of part of the texture/multiple textures over frames, without blocking access to currently computing texture. When readback is performed, best way is to encode it to PNG/JPG in separate thread, without blocking main thread.
Alternately to async readbacks, if you are on DX11/Desktop, it's also possible to have D3D buffer configured for fast cpu readback, and map it every frame if you want to avoid few-frames latency which happens because of using async readback.
Creating texture from buffer is another waste of performance here, since readback gives you pixel values, you can use general purpose png encoders and save it multi-threaded (while texture creation is only allowed in "main" thread)
If latencies are fine for you, but you want to have exact framenumber to image mapping, it's also possible to encode frame number into target image, so you'll always have it before saving in png.
About side-question, CSV could be faster than default PNG encoding, because PNG using zip-like compression inside, while CSV is just a bunch of numbers compiled in strings

image cropping on Unity

I've had an issue trying to crop an image in unity. I looked up various methods of tryin to accomplish this but am still pretty new to C# coding. My code is below.
My idea is to use Rectangle information as my cropping tool of the image but it only crops the lower left corner. The goal is to crop a bulk of images and save them back to the folder.
public class ImageCropper : MonoBehaviour
{
[SerializeField] Sprite oldImage;
[SerializeField] GameObject newSize;
//Select a Texture in the Inspector to change to
Texture2D sourceTexture;
RectTransform sourceRect;
private string folder = "**FILEPATH**";
// Start is called before the first frame update
void Start()
{
//Change the Texture to be the one you define in the Inspector
sourceTexture = oldImage.texture;
sourceRect = newSize.GetComponent<RectTransform>();
int x = Mathf.FloorToInt(sourceRect.position.x);
int y = Mathf.FloorToInt(sourceRect.position.y);
int width = Mathf.FloorToInt(sourceRect.rect.width);
int height = Mathf.FloorToInt(sourceRect.rect.height);
/*Debug.Log(sourceRect.position.x + " " + sourceRect.position.y + " " + sourceRect.rect.width + " " + sourceRect.rect.height);
Debug.Log(x + " " + y + " " + width + " " + height);*/
Color[] pix = sourceTexture.GetPixels();
Texture2D newImage = new Texture2D(width, height);
newImage.SetPixels(pix);
newImage.Apply();
byte[] bytes = newImage.EncodeToPNG();
File.WriteAllBytes(folder + "/soldier1.png", bytes);
}
}
Texture2D.GetPixels has an overload that lets you retrieve only a subset of the pixels.
Texture2D Crop(Texture2D sourceTexture, RectTransform cropRectTransform)
{
var cropRect = new RectInt
(
Mathf.FloorToInt(cropRectTransform.position.x),
Mathf.FloorToInt(cropRectTransform.position.y),
Mathf.FloorToInt(cropRectTransform.rect.width),
Mathf.FloorToInt(cropRectTransform.rect.height)
);
var newPixels = sourceTexture.GetPixels(cropRect.x, cropRect.y, cropRect.width, cropRect.height);
var newTexture = new Texture2D(cropRect.width, cropRect.height);
newTexture.SetPixels(newPixels);
newTexture.Apply();
return newTexture;
}

c# 3d game garbage collection freezing

I’m currently developing a 3d multiplayer game in Monogame and noticing freezing every 5 seconds and quite allot of garbage collection. I’m trying to solve the issue by finding what creates garbage for the garbage collector and remove the garbage.
The below examples are only a smart part of a very large project which is coded in the same way as the examples shown, for example the games networking initializes variables in runtime as it collects data from the server, for example this code is checking what a player is wearing. This code is sent by the server when a player changes any gear it is wearing to update other players with the changes. (this code is called only in a specific situation)
if (messageTitle == "Equipment")
{
string who = msg.ReadString();
//get the item names
string helmet = msg.ReadString();
string shoulder = msg.ReadString();
string chest = msg.ReadString();
string shirt = msg.ReadString();
string cape = msg.ReadString();
string bracers = msg.ReadString();
string gloves = msg.ReadString();
string belt = msg.ReadString();
string pants = msg.ReadString();
string boots = msg.ReadString();
string slot1 = msg.ReadString();
string slot2 = msg.ReadString();
string slot3 = msg.ReadString();
string slot4 = msg.ReadString();
if (unitDatabase.realUnits.ContainsKey(who) == true)
{
CheckEquipment(who + "_Equipment_Helmet", helmet);
CheckEquipment(who + "_Equipment_Shoulder", shoulder);
CheckEquipment(who + "_Equipment_Chest", chest);
CheckEquipment(who + "_Equipment_Shirt", shirt);
CheckEquipment(who + "_Equipment_Cape", cape);
CheckEquipment(who + "_Equipment_Bracers", bracers);
CheckEquipment(who + "_Equipment_Gloves", gloves);
CheckEquipment(who + "_Equipment_Belt", belt);
CheckEquipment(who + "_Equipment_Pants", pants);
CheckEquipment(who + "_Equipment_Boots", boots);
CheckEquipment(who + "_Equipment_slot1", slot1);
CheckEquipment(who + "_Equipment_slot2", slot2);
CheckEquipment(who + "_Equipment_slot3", slot3);
CheckEquipment(who + "_Equipment_slot4", slot4);
}
}
But the code below is always sent by the server unreliably so mostly every frame it will be initializing these variables.
if (messageTitle == "Stats")
{
string who = msg.ReadString();
int health = msg.ReadInt32();
int mana = msg.ReadInt32();
int energy = msg.ReadInt32();
int rage = msg.ReadInt32();
bool inCombat = msg.ReadBoolean();
int experience = msg.ReadInt32();
if (unitDatabase.realUnits.ContainsKey(who) == true)
{
unitDatabase.realUnits[who].attributes.health = health;
unitDatabase.realUnits[who].attributes.mana = mana;
unitDatabase.realUnits[who].attributes.energy = energy;
unitDatabase.realUnits[who].attributes.rage = rage;
unitDatabase.realUnits[who].attributes.inCombat = inCombat;
if (unitDatabase.realUnits[who].attributes.experience != experience)
{
int difference = experience - unitDatabase.realUnits[who].attributes.experience;
unitDatabase.realUnits[who].attributes.experience = experience;
floatingTextDatabase.AddFloatingText("XP: " + difference, unitDatabase.realUnits[who], 0.35f, new Vector3(0, 0, 20), Color.Blue);
}
}
}
Everything received from the server is setup to receive data in this way, I believe the initializing in runtime all these variables would be creating allot of memory for the garbage collector which may be causing the 1 second freezing every 5seconds.
public void Draw(SpriteBatch spriteBatch, SpriteFont font)
{
#region DrawAllScreenFloatingText
for (int i = 0; i < floatingText.Count(); i++)
{
string message = floatingText[i].text.ToString();
Vector2 origin = font.MeasureString(message) / 2;
float textSize = floatingText[i].size;
Color backdrop = new Color((byte)50, (byte)0, (byte)0, (byte)MathHelper.Clamp(floatingText[i].fade, 0, 255));
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position) + new Vector2(-4 * textSize, -4 * textSize), backdrop, 0, origin, textSize, 0, 1);
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position) + new Vector2(-4 * textSize, 4 * textSize), backdrop, 0, origin, textSize, 0, 1);
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position) + new Vector2(4 * textSize, -4 * textSize), backdrop, 0, origin, textSize, 0, 1);
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position) + new Vector2(4 * textSize, 4 * textSize), backdrop, 0, origin, textSize, 0, 1);
spriteBatch.DrawString(font, message, (floatingText[i].startPositon + floatingText[i].position), new Color(floatingText[i].color.R, floatingText[i].color.G, floatingText[i].color.B, (byte)MathHelper.Clamp(floatingText[i].fade, 0, 255)), 0, origin, textSize, 0, 1);
}
#endregion
}
The above code is drawing floating text on the screen, like damage being done to an enemy. Allot of the drawing would be handled like this and would be drawn every frame if there is damage being done to something. As you can see I would be initializing a string, vector2, float and color variable each draw frame multiplied by each damage number shown.
public void Draw(GraphicsDevice graphics, BasicEffect basicEffect, Effect modelEffect, MeshRenderer meshRenderer, ItemDatabase itemDatabase, ThirdPersonCamera camera, Weather weather, LightDatabase lightDatabase, AudioList audioList)
{
sw = new Stopwatch();
sw.Start();
if (camera.target != null)
{
foreach (var unit in realUnits)
{
//only draw close enough to us
double distance = Math.Sqrt((unit.Value.body.position.X - camera.target.body.position.X) * (unit.Value.body.position.X - camera.target.body.position.X) +
(unit.Value.body.position.Y - camera.target.body.position.Y) * (unit.Value.body.position.Y - camera.target.body.position.Y) +
(unit.Value.body.position.Z - camera.target.body.position.Z) * (unit.Value.body.position.Z - camera.target.body.position.Z));
if (distance < unit.Value.renderDistance)
{
//only draw units inside our view
if (camera.InsideCamera(unit.Value.body.position) == true || camera.target == unit.Value)
{
unit.Value.Draw(graphics, unitList[unit.Value.name], arrow, basicEffect, meshRenderer, itemDatabase, camera, weather, lightDatabase);
}
}
}
}
sw.Stop();
if (drawMs < sw.Elapsed.TotalMilliseconds) drawMs = sw.Elapsed.TotalMilliseconds;
}
Another code being updated every draw frame, it checks if the player being drawn is within view distance of the camera and then within the view frustum of the camera. You can see it is initializing a double every draw frame.
and inside the unit.Value.Draw(); function its initializing:
new SamplerState //for the shadow casting
Matrix[] bones
Matrix getWorld //the players world transform into shader
Matrix worldMatrix
float colorR //ambient sky color
float colorG //ambient sky color
float colorB //ambient sky color
int MAXLIGHTS //total light sources in scene
Vector3[] PointLightPosition = new Vector3[MAXLIGHTS];
Vector4[] PointLightColor = new Vector4[MAXLIGHTS];
float[] PointLightPower = new float[MAXLIGHTS];
float[] PointLightRadius = new float[MAXLIGHTS];
These would be initialized inside the draw call every draw frame if this player is within view distance of the player and view frustum of the camera.
I believe all these initialized variables in runtime every frame would be creating allot of garbage collection. Before I rework the whole game to eliminate calling new variables each frame I wanted to make sure this could be the reason for the garbage collection filling up and freezing the game every 5seconds.
Thank you for taking the time to read my question.
Edit: adding images of VS2019 profiler
So I figured I'll make a quick project testing how the garbage collector is working. Below I added a project that updates the below code every draw call (exactly what I'm currently doing)
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
string[] test = new string[100000];
// TODO: Add your drawing code here
base.Draw(gameTime);
}
This is what the VS2019 profiler recorded
The next example I initialize string[] test at the start of the program only
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
string[] test = new string[100000];
}
This is what the VS2019 profiler recorded
So from this example it looks like I never want to initialize variables during runtime and try to initialize all variables at the start of the program. I will need to re-use the same variables to avoid the garbage collector freezing the game. Thankyou everyone who replied.
Just wanted to update everyone the freezing was actually not due to garbage collection, It was actually because of this line of code.
if (pingDelay > 3)
{
ping = Int32.Parse(pingSender.Send(client.ServerConnection.RemoteEndPoint.Address, 120, Encoding.ASCII.GetBytes(“0”), options).RoundtripTime.ToString());
pingDelay = 0;
}
pingSender.Send() is supposed to be Async. Because it wasn’t it was freezing the game while it waited for the server to return the result.

Unity EncodeToPNG saving at 512x512, can't change dimension

I can't seem to get EncodeToPNG() to save to a file dimension other than 512 x 512 even though my texture is 1280 x 1024 which I'm pulling from the dimensions of my RenderTexture object 'tex'. What am I missing? Thank you!
// Saves texture as PNG file.
using UnityEngine;
using System.Collections;
using System.IO;
public class SaveTexture : MonoBehaviour {
public RenderTexture tex;
int tWidth, tHeight;
int getTextureWidth(int texWidth)
{
return tex.width;
}
int getTextureHeight(int texHeight)
{
return tex.height;
}
public void Start()
{
tWidth = getTextureWidth(tex.width);
tHeight = getTextureHeight(tex.height);
Debug.Log("Texture Width: " + tWidth + ", Texture Height: " + tHeight);
}
Texture2D toTexture2D(RenderTexture rTex)
{
Texture2D tex = new Texture2D(tWidth, tHeight, TextureFormat.ARGB32, false);
RenderTexture.active = rTex;
tex.ReadPixels(new Rect(0, 0, tWidth, tHeight), 0, 0);
tex.Apply();
return tex;
}
// Save Texture as PNG
public void SaveTexturePNG()
{
Texture2D myTexture = tex.toTexture2D();
// Encode texture into PNG
byte[] bytes = myTexture.EncodeToPNG();
Object.Destroy(myTexture);
// For testing purposes, also write to a file in the project folder
File.WriteAllBytes(Application.dataPath + "/../AnimalTexture/AnimalTexture.png", bytes);
}
}
I challenged this problem too. Actually there is no problem, file already saved with the resolution you define. On created png's inspector, there is an option named "Advanced->Non-Power of 2". It is selected "ToNearest" as default. Change it to "None" and it will be fixed.

Unity, Save cubemap to one circle image

I have cubemap. I need to save it in a circular image, for example in PNG. Many hours of searching on the Internet in what I have failed. How I do it? Is that possible?
I have image: joxi.ru/zANd66wSl6Kdkm
I need to save in png: joxi.ru/12MW55wT40LYjr Part code, which help you:
tex.SetPixels(cubemap.GetPixels(CubemapFace.PositiveZ));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Application.dataPath + "/" + cubemap.name +"_PositiveZ.png", bytes);
You can create a class that inherits ScriptableWizard class that will render a cubemap from a specific transform. Here is my code:
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class RenderCubemapWizard : ScriptableWizard
{
public Transform renderFromPosition;
public Cubemap cubemap;
void OnWizardUpdate()
{
string helpString = "Select transform to render from and cubemap to render into";
bool isValid = (renderFromPosition != null) && (cubemap != null);
}
void OnWizardCreate()
{
// create temporary camera for rendering
GameObject go = new GameObject("CubemapCamera");
go.AddComponent<Camera>();
// place it on the object
go.transform.position = renderFromPosition.position;
go.transform.rotation = Quaternion.identity;
// render into cubemap
go.GetComponent<Camera>().RenderToCubemap(cubemap);
// destroy temporary camera
DestroyImmediate(go);
ConvertToPng();
}
[MenuItem("GameObject/Render into Cubemap")]
static void RenderCubemap()
{
ScriptableWizard.DisplayWizard<RenderCubemapWizard>(
"Render cubemap", "Render!");
}
void ConvertToPng()
{
Debug.Log(Application.dataPath + "/" +cubemap.name +"_PositiveX.png");
var tex = new Texture2D (cubemap.width, cubemap.height, TextureFormat.RGB24, false);
// Read screen contents into the texture
tex.SetPixels(cubemap.GetPixels(CubemapFace.PositiveX));
// Encode texture into PNG
var bytes = tex.EncodeToPNG();
File.WriteAllBytes(Application.dataPath + "/" + cubemap.name +"_PositiveX.png", bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.NegativeX));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Application.dataPath + "/" + cubemap.name +"_NegativeX.png", bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.PositiveY));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Application.dataPath + "/" + cubemap.name +"_PositiveY.png", bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.NegativeY));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Application.dataPath + "/" + cubemap.name +"_NegativeY.png", bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.PositiveZ));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Application.dataPath + "/" + cubemap.name +"_PositiveZ.png", bytes);
tex.SetPixels(cubemap.GetPixels(CubemapFace.NegativeZ));
bytes = tex.EncodeToPNG();
File.WriteAllBytes(Application.dataPath + "/" + cubemap.name +"_NegativeZ.png", bytes);
DestroyImmediate(tex);
}
}
This basically creates a new cubemap from the given position that you specify from within the wizard (to use the wizard go to GameObject in the top menu and at the bottom of the list you'll see 'Render into Cubemap'). It will then grab the six positions of the cubemap and convert it into a PNG file from with in the ConvertToPng() function. This works for me and it should work for you since it essentially only needs a transform position.
Sorry for how long it is tried to simplify it but this as simplified as I could make it.
Here are the links that helped me come to this conclusion:
How to convert a face to png
Unity's scriptable wizard for rendering a cubemap
This is the correct approach that allows for a single compressed cubemap texture. After .png texture is saved, just set its settings to cube & the compression settings you want.
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.IO;
public class RenderCubemapUtil : ScriptableWizard
{
public Transform renderFromPosition;
public int size = 512;
public string newCubmapPath;
void OnWizardUpdate()
{
isValid = renderFromPosition != null && size >= 16 && !string.IsNullOrEmpty(newCubmapPath);
}
void OnWizardCreate()
{
if (!isValid) return;
// create temporary camera for rendering
var go = new GameObject("CubemapCamera");
go.AddComponent<Camera>();
try
{
// place it on the object
go.transform.position = renderFromPosition.position;
go.transform.rotation = Quaternion.identity;
// create new texture
var cubemap = new Cubemap(size, TextureFormat.RGB24, false);
// render into cubemap
go.GetComponent<Camera>().RenderToCubemap(cubemap);
// convert cubemap to single horizontal texture
var texture = new Texture2D(size * 6, size, cubemap.format, false);
int texturePixelCount = (size * 6) * size;
var texturePixels = new Color[texturePixelCount];
var cubeFacePixels = cubemap.GetPixels(CubemapFace.PositiveX);
CopyTextureIntoCubemapRegion(cubeFacePixels, texturePixels, size * 0);
cubeFacePixels = cubemap.GetPixels(CubemapFace.NegativeX);
CopyTextureIntoCubemapRegion(cubeFacePixels, texturePixels, size * 1);
cubeFacePixels = cubemap.GetPixels(CubemapFace.PositiveY);
CopyTextureIntoCubemapRegion(cubeFacePixels, texturePixels, size * 3);
cubeFacePixels = cubemap.GetPixels(CubemapFace.NegativeY);
CopyTextureIntoCubemapRegion(cubeFacePixels, texturePixels, size * 2);
cubeFacePixels = cubemap.GetPixels(CubemapFace.PositiveZ);
CopyTextureIntoCubemapRegion(cubeFacePixels, texturePixels, size * 4);
cubeFacePixels = cubemap.GetPixels(CubemapFace.NegativeZ);
CopyTextureIntoCubemapRegion(cubeFacePixels, texturePixels, size * 5);
texture.SetPixels(texturePixels, 0);
// write texture as png to disk
var textureData = texture.EncodeToPNG();
File.WriteAllBytes(Path.Combine(Application.dataPath, $"{newCubmapPath}.png"), textureData);
// save to disk
AssetDatabase.SaveAssetIfDirty(cubemap);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
finally
{
// destroy temporary camera
DestroyImmediate(go);
}
}
private void CopyTextureIntoCubemapRegion(Color[] srcPixels, Color[] dstPixels, int xOffsetDst)
{
int cubemapWidth = size * 6;
for (int y = 0; y != size; ++y)
{
for (int x = 0; x != size; ++x)
{
int iSrc = x + (y * size);
int iDst = (x + xOffsetDst) + (y * cubemapWidth);
dstPixels[iDst] = srcPixels[iSrc];
}
}
}
[MenuItem("GameObject/Render into Cubemap")]
static void RenderCubemap()
{
DisplayWizard<RenderCubemapUtil>("Render cubemap", "Render!");
}
}
#endif

Categories