namespace UnityEngine.AzureSky { [ExecuteInEditMode] [RequireComponent(typeof(Light))] [AddComponentMenu("Azure[Sky] Dynamic Skybox/Azure Volumetric Light")] public sealed class AzureVolumetricLight : MonoBehaviour { #pragma warning disable 0108 // Remove warning caused by obsolete members that do not actually exist /// The reference to the target light component. public Light light { get => m_light; set => m_light = value; } [SerializeField] private Light m_light = null; #pragma warning restore 0108 /// The mesh used to render the light in the scene. public Mesh lightMesh { get => m_lightMesh; set => m_lightMesh = value; } [SerializeField] private Mesh m_lightMesh = null; /// The material used to render the light in the scene. public Material lightMaterial { get => m_lightMaterial; set => m_lightMaterial = value; } [SerializeField] private Material m_lightMaterial = null; /// The mie directionality factor. public float mieDirectionalityFactor { get => m_mieDirectionalityFactor; set => m_mieDirectionalityFactor = value; } [Range(0.0f, 0.9f)] [SerializeField] private float m_mieDirectionalityFactor = 0.75f; /// The volumetric light intensity multiplier. public float intensityMultiplier { get => m_intensityMultiplier; set => m_intensityMultiplier = value; } [Range(0.0f, 1.0f)][SerializeField] private float m_intensityMultiplier = 0.7f; /// The volumetric light range multiplier. public float rangeMultiplier { get => m_rangeMultiplier; set => m_rangeMultiplier = value; } [Range(0.0f, 1.0f)][SerializeField] private float m_rangeMultiplier = 1.0f; /// The volumetric light angle multiplier (Spot Light Only!). public float angleMultiplier { get => m_angleMultiplier; set => m_angleMultiplier = value; } [Range(0.0f, 5.0f)][SerializeField] private float m_angleMultiplier = 1.0f; /// The transformation matrix used to render the light mesh in the scene. public Matrix4x4 trsMatrix { get => m_trsMatrix; set => m_trsMatrix = value; } private Matrix4x4 m_trsMatrix = Matrix4x4.identity; /// The distance this light is from the camera. public float distanceToCamera { get => m_distanceToCamera; set => m_distanceToCamera = value; } private float m_distanceToCamera = 0.0f; /// Is the camera inside this light sphere range? (1 = true) (0 = false). Point/Spot light only! public int isCameraInsideSphereRange { get => m_isCameraInsideSphereRange; set => m_isCameraInsideSphereRange = value; } private int m_isCameraInsideSphereRange = 0; /// Is the camera inside this light cone range? (1 = true) (0 = false). Spot light only! public int isCameraInsideConeRange { get => m_isCameraInsideConeRange; set => m_isCameraInsideConeRange = value; } private int m_isCameraInsideConeRange = 0; /// The property block used to render this volumetric light instance. public MaterialPropertyBlock propertyBlock { get => m_propertyBlock; set => m_propertyBlock = value; } [SerializeField] private MaterialPropertyBlock m_propertyBlock = null; private float m_spotLightAngle = 1.0f; private float m_spotLightAngleRad = 1.0f; private float m_spotLightAngleScale = 0.0f; private float m_lightRange = 1.0f; private float m_spotAngleToCamera = 0.0f; private Vector3 m_spotToCameraVector = Vector3.zero; private void Start() { m_light = GetComponent(); m_propertyBlock = new MaterialPropertyBlock(); } private void OnEnable() { AzureNotificationCenter.OnVolumetricLightPreRender += OnVolumetricLightPreRender; } private void OnDisable() { AzureNotificationCenter.OnVolumetricLightPreRender -= OnVolumetricLightPreRender; } /// Check if a sphere is inside the camera frustum planes. bool CheckSphereCulling(Plane[] planes, Vector3 center, float radius) { for (int i = 0; i < planes.Length; i++) { if (planes[i].normal.x * center.x + planes[i].normal.y * center.y + planes[i].normal.z * center.z + planes[i].distance < -radius) { return false; } } return true; } /// /// Event triggered from the OnPreRender event of the current camera rendering the volumetric lights using the "AzureVolumetricLightRenderer.cs" component. /// See the OnPreRender method of the AzureVolumetricLightRenderer.cs component attached to the camera. /// We need to register to the event this way, because the OnPreRender event is only called by the scripts attached to the camera. /// We can't write our code into the OnPreRender event right here in this script! /// We use the OnPreRender event to adjust the light mesh and material setup just before the camera rendering. /// private void OnVolumetricLightPreRender(AzureVolumetricLightRenderer renderer) { if (m_light == null) return; if (m_propertyBlock == null) { m_propertyBlock = new MaterialPropertyBlock(); } // Renders the volumetric light according to its type. switch (m_light.type) { case LightType.Spot: m_spotLightAngle = m_light.spotAngle * 0.5f * m_angleMultiplier; m_lightRange = m_light.range * m_rangeMultiplier; // Cancel this volumetric light rendering if the light is outside frustum culling of the current camera rendering if (!CheckSphereCulling(renderer.cameraFrustumPlanes, transform.position, m_lightRange)) return; // Set the spotlight transformation matrix m_spotLightAngleRad = m_spotLightAngle * Mathf.Deg2Rad; m_spotLightAngleScale = Mathf.Tan(m_spotLightAngleRad) * m_lightRange * 2.0f; m_trsMatrix = Matrix4x4.TRS(transform.position, transform.rotation, new Vector3(m_spotLightAngleScale, m_spotLightAngleScale, m_lightRange)); // Get the light distance from the camera m_distanceToCamera = Vector3.Distance(renderer.camera.transform.position, transform.position); // Check if the camera is inside the volumetric light sphere m_isCameraInsideSphereRange = m_distanceToCamera < m_lightRange ? 1 : 0; m_isCameraInsideConeRange = 0; // Check if the camera is inside the volumetric light cone if (m_isCameraInsideSphereRange == 1) { // Calculate the direction vector from the spotlight position to the camera position m_spotToCameraVector = renderer.camera.transform.position - transform.position; // Calculate the angle between the spotlight's forward direction and the direction to the camera m_spotAngleToCamera = Vector3.Angle(transform.forward, m_spotToCameraVector); // If the angle is less than half of the spotlight's range angle, the camera is inside the cone m_isCameraInsideConeRange = m_spotAngleToCamera < m_spotLightAngle ? 1 : 0; } // Set the parameters of the material property block m_propertyBlock.SetFloat(AzureShaderUniforms.VolumetricLightIntensity, m_light.intensity * m_intensityMultiplier); m_propertyBlock.SetColor(AzureShaderUniforms.VolumetricLightColor, m_light.color); m_propertyBlock.SetFloat(AzureShaderUniforms.VolumetricLightExtinctionDistance, renderer.lightExtinctionDistance); m_propertyBlock.SetFloat(AzureShaderUniforms.VolumetricLightExtinctionSmoothStep, renderer.lightExtinctionSmoothStep); m_propertyBlock.SetVector(AzureShaderUniforms.VolumetricLightMieG, ComputeMieG()); m_propertyBlock.SetVector(AzureShaderUniforms.VolumetricLightWorldPosition, transform.position); renderer.commandBuffer.DrawMesh(m_lightMesh, m_trsMatrix, m_lightMaterial, 0, m_isCameraInsideConeRange, m_propertyBlock); break; case LightType.Directional: // Current not supported! break; case LightType.Point: // Get the light distance from the camera m_distanceToCamera = Vector3.Distance(transform.position, renderer.camera.transform.position); // Cancel this volumetric light rendering if the light is outside frustum culling of the current camera rendering if (!CheckSphereCulling(renderer.cameraFrustumPlanes, transform.position, m_light.range)) return; // Check if the camera is inside the volumetric light sphere m_isCameraInsideSphereRange = m_distanceToCamera < m_light.range ? 1 : 0; // Set the point light transformation matrix m_lightRange = m_light.range * m_rangeMultiplier; m_trsMatrix = Matrix4x4.TRS(transform.position, transform.rotation, new Vector3(m_lightRange, m_lightRange, m_lightRange)); // Set the parameters of the material property block m_propertyBlock.SetFloat(AzureShaderUniforms.VolumetricLightIntensity, m_light.intensity * m_intensityMultiplier); m_propertyBlock.SetColor(AzureShaderUniforms.VolumetricLightColor, m_light.color); m_propertyBlock.SetFloat(AzureShaderUniforms.VolumetricLightExtinctionDistance, renderer.lightExtinctionDistance); m_propertyBlock.SetFloat(AzureShaderUniforms.VolumetricLightExtinctionSmoothStep, renderer.lightExtinctionSmoothStep); m_propertyBlock.SetVector(AzureShaderUniforms.VolumetricLightMieG, ComputeMieG()); renderer.commandBuffer.DrawMesh(m_lightMesh, m_trsMatrix, m_lightMaterial, 0, m_isCameraInsideSphereRange, m_propertyBlock); break; } } /// Computed the mie directionality factor. private Vector3 ComputeMieG() { return new Vector3(1.0f - m_mieDirectionalityFactor * m_mieDirectionalityFactor, 1.0f + m_mieDirectionalityFactor * m_mieDirectionalityFactor, 2.0f * m_mieDirectionalityFactor); } } }