Files
2024-11-20 15:21:28 +01:00

402 lines
14 KiB
C#

using System;
using UnityEngine;
using UnityEngine.Rendering;
#if UPPipeline
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering.Universal;
#endif
namespace Gaia
{
[ExecuteAlways]
public class GaiaPlanarReflections : MonoBehaviour
{
[Serializable]
public class PlanarReflectionSettings
{
public bool m_enableReflections = true;
public GaiaConstants.ResolutionMulltiplier m_ResolutionMultiplier = GaiaConstants.ResolutionMulltiplier.Half;
public float m_ClipPlaneOffset = 0f;
public LayerMask m_ReflectLayers = -1;
public bool m_Shadows = false;
public bool m_enableRenderDistance = false;
public bool m_enableRenderDistances = false;
public float m_customRenderDistance = 500f;
public float[] m_customRenderDistances = new float[32];
public int m_textureResolution = 512;
}
public class PlanarReflectionSettingData
{
private bool _fog;
private int _maxLod;
private float _lodBias;
public void Get()
{
_fog = RenderSettings.fog;
_maxLod = QualitySettings.maximumLODLevel;
_lodBias = QualitySettings.lodBias;
}
public void Set()
{
GL.invertCulling = true;
RenderSettings.fog = true; // disable fog for now as it's incorrect with projection
QualitySettings.maximumLODLevel = 1;
QualitySettings.lodBias = _lodBias * 0.5f;
}
public void Restore()
{
GL.invertCulling = false;
RenderSettings.fog = _fog;
QualitySettings.maximumLODLevel = _maxLod;
QualitySettings.lodBias = _lodBias;
}
}
public PlanarReflectionSettings m_settings = new PlanarReflectionSettings();
public PlanarReflectionSettingData m_reflectionData = new PlanarReflectionSettingData();
public static event Action<ScriptableRenderContext, Camera> BeginPlanarReflections;
private static Camera m_reflectionCamera;
private RenderTexture m_reflectionTexture = null;
[SerializeField]
private float[] m_distances = new float[32];
#region Unity Functions
/// <summary>
/// Load on enable
/// </summary>
private void OnEnable()
{
RenderPipelineManager.beginCameraRendering += ExecutePlanarReflections;
}
/// <summary>
/// Execute on disable
/// </summary>
private void OnDisable()
{
Cleanup();
}
/// <summary>
/// Execute when the object is destroyed
/// </summary>
private void OnDestroy()
{
Cleanup();
}
#endregion
#region Planar Reflection Functions
/// <summary>
/// Calculates the matrix of the reflection plane view
/// </summary>
/// <param name="reflectionMat"></param>
/// <param name="plane"></param>
private static void CalculateReflectionMatrix(ref Matrix4x4 reflectionMat, Vector4 plane)
{
reflectionMat.m00 = (1f - 2f * plane[0] * plane[0]);
reflectionMat.m01 = (-2f * plane[0] * plane[1]);
reflectionMat.m02 = (-2f * plane[0] * plane[2]);
reflectionMat.m03 = (-2f * plane[3] * plane[0]);
reflectionMat.m10 = (-2f * plane[1] * plane[0]);
reflectionMat.m11 = (1f - 2f * plane[1] * plane[1]);
reflectionMat.m12 = (-2f * plane[1] * plane[2]);
reflectionMat.m13 = (-2f * plane[3] * plane[1]);
reflectionMat.m20 = (-2f * plane[2] * plane[0]);
reflectionMat.m21 = (-2f * plane[2] * plane[1]);
reflectionMat.m22 = (1f - 2f * plane[2] * plane[2]);
reflectionMat.m23 = (-2f * plane[3] * plane[2]);
reflectionMat.m30 = 0f;
reflectionMat.m31 = 0f;
reflectionMat.m32 = 0f;
reflectionMat.m33 = 1f;
}
/// <summary>
/// Updates position
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
private static Vector3 ReflectPosition(Vector3 pos)
{
var newPos = new Vector3(pos.x, -pos.y, pos.z);
return newPos;
}
/// <summary>
/// Updates the camera planes based on it's current view
/// </summary>
/// <param name="cam"></param>
/// <param name="pos"></param>
/// <param name="normal"></param>
/// <param name="sideSign"></param>
/// <returns></returns>
private Vector4 CameraSpacePlane(Camera cam, Vector3 pos, Vector3 normal, float sideSign)
{
var offsetPos = pos + normal * m_settings.m_ClipPlaneOffset;
var m = cam.worldToCameraMatrix;
var cameraPosition = m.MultiplyPoint(offsetPos);
var cameraNormal = m.MultiplyVector(normal).normalized * sideSign;
return new Vector4(cameraNormal.x, cameraNormal.y, cameraNormal.z, -Vector3.Dot(cameraPosition, cameraNormal));
}
/// <summary>
/// Builds the reflection texture if null or new resolution has been set
/// </summary>
private void PlanarReflectionTexture()
{
if (m_reflectionTexture == null)
{
#if UPPipeline
var res = ReflectionResolution(m_settings.m_textureResolution, UniversalRenderPipeline.asset.renderScale);
const RenderTextureFormat hdrFormat = RenderTextureFormat.DefaultHDR;
m_reflectionTexture = RenderTexture.GetTemporary(res.x, res.y, 24, GraphicsFormatUtility.GetGraphicsFormat(hdrFormat, true));
#endif
}
else
{
if (m_reflectionTexture.width != m_settings.m_textureResolution)
{
#if UPPipeline
var res = ReflectionResolution(m_settings.m_textureResolution, UniversalRenderPipeline.asset.renderScale);
const RenderTextureFormat hdrFormat = RenderTextureFormat.DefaultHDR;
m_reflectionTexture = RenderTexture.GetTemporary(res.x, res.y, 24, GraphicsFormatUtility.GetGraphicsFormat(hdrFormat, true));
#endif
}
}
m_reflectionCamera.targetTexture = m_reflectionTexture;
}
/// <summary>
/// Gets the reflection resolution
/// </summary>
/// <param name="textureSize"></param>
/// <param name="scale"></param>
/// <returns></returns>
private Vector2Int ReflectionResolution(int textureSize, float scale)
{
var x = (int)(textureSize * scale * GetScaleValue());
var y = (int)(textureSize * scale * GetScaleValue());
return new Vector2Int(x, y);
}
/// <summary>
/// Executes the planar reflections functions
/// </summary>
/// <param name="context"></param>
/// <param name="cam"></param>
private void ExecutePlanarReflections(ScriptableRenderContext context, Camera cam)
{
if (cam.cameraType == CameraType.Reflection || cam.cameraType == CameraType.Preview)
{
return;
}
UpdateReflectionCamera(cam);
PlanarReflectionTexture();
if (m_reflectionData == null)
{
m_reflectionData = new PlanarReflectionSettingData();
}
m_reflectionData.Get();
m_reflectionData.Set();
BeginPlanarReflections?.Invoke(context, m_reflectionCamera);
#if UPPipeline
UniversalRenderPipeline.RenderSingleCamera(context, m_reflectionCamera);
#endif
Shader.SetGlobalTexture(GaiaShaderID.m_globalReflectionTexture, m_reflectionTexture);
m_reflectionData.Restore();
}
#endregion
#region Utils
/// <summary>
/// Cleans up planar reflections
/// </summary>
private void Cleanup()
{
RenderPipelineManager.beginCameraRendering -= ExecutePlanarReflections;
if(m_reflectionCamera != null)
{
m_reflectionCamera.targetTexture = null;
SafeDestroy(m_reflectionCamera.gameObject);
}
if (m_reflectionTexture != null)
{
RenderTexture.ReleaseTemporary(m_reflectionTexture);
}
}
/// <summary>
/// Creates the reflection camera
/// </summary>
/// <returns></returns>
private Camera CreateMirrorObjects()
{
var go = new GameObject("Gaia Planar Reflections Camera",typeof(Camera));
#if UPPipeline
var cameraData = go.AddComponent(typeof(UniversalAdditionalCameraData)) as UniversalAdditionalCameraData;
cameraData.requiresColorOption = CameraOverrideOption.Off;
cameraData.requiresDepthOption = CameraOverrideOption.Off;
cameraData.SetRenderer(0);
#endif
var t = transform;
var reflectionCamera = go.GetComponent<Camera>();
reflectionCamera.transform.SetPositionAndRotation(t.position, t.rotation);
reflectionCamera.depth = -10;
reflectionCamera.enabled = false;
go.hideFlags = HideFlags.HideAndDontSave;
return reflectionCamera;
}
/// <summary>
/// Destory the object
/// </summary>
/// <param name="obj"></param>
private static void SafeDestroy(GameObject obj)
{
if (Application.isEditor)
{
DestroyImmediate(obj);
}
else
{
Destroy(obj);
}
}
/// <summary>
/// Updates the reflection camera settings
/// </summary>
/// <param name="src"></param>
/// <param name="dest"></param>
private void UpdateCamera(Camera src, Camera dest)
{
if (dest == null) return;
dest.CopyFrom(src);
dest.useOcclusionCulling = true;
SetCameraBackgroundType(dest, CameraClearFlags.SolidColor);
#if UPPipeline
if (dest.gameObject.TryGetComponent(out UniversalAdditionalCameraData camData))
{
camData.SetRenderer(0);
camData.renderShadows = m_settings.m_Shadows; // turn off shadows for the reflection camera
}
#endif
}
/// <summary>
///
/// </summary>
/// <param name="camera"></param>
private void SetCameraBackgroundType(Camera camera, CameraClearFlags backgroundRenderMode)
{
if (camera == null)
{
return;
}
camera.clearFlags = backgroundRenderMode;
camera.backgroundColor = RenderSettings.fogColor;
}
/// <summary>
/// Updates the reflection camera for rendering
/// </summary>
/// <param name="realCamera"></param>
private void UpdateReflectionCamera(Camera realCamera)
{
if (m_reflectionCamera == null)
{
m_reflectionCamera = CreateMirrorObjects();
}
Vector3 pos = Vector3.zero;
Vector3 normal = Vector3.up;
UpdateCamera(realCamera, m_reflectionCamera);
var d = Vector3.Dot(normal, pos) - m_settings.m_ClipPlaneOffset;
var reflectionPlane = new Vector4(normal.x, normal.y, normal.z, d);
var reflection = Matrix4x4.identity;
reflection = Matrix4x4.Scale(new Vector3(1, 1, 1));
CalculateReflectionMatrix(ref reflection, reflectionPlane);
var oldPosition = realCamera.transform.position - new Vector3(0, pos.y, 0);
var newPosition = ReflectPosition(oldPosition);
m_reflectionCamera.transform.forward = Vector3.Scale(realCamera.transform.forward, new Vector3(1, 1, 1));
m_reflectionCamera.worldToCameraMatrix = realCamera.worldToCameraMatrix * reflection;
var clipPlane = CameraSpacePlane(m_reflectionCamera, pos - Vector3.up * 0.1f, normal, 1.0f);
var projection = realCamera.CalculateObliqueMatrix(clipPlane);
m_reflectionCamera.projectionMatrix = projection;
if (m_settings.m_enableReflections)
{
m_reflectionCamera.cullingMask = ~(1 << 4) & m_settings.m_ReflectLayers;
}
else
{
m_reflectionCamera.cullingMask = 0;
}
if (m_distances.Length != 32)
{
m_distances = new float[32];
}
if (m_settings.m_enableRenderDistance)
{
if (m_settings.m_enableRenderDistances)
{
m_reflectionCamera.layerCullDistances = m_settings.m_customRenderDistances;
m_reflectionCamera.layerCullSpherical = true;
}
else
{
for (int idx = 0; idx < m_distances.Length; idx++)
{
m_distances[idx] = m_settings.m_customRenderDistance;
}
m_reflectionCamera.layerCullDistances = m_distances;
m_reflectionCamera.layerCullSpherical = true;
}
}
else
{
for (int idx = 0; idx < m_distances.Length; idx++)
{
m_distances[idx] = 0f;
}
m_reflectionCamera.layerCullDistances = m_distances;
m_reflectionCamera.layerCullSpherical = true;
}
m_reflectionCamera.transform.position = newPosition;
}
/// <summary>
/// Gets the multiplier scale
/// </summary>
/// <returns></returns>
private float GetScaleValue()
{
switch(m_settings.m_ResolutionMultiplier)
{
case GaiaConstants.ResolutionMulltiplier.Full:
return 1f;
case GaiaConstants.ResolutionMulltiplier.Half:
return 0.5f;
case GaiaConstants.ResolutionMulltiplier.Third:
return 0.33f;
case GaiaConstants.ResolutionMulltiplier.Quarter:
return 0.25f;
default:
return 0.5f;
}
}
#endregion
}
}