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("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(); 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("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"); } } } }