253 lines
6.8 KiB
C#
253 lines
6.8 KiB
C#
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<Camera>();
|
|
_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();
|
|
}
|
|
}
|
|
}
|