summaryrefslogtreecommitdiff
path: root/DeepSky.Haze/DS_HazeLightVolume.cs
diff options
context:
space:
mode:
Diffstat (limited to 'DeepSky.Haze/DS_HazeLightVolume.cs')
-rw-r--r--DeepSky.Haze/DS_HazeLightVolume.cs604
1 files changed, 604 insertions, 0 deletions
diff --git a/DeepSky.Haze/DS_HazeLightVolume.cs b/DeepSky.Haze/DS_HazeLightVolume.cs
new file mode 100644
index 0000000..1a9928e
--- /dev/null
+++ b/DeepSky.Haze/DS_HazeLightVolume.cs
@@ -0,0 +1,604 @@
+using System;
+using UnityEngine;
+using UnityEngine.Rendering;
+
+namespace DeepSky.Haze;
+
+[ExecuteInEditMode]
+[RequireComponent(typeof(Light))]
+[AddComponentMenu("DeepSky Haze/Light Volume", 2)]
+public class DS_HazeLightVolume : MonoBehaviour
+{
+ private static int kConeSubdivisions = 16;
+
+ private static Shader kLightVolumeShader;
+
+ private Light m_Light;
+
+ private Mesh m_ProxyMesh;
+
+ private Matrix4x4 m_LightVolumeTransform;
+
+ private CommandBuffer m_RenderCmd;
+
+ private Material m_VolumeMaterial;
+
+ private Vector3 m_DensityOffset = Vector3.zero;
+
+ [SerializeField]
+ private DS_SamplingQuality m_Samples = DS_SamplingQuality.x16;
+
+ [SerializeField]
+ private DS_LightFalloff m_Falloff;
+
+ [SerializeField]
+ private bool m_UseFog;
+
+ [SerializeField]
+ [Range(0f, 100f)]
+ private float m_Scattering = 1f;
+
+ [SerializeField]
+ [Range(0f, 1f)]
+ private float m_SecondaryScattering = 0.1f;
+
+ [SerializeField]
+ [Range(-1f, 1f)]
+ private float m_ScatteringDirection = 0.75f;
+
+ [SerializeField]
+ private Texture3D m_DensityTexture;
+
+ [SerializeField]
+ [Range(0.1f, 10f)]
+ private float m_DensityTextureScale = 1f;
+
+ [SerializeField]
+ [Range(0.1f, 3f)]
+ private float m_DensityTextureContrast = 1f;
+
+ [SerializeField]
+ private Vector3 m_AnimateDirection = Vector3.zero;
+
+ [SerializeField]
+ [Range(0f, 10f)]
+ private float m_AnimateSpeed = 1f;
+
+ [SerializeField]
+ private float m_StartFade = 25f;
+
+ [SerializeField]
+ private float m_EndFade = 30f;
+
+ [SerializeField]
+ [Range(0.01f, 1f)]
+ private float m_FarClip = 1f;
+
+ private LightType m_PreviousLightType = LightType.Point;
+
+ private float m_PreviousAngle = 45f;
+
+ private LightShadows m_PreviousShadowMode;
+
+ public Light LightSource => m_Light;
+
+ public LightType Type => (!(m_Light != null)) ? LightType.Point : m_Light.type;
+
+ public bool CastShadows => (m_Light.shadows != 0) ? true : false;
+
+ public CommandBuffer RenderCommandBuffer => m_RenderCmd;
+
+ public DS_SamplingQuality Samples
+ {
+ get
+ {
+ return m_Samples;
+ }
+ set
+ {
+ m_Samples = value;
+ }
+ }
+
+ public DS_LightFalloff Falloff
+ {
+ get
+ {
+ return m_Falloff;
+ }
+ set
+ {
+ m_Falloff = value;
+ }
+ }
+
+ public bool UseFog
+ {
+ get
+ {
+ return m_UseFog;
+ }
+ set
+ {
+ m_UseFog = value;
+ }
+ }
+
+ public float Scattering
+ {
+ get
+ {
+ return m_Scattering;
+ }
+ set
+ {
+ m_Scattering = Mathf.Clamp01(value);
+ }
+ }
+
+ public float ScatteringDirection
+ {
+ get
+ {
+ return m_ScatteringDirection;
+ }
+ set
+ {
+ m_ScatteringDirection = Mathf.Clamp(value, -1f, 1f);
+ }
+ }
+
+ public Texture3D DensityTexture
+ {
+ get
+ {
+ return m_DensityTexture;
+ }
+ set
+ {
+ m_DensityTexture = value;
+ }
+ }
+
+ public float DensityTextureScale
+ {
+ get
+ {
+ return m_DensityTextureScale;
+ }
+ set
+ {
+ m_DensityTextureScale = Mathf.Clamp01(m_DensityTextureScale);
+ }
+ }
+
+ public Vector3 AnimateDirection
+ {
+ get
+ {
+ return m_AnimateDirection;
+ }
+ set
+ {
+ m_AnimateDirection = value.normalized;
+ }
+ }
+
+ public float AnimateSpeed
+ {
+ get
+ {
+ return m_AnimateSpeed;
+ }
+ set
+ {
+ m_AnimateSpeed = Mathf.Clamp01(value);
+ }
+ }
+
+ public float StartFade
+ {
+ get
+ {
+ return m_StartFade;
+ }
+ set
+ {
+ m_StartFade = ((!(value > 0f)) ? 1f : value);
+ }
+ }
+
+ public float EndFade
+ {
+ get
+ {
+ return m_EndFade;
+ }
+ set
+ {
+ m_EndFade = ((!(value > m_StartFade)) ? (m_StartFade + 1f) : value);
+ }
+ }
+
+ private void CreateProxyMeshCone(Mesh proxyMesh)
+ {
+ Vector3[] array = null;
+ int[] array2 = null;
+ float num = Mathf.Tan(m_Light.spotAngle / 2f * ((float)Math.PI / 180f)) * m_FarClip;
+ array = new Vector3[kConeSubdivisions + 2];
+ array2 = new int[kConeSubdivisions * 6];
+ float num2 = (float)Math.PI * 2f / (float)kConeSubdivisions;
+ float num3 = 0f;
+ for (int i = 0; i < kConeSubdivisions; i++)
+ {
+ ref Vector3 reference = ref array[i];
+ reference = new Vector3(Mathf.Sin(num3) * num, Mathf.Cos(num3) * num, m_FarClip);
+ num3 += num2;
+ }
+ ref Vector3 reference2 = ref array[kConeSubdivisions];
+ reference2 = new Vector3(0f, 0f, m_FarClip);
+ ref Vector3 reference3 = ref array[kConeSubdivisions + 1];
+ reference3 = new Vector3(0f, 0f, -0.1f);
+ for (int j = 0; j < kConeSubdivisions; j++)
+ {
+ array2[j * 3] = kConeSubdivisions;
+ array2[j * 3 + 1] = ((j != kConeSubdivisions - 1) ? (j + 1) : 0);
+ array2[j * 3 + 2] = j;
+ array2[kConeSubdivisions * 3 + j * 3] = j;
+ array2[kConeSubdivisions * 3 + j * 3 + 1] = ((j != kConeSubdivisions - 1) ? (j + 1) : 0);
+ array2[kConeSubdivisions * 3 + j * 3 + 2] = kConeSubdivisions + 1;
+ }
+ proxyMesh.vertices = array;
+ proxyMesh.triangles = array2;
+ proxyMesh.hideFlags = HideFlags.HideAndDontSave;
+ m_PreviousAngle = m_Light.spotAngle;
+ }
+
+ public bool ProxyMeshRequiresRebuild()
+ {
+ if (m_Light == null)
+ {
+ return false;
+ }
+ if (m_ProxyMesh == null || (m_Light.type == LightType.Spot && m_Light.spotAngle != m_PreviousAngle))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ public bool LightTypeChanged()
+ {
+ if (m_Light == null)
+ {
+ return false;
+ }
+ return m_Light.type != m_PreviousLightType;
+ }
+
+ public void UpdateLightType()
+ {
+ m_VolumeMaterial.DisableKeyword("POINT_COOKIE");
+ m_VolumeMaterial.DisableKeyword("SPOT_COOKIE");
+ if (m_Light.type == LightType.Point)
+ {
+ m_VolumeMaterial.EnableKeyword("POINT");
+ m_VolumeMaterial.DisableKeyword("SPOT");
+ }
+ else
+ {
+ if (m_Light.type != 0)
+ {
+ Debug.LogError("DeepSky::DS_HazeLightVolume: Unsupported light type! " + base.gameObject.name + " will not render volumetrics.");
+ base.enabled = false;
+ return;
+ }
+ m_VolumeMaterial.EnableKeyword("SPOT");
+ m_VolumeMaterial.DisableKeyword("POINT");
+ }
+ RebuildProxyMesh();
+ m_PreviousLightType = m_Light.type;
+ }
+
+ public void RebuildProxyMesh()
+ {
+ switch (m_Light.type)
+ {
+ case LightType.Point:
+ if (m_PreviousLightType != LightType.Point)
+ {
+ UnityEngine.Object.DestroyImmediate(m_ProxyMesh);
+ }
+ m_ProxyMesh = Resources.Load<Mesh>("DS_HazeMeshProxySphere");
+ break;
+ case LightType.Spot:
+ if (m_PreviousLightType == LightType.Point)
+ {
+ m_ProxyMesh = new Mesh();
+ }
+ else if (m_ProxyMesh != null)
+ {
+ m_ProxyMesh.Clear();
+ }
+ CreateProxyMeshCone(m_ProxyMesh);
+ break;
+ default:
+ Debug.LogError("DeepSky::DS_HazeLightVolume: Unsupported light type! " + base.gameObject.name + " will not render volumetrics.");
+ base.enabled = false;
+ break;
+ }
+ }
+
+ public bool ShadowModeChanged()
+ {
+ if (m_Light == null)
+ {
+ return false;
+ }
+ return m_Light.shadows != m_PreviousShadowMode;
+ }
+
+ public void UpdateShadowMode()
+ {
+ if (m_Light.shadows == LightShadows.None)
+ {
+ m_VolumeMaterial.DisableKeyword("SHADOWS_DEPTH");
+ m_VolumeMaterial.DisableKeyword("SHADOWS_CUBE");
+ }
+ else if (m_Light.type == LightType.Point)
+ {
+ m_VolumeMaterial.EnableKeyword("SHADOWS_CUBE");
+ m_VolumeMaterial.DisableKeyword("SHADOWS_DEPTH");
+ }
+ else if (m_Light.type == LightType.Spot)
+ {
+ m_VolumeMaterial.EnableKeyword("SHADOWS_DEPTH");
+ m_VolumeMaterial.DisableKeyword("SHADOWS_CUBE");
+ }
+ m_PreviousShadowMode = m_Light.shadows;
+ }
+
+ public void Register()
+ {
+ DS_HazeCore instance = DS_HazeCore.Instance;
+ if (instance == null)
+ {
+ Debug.LogError("DeepSky::DS_HazeLightVolume: Attempting to add a light volume but no HS_HazeCore found in scene! Please make sure there is a DS_HazeCore object.");
+ }
+ else
+ {
+ instance.AddLightVolume(this);
+ }
+ }
+
+ public void Deregister()
+ {
+ DS_HazeCore instance = DS_HazeCore.Instance;
+ if (instance != null)
+ {
+ instance.RemoveLightVolume(this);
+ }
+ }
+
+ public bool WillRender(Vector3 cameraPos)
+ {
+ return base.isActiveAndEnabled & (Vector3.Distance(cameraPos, base.transform.position) < m_EndFade);
+ }
+
+ private void Update()
+ {
+ m_DensityOffset -= m_AnimateDirection * m_AnimateSpeed * Time.deltaTime * 0.1f;
+ }
+
+ private void OnEnable()
+ {
+ m_Light = GetComponent<Light>();
+ if (m_Light == null)
+ {
+ Debug.LogError("DeepSky::DS_HazeLightVolume: No Light component found on " + base.gameObject.name);
+ base.enabled = false;
+ }
+ if (kLightVolumeShader == null)
+ {
+ kLightVolumeShader = Resources.Load<Shader>("DS_HazeLightVolume");
+ }
+ if (m_VolumeMaterial == null)
+ {
+ m_VolumeMaterial = new Material(kLightVolumeShader);
+ m_VolumeMaterial.hideFlags = HideFlags.HideAndDontSave;
+ }
+ if (m_RenderCmd == null)
+ {
+ m_RenderCmd = new CommandBuffer();
+ m_RenderCmd.name = base.gameObject.name + "_DS_Haze_RenderLightVolume";
+ m_Light.AddCommandBuffer(LightEvent.AfterShadowMap, m_RenderCmd);
+ }
+ if (LightTypeChanged())
+ {
+ UpdateLightType();
+ }
+ else if (ProxyMeshRequiresRebuild())
+ {
+ RebuildProxyMesh();
+ }
+ if (ShadowModeChanged())
+ {
+ UpdateShadowMode();
+ }
+ Register();
+ }
+
+ private void OnDisable()
+ {
+ Deregister();
+ }
+
+ private void OnDestroy()
+ {
+ if (m_RenderCmd != null)
+ {
+ m_RenderCmd.Dispose();
+ }
+ Deregister();
+ if (m_ProxyMesh != null && m_Light.type != LightType.Point)
+ {
+ UnityEngine.Object.DestroyImmediate(m_ProxyMesh);
+ }
+ if (m_VolumeMaterial != null)
+ {
+ UnityEngine.Object.DestroyImmediate(m_VolumeMaterial);
+ }
+ }
+
+ private int SetShaderPassAndMatrix(Transform cameraTransform, int downSampleFactor, out Matrix4x4 worldMtx)
+ {
+ worldMtx = Matrix4x4.TRS(base.transform.position, base.transform.rotation, new Vector3(m_Light.range, m_Light.range, m_Light.range));
+ int num = 0;
+ if (m_Light.type == LightType.Spot)
+ {
+ float num2 = Mathf.Cos(m_Light.spotAngle / 2f * ((float)Math.PI / 180f));
+ Vector3 normalized = (cameraTransform.position - base.transform.position).normalized;
+ float num3 = Vector3.Dot(normalized, base.transform.forward);
+ num = ((num3 > num2) ? 1 : 2);
+ }
+ if (downSampleFactor == 4)
+ {
+ num += 3;
+ }
+ if (m_Falloff == DS_LightFalloff.Quadratic)
+ {
+ num += 6;
+ }
+ if (m_UseFog)
+ {
+ num += 12;
+ }
+ return num;
+ }
+
+ public void FillLightCommandBuffer(RenderTexture radianceTarget, Transform cameraTransform, int downSampleFactor)
+ {
+ m_RenderCmd.SetGlobalTexture("_ShadowMapTexture", BuiltinRenderTextureType.CurrentActive);
+ Matrix4x4 worldMtx;
+ int shaderPass = SetShaderPassAndMatrix(cameraTransform, downSampleFactor, out worldMtx);
+ m_RenderCmd.SetRenderTarget(radianceTarget);
+ m_RenderCmd.DrawMesh(m_ProxyMesh, worldMtx, m_VolumeMaterial, 0, shaderPass);
+ }
+
+ public void AddLightRenderCommand(Transform cameraTransform, CommandBuffer cmd, int downSampleFactor)
+ {
+ Matrix4x4 worldMtx;
+ int shaderPass = SetShaderPassAndMatrix(cameraTransform, downSampleFactor, out worldMtx);
+ cmd.DrawMesh(m_ProxyMesh, worldMtx, m_VolumeMaterial, 0, shaderPass);
+ }
+
+ public void SetupMaterialPerFrame(Matrix4x4 viewProjMtx, Matrix4x4 viewMtx, Transform cameraTransform, float offsetIndex)
+ {
+ m_VolumeMaterial.DisableKeyword("SAMPLES_4");
+ m_VolumeMaterial.DisableKeyword("SAMPLES_8");
+ m_VolumeMaterial.DisableKeyword("SAMPLES_16");
+ m_VolumeMaterial.DisableKeyword("SAMPLES_32");
+ switch (m_Samples)
+ {
+ case DS_SamplingQuality.x4:
+ m_VolumeMaterial.EnableKeyword("SAMPLES_4");
+ break;
+ case DS_SamplingQuality.x8:
+ m_VolumeMaterial.EnableKeyword("SAMPLES_8");
+ break;
+ case DS_SamplingQuality.x16:
+ m_VolumeMaterial.EnableKeyword("SAMPLES_16");
+ break;
+ case DS_SamplingQuality.x32:
+ m_VolumeMaterial.EnableKeyword("SAMPLES_32");
+ break;
+ default:
+ m_VolumeMaterial.EnableKeyword("SAMPLES_16");
+ break;
+ }
+ float num = 1f - Mathf.Clamp01((Vector3.Distance(cameraTransform.position, base.transform.position) - m_StartFade) / (m_EndFade - m_StartFade));
+ m_VolumeMaterial.SetVector("_DS_HazeSamplingParams", new Vector4(offsetIndex, 0f, m_DensityTextureContrast, 0f));
+ m_VolumeMaterial.SetVector("_DS_HazeCameraDirection", new Vector4(cameraTransform.forward.x, cameraTransform.forward.y, cameraTransform.forward.z, 1f));
+ m_VolumeMaterial.SetColor("_DS_HazeLightVolumeColour", m_Light.color.linear * m_Light.intensity * num);
+ m_VolumeMaterial.SetVector("_DS_HazeLightVolumeScattering", new Vector4(m_Scattering, m_SecondaryScattering, m_ScatteringDirection, Mathf.Clamp01(1f - m_SecondaryScattering)));
+ m_VolumeMaterial.SetVector("_DS_HazeLightVolumeParams0", new Vector4(base.transform.position.x, base.transform.position.y, base.transform.position.z, m_Light.range));
+ Matrix4x4 matrix4x = Matrix4x4.TRS(base.transform.position, base.transform.rotation, new Vector3(m_Light.range, m_Light.range, m_Light.range));
+ m_VolumeMaterial.SetMatrix("_WorldViewProj", viewProjMtx * matrix4x);
+ m_VolumeMaterial.SetMatrix("_WorldView", viewMtx * matrix4x);
+ if ((bool)m_DensityTexture)
+ {
+ m_VolumeMaterial.EnableKeyword("DENSITY_TEXTURE");
+ m_VolumeMaterial.SetTexture("_DensityTexture", m_DensityTexture);
+ m_VolumeMaterial.SetVector("_DS_HazeDensityParams", new Vector4(m_DensityOffset.x, m_DensityOffset.y, m_DensityOffset.z, m_DensityTextureScale * 0.01f));
+ }
+ else
+ {
+ m_VolumeMaterial.DisableKeyword("DENSITY_TEXTURE");
+ }
+ bool flag = m_Light.shadows != LightShadows.None;
+ if (m_Light.type == LightType.Point)
+ {
+ m_VolumeMaterial.DisableKeyword("SPOT_COOKIE");
+ m_VolumeMaterial.DisableKeyword("SHADOWS_DEPTH");
+ if (flag)
+ {
+ m_VolumeMaterial.EnableKeyword("SHADOWS_CUBE");
+ }
+ else
+ {
+ m_VolumeMaterial.DisableKeyword("SHADOWS_CUBE");
+ }
+ if ((bool)m_Light.cookie)
+ {
+ m_VolumeMaterial.EnableKeyword("POINT_COOKIE");
+ m_VolumeMaterial.SetMatrix("_DS_Haze_WorldToCookie", base.transform.worldToLocalMatrix);
+ m_VolumeMaterial.SetTexture("_LightTexture0", m_Light.cookie);
+ }
+ else
+ {
+ m_VolumeMaterial.DisableKeyword("POINT_COOKIE");
+ }
+ }
+ else if (m_Light.type == LightType.Spot)
+ {
+ m_VolumeMaterial.DisableKeyword("POINT_COOKIE");
+ m_VolumeMaterial.DisableKeyword("SHADOWS_CUBE");
+ if (flag)
+ {
+ m_VolumeMaterial.EnableKeyword("SHADOWS_DEPTH");
+ Matrix4x4 inverse = Matrix4x4.TRS(base.transform.position, base.transform.rotation, Vector3.one).inverse;
+ Matrix4x4 matrix4x2 = Matrix4x4.TRS(new Vector3(0.5f, 0.5f, 0.5f), Quaternion.identity, new Vector3(0.5f, 0.5f, 0.5f));
+ Matrix4x4 matrix4x3 = Matrix4x4.Perspective(m_Light.spotAngle, 1f, m_Light.range, m_Light.shadowNearPlane);
+ Matrix4x4 value = matrix4x2 * matrix4x3;
+ value[0, 2] *= -1f;
+ value[1, 2] *= -1f;
+ value[2, 2] *= -1f;
+ value[3, 2] *= -1f;
+ value *= inverse;
+ m_VolumeMaterial.SetMatrix("_DS_Haze_WorldToShadow", value);
+ }
+ else
+ {
+ m_VolumeMaterial.DisableKeyword("SHADOWS_DEPTH");
+ }
+ float num2 = Mathf.Cos(m_Light.spotAngle / 2f * ((float)Math.PI / 180f));
+ Vector3 lhs = base.transform.position + base.transform.forward * m_Light.range;
+ float z = 0f - Vector3.Dot(lhs, base.transform.forward);
+ m_VolumeMaterial.SetVector("_DS_HazeLightVolumeParams1", new Vector4(base.transform.forward.x, base.transform.forward.y, base.transform.forward.z, 1f));
+ m_VolumeMaterial.SetVector("_DS_HazeLightVolumeParams2", new Vector4(num2, 1f / num2, z, 0f));
+ if ((bool)m_Light.cookie)
+ {
+ m_VolumeMaterial.EnableKeyword("SPOT_COOKIE");
+ Matrix4x4 inverse2 = Matrix4x4.TRS(base.transform.position, base.transform.rotation, Vector3.one).inverse;
+ Matrix4x4 matrix4x4 = Matrix4x4.TRS(new Vector3(0.5f, 0.5f, 0f), Quaternion.identity, new Vector3(-0.5f, -0.5f, 1f));
+ Matrix4x4 matrix4x5 = Matrix4x4.Perspective(m_Light.spotAngle, 1f, 0f, 1f);
+ m_VolumeMaterial.SetMatrix("_DS_Haze_WorldToCookie", matrix4x4 * matrix4x5 * inverse2);
+ m_VolumeMaterial.SetTexture("_LightTexture0", m_Light.cookie);
+ }
+ else
+ {
+ m_VolumeMaterial.DisableKeyword("SPOT_COOKIE");
+ }
+ }
+ }
+}