using System; using System.IO; using UnityEngine; public class LilyRender360 : MonoBehaviour { public enum Format { PNG = 0, EXR = 1 } public enum CubeFace { PX = 0, NX = 1, PY = 2, NY = 3, PZ = 4, NZ = 5 } public int targetFramerate = 30; public Format format; public string prefix = "Recordings/"; public int nDigits = 4; public bool overwriteFile; public int width = 1024; public bool enableHeight; public int height = 1024; public int startFrame; public bool enableEndFrame; public int endFrame = -1; public float horizontalFov = 360f; public float verticalFov = 180f; public float overlap = 0.5f; public Transform stitchingOrientation; public bool showStitchLines; public bool enableCubeFaceSize; public int cubeFaceSize = 512; public bool doubleRender; public bool smoothStitching = true; private Camera _cam; private Texture2D _tex; private int _frame; private Material _equirectMat; private RenderTexture[] _faces; private RenderTexture _equirect; public string AbsolutePrefix { get { string text = Path.GetDirectoryName(Application.dataPath).Replace(Path.DirectorySeparatorChar, '/') + "/"; if (!Path.IsPathRooted(prefix)) { return text + prefix; } return prefix; } } public int MaxFrame => (int)Mathf.Min((enableEndFrame && endFrame > -1) ? ((float)endFrame) : float.PositiveInfinity, Mathf.Pow(10f, nDigits) - 1f); public float FullWidth => Mathf.Min((float)(width * 360) / horizontalFov, 8 * width); public float SuggestedHeight => FullWidth / 2f * verticalFov / 180f; public string AbsoluteFramePath(int frame) { return string.Format("{0}{1:D" + nDigits + "}.{2}", AbsolutePrefix, frame, (format == Format.EXR) ? "exr" : "png"); } public void ChechParameters() { if (!enableHeight) { height = (int)SuggestedHeight; } if (!enableCubeFaceSize) { cubeFaceSize = (int)(FullWidth / 4f * (1f + overlap * 2f)); } } private void InitCubemap() { int num = (doubleRender ? 12 : 6); _faces = new RenderTexture[num]; for (int i = 0; i < num; i++) { _faces[i] = new RenderTexture(cubeFaceSize, cubeFaceSize, 24, (format != Format.PNG) ? RenderTextureFormat.ARGBFloat : RenderTextureFormat.ARGB32); } _equirect = new RenderTexture(width, height, 24, (format != Format.PNG) ? RenderTextureFormat.ARGBFloat : RenderTextureFormat.ARGB32); } private RenderTexture Face(CubeFace face, int cube = 0) { return _faces[(int)(face + cube * 6)]; } private void RenderCubemap(int cube = 0) { float fieldOfView = _cam.fieldOfView; Quaternion rotation = _cam.transform.rotation; RenderTexture targetTexture = _cam.targetTexture; if (stitchingOrientation != null) { _cam.transform.rotation = stitchingOrientation.rotation; } if (cube == 1) { _cam.transform.Rotate(-45f, 45f, 0f); } _cam.fieldOfView = 2f * Mathf.Atan(1f + overlap) / MathF.PI * 180f; _cam.targetTexture = Face(CubeFace.PX, cube); _cam.transform.Rotate(0f, 90f, 0f); _cam.Render(); _cam.targetTexture = Face(CubeFace.NZ, cube); _cam.transform.Rotate(0f, 90f, 0f); _cam.Render(); _cam.targetTexture = Face(CubeFace.NX, cube); _cam.transform.Rotate(0f, 90f, 0f); _cam.Render(); _cam.targetTexture = Face(CubeFace.PZ, cube); _cam.transform.Rotate(0f, 90f, 0f); _cam.Render(); _cam.targetTexture = Face(CubeFace.PY, cube); _cam.transform.Rotate(90f, 0f, 0f); _cam.Render(); _cam.targetTexture = Face(CubeFace.NY, cube); _cam.transform.Rotate(180f, 0f, 0f); _cam.Render(); _cam.fieldOfView = fieldOfView; _cam.transform.rotation = rotation; _cam.targetTexture = targetTexture; } private void ConvertToEquirect() { Matrix4x4 matrix4x = Matrix4x4.identity; if (stitchingOrientation != null) { Vector3 eulerAngles = (_cam.transform.worldToLocalMatrix * stitchingOrientation.localToWorldMatrix).rotation.eulerAngles; matrix4x = Matrix4x4.Rotate(Quaternion.identity * Quaternion.AngleAxis(eulerAngles.z, Vector3.forward) * Quaternion.AngleAxis(eulerAngles.x, Vector3.right) * Quaternion.AngleAxis(0f - eulerAngles.y, Vector3.up)); } _equirectMat.SetTexture("_FaceTexPX", Face(CubeFace.PX)); _equirectMat.SetTexture("_FaceTexNX", Face(CubeFace.NX)); _equirectMat.SetTexture("_FaceTexPY", Face(CubeFace.PY)); _equirectMat.SetTexture("_FaceTexNY", Face(CubeFace.NY)); _equirectMat.SetTexture("_FaceTexPZ", Face(CubeFace.PZ)); _equirectMat.SetTexture("_FaceTexNZ", Face(CubeFace.NZ)); _equirectMat.SetMatrix("_OrientMatrix", matrix4x); _equirectMat.SetFloat("_Beta", 1f / (1f + overlap)); _equirectMat.SetFloat("_HorizontalFov", horizontalFov * (MathF.PI / 180f)); _equirectMat.SetFloat("_VerticalFov", verticalFov * (MathF.PI / 180f)); if (doubleRender) { _equirectMat.SetTexture("_FaceTexPX2", Face(CubeFace.PX, 1)); _equirectMat.SetTexture("_FaceTexNX2", Face(CubeFace.NX, 1)); _equirectMat.SetTexture("_FaceTexPY2", Face(CubeFace.PY, 1)); _equirectMat.SetTexture("_FaceTexNY2", Face(CubeFace.NY, 1)); _equirectMat.SetTexture("_FaceTexPZ2", Face(CubeFace.PZ, 1)); _equirectMat.SetTexture("_FaceTexNZ2", Face(CubeFace.NZ, 1)); Matrix4x4 inverse = Matrix4x4.Rotate(Quaternion.Euler(45f, 45f, 0f)).inverse; _equirectMat.SetMatrix("_OrientMatrix2", inverse * matrix4x); } Graphics.Blit(null, _equirect, _equirectMat); } private void Start() { ChechParameters(); InitCubemap(); _cam = GetComponent(); _tex = new Texture2D(_equirect.width, _equirect.height, (format == Format.EXR) ? TextureFormat.RGBAFloat : TextureFormat.RGB24, mipChain: false); Time.maximumDeltaTime = 1f / (float)targetFramerate; Time.captureFramerate = targetFramerate; _equirectMat = new Material(Shader.Find("Hidden/LilyRender/Equirectangular")); _equirectMat.EnableKeyword("ORIENT_CUBE"); if (showStitchLines) { _equirectMat.EnableKeyword("SHOW_STITCH_LINES"); } if (doubleRender) { _equirectMat.EnableKeyword("TWO_CUBES"); } if (smoothStitching && overlap > 0f) { _equirectMat.EnableKeyword("SMOOTH_STITCHING"); } Directory.CreateDirectory(Path.GetDirectoryName(AbsoluteFramePath(0))); } private void LateUpdate() { ChechParameters(); if (_frame >= startFrame) { string text = AbsoluteFramePath(_frame); if (File.Exists(text) && !overwriteFile) { Debug.LogWarning("File '" + text + "' already exists. Skipping frame (check 'Override' to force overriding existing files)."); } else { RenderCubemap(); if (doubleRender) { RenderCubemap(1); } ConvertToEquirect(); RenderTexture.active = _equirect; _tex.ReadPixels(new Rect(0f, 0f, _equirect.width, _equirect.height), 0, 0); byte[] bytes = ((format == Format.EXR) ? _tex.EncodeToEXR(Texture2D.EXRFlags.CompressZIP) : _tex.EncodeToPNG()); RenderTexture.active = null; File.WriteAllBytes(text, bytes); } } _frame++; if (_frame > MaxFrame) { Application.Quit(); } } }