You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

845 lines
33 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
//=================================================================================================
/**
* @brief This class manages all lights for a particular FogVolume.
*
*************************************************************************************************/
public class FogVolumeLightManager : MonoBehaviour
{
public int CurrentLightCount { get; private set; }
public int VisibleLightCount { get; private set; }
public bool DrawDebugData { get; set; }
public bool AlreadyUsesTransformForPoI { get { return m_pointOfInterestTf != null; } }
// Very slow and garbage allocating. Only call this once!
public void FindLightsInScene()
{
CurrentLightCount = 0;
VisibleLightCount = 0;
m_lights.Clear();
m_lightsInFrustum.Clear();
for (int i = 0; i < MaxLightCount; i++)
{
m_lights.Add(new LightData());
m_lightsInFrustum.Add(new LightData());
}
FogVolumeLight[] lights = FindObjectsOfType<FogVolumeLight>();
for (int i = 0; i < lights.Length; i++)
{
Light unityLight = lights[i].GetComponent<Light>();
if (unityLight != null)
{
switch (unityLight.type)
{
case LightType.Point:
{
AddPointLight(unityLight);
lights[i].IsAddedToNormalLight = true;
break;
}
case LightType.Spot:
{
AddSpotLight(unityLight);
lights[i].IsAddedToNormalLight = true;
break;
}
}
}
else
{
if (lights[i].IsPointLight)
{
AddSimulatedPointLight(lights[i]);
lights[i].IsAddedToNormalLight = false;
}
else
{
AddSimulatedSpotLight(lights[i]);
lights[i].IsAddedToNormalLight = false;
}
}
}
}
// Very slow and garbage allocating. Only call this once!
public void FindLightsInFogVolume()
{
CurrentLightCount = 0;
VisibleLightCount = 0;
m_lights.Clear();
m_lightsInFrustum.Clear();
for (int i = 0; i < MaxLightCount; i++)
{
m_lights.Add(new LightData());
m_lightsInFrustum.Add(new LightData());
}
if (m_boxCollider == null) { m_boxCollider = gameObject.GetComponent<BoxCollider>(); }
Bounds boundingBox = m_boxCollider.bounds;
FogVolumeLight[] lights = FindObjectsOfType<FogVolumeLight>();
for (int i = 0; i < lights.Length; i++)
{
if (boundingBox.Intersects(new Bounds(lights[i].gameObject.transform.position,
Vector3.one * LightInVolumeBoundsSize)))
{
Light unityLight = lights[i].GetComponent<Light>();
if (unityLight != null)
{
switch (unityLight.type)
{
case LightType.Point:
{
AddPointLight(unityLight);
lights[i].IsAddedToNormalLight = true;
break;
}
case LightType.Spot:
{
AddSpotLight(unityLight);
lights[i].IsAddedToNormalLight = true;
break;
}
}
}
else
{
if (lights[i].IsPointLight)
{
AddSimulatedPointLight(lights[i]);
lights[i].IsAddedToNormalLight = false;
}
else
{
AddSimulatedSpotLight(lights[i]);
lights[i].IsAddedToNormalLight = false;
}
}
}
}
}
//=============================================================================================
/**
* @brief Add a simulated point light to the manager.
*
* Note that a simulated point light is any GameObject with a SimulatedPointLight component on
* it.
*
* @param _light The SimulatedPointLight component of an existing GameObject.
*
* @return True if the light was added to the managers list of lights.
* @return False if the manager already contains the maximum amount of lights that was
* specified in the constructor.
*
*********************************************************************************************/
public bool AddSimulatedPointLight(FogVolumeLight _light)
{
Assert.IsTrue(CurrentLightCount < MaxLightCount,
"The maximum number of lights is already reached!");
int index = _FindFirstFreeLight();
if (index != InvalidIndex)
{
LightData data = m_lights[index];
CurrentLightCount++;
data.LightType = EFogVolumeLightType.FogVolumePointLight;
data.Transform = _light.transform;
data.Light = null;
data.FogVolumeLight = _light;
data.Bounds = new Bounds(data.Transform.position,
Vector3.one * data.FogVolumeLight.Range * 2.5f);
return true;
}
return false;
}
//=============================================================================================
/**
* @brief Add a simulated spot light to the manager.
*
* Note that a simulated spot light is any GameObject with a SimulatedSpotLight component on
* it.
*
* @param _light The SimulatedSpotLight component of an existing GameObject.
*
* @return True if the light was added to the managers list of lights.
* @return False if the manager already contains the maximum amount of lights that was
* specified in the constructor.
*
*********************************************************************************************/
public bool AddSimulatedSpotLight(FogVolumeLight _light)
{
Assert.IsTrue(CurrentLightCount < MaxLightCount,
"The maximum number of lights is already reached!");
int index = _FindFirstFreeLight();
if (index != InvalidIndex)
{
LightData data = m_lights[index];
CurrentLightCount++;
data.LightType = EFogVolumeLightType.FogVolumeSpotLight;
data.Transform = _light.transform;
data.Light = null;
data.FogVolumeLight = _light;
Vector3 center = data.Transform.position +
data.Transform.forward * data.FogVolumeLight.Range * 0.5f;
data.Bounds = new Bounds(center,
Vector3.one * data.FogVolumeLight.Range *
(0.75f + data.FogVolumeLight.Angle * 0.03f));
return true;
}
return false;
}
//=============================================================================================
/**
* @brief Add an existing point light to the manager.
*
* @param _light The light component of an existing point light.
*
* @return True if the light was added to the managers list of lights.
* @return False if the manager already contains the maximum amount of lights that was
* specified in the constructor.
*
*********************************************************************************************/
public bool AddPointLight(Light _light)
{
Assert.IsTrue(CurrentLightCount < MaxLightCount,
"The maximum number of lights is already reached!");
int index = _FindFirstFreeLight();
if (index != InvalidIndex)
{
LightData data = m_lights[index];
CurrentLightCount++;
data.LightType = EFogVolumeLightType.PointLight;
data.Transform = _light.transform;
data.Light = _light;
data.FogVolumeLight = null;
data.Bounds = new Bounds(data.Transform.position,
Vector3.one * data.Light.range * 2.5f);
return true;
}
return false;
}
//=============================================================================================
/**
* @brief Add an existing spot light to the manager.
*
* @param _light The light component of an existing spot light.
*
* @return True if the light was added to the managers list of lights.
* @return False if the manager already contains the maximum amount of lights that was
* specified in the constructor.
*
*********************************************************************************************/
public bool AddSpotLight(Light _light)
{
Assert.IsTrue(CurrentLightCount < MaxLightCount,
"The maximum number of lights is already reached!");
int index = _FindFirstFreeLight();
if (index != InvalidIndex)
{
LightData data = m_lights[index];
CurrentLightCount++;
data.LightType = EFogVolumeLightType.SpotLight;
data.Transform = _light.transform;
data.Light = _light;
data.FogVolumeLight = null;
Vector3 center = data.Transform.position +
data.Transform.forward * data.Light.range * 0.5f;
data.Bounds = new Bounds(center,
Vector3.one * data.Light.range *
(0.75f + data.Light.spotAngle * 0.03f));
return true;
}
return false;
}
//=============================================================================================
/**
* @brief Removes the light with the specified transform.
*
* Note that nothing will happen if the light is not currently present in the manager.
*
* @param _lightToRemove The light that will be removed from this manager.
*
* @return True if the light was found inside the manager and removed successfully.
* @return False if the light was not found an thus not removed.
*
*********************************************************************************************/
public bool RemoveLight(Transform _lightToRemove)
{
int count = m_lights.Count;
for (int i = 0; i < count; i++)
{
if (ReferenceEquals(m_lights[i].Transform, _lightToRemove))
{
m_lights[i].LightType = EFogVolumeLightType.None;
CurrentLightCount--;
return true;
}
}
return false;
}
//=============================================================================================
/**
* @brief Updates the lights that will be rendered.
*
* Note that this method should be called once per frame, before any data is sent to the
* shaders.
*
*********************************************************************************************/
public void ManualUpdate(ref Plane[] _frustumPlanes)
{
FrustumPlanes = _frustumPlanes;
m_camera = m_fogVolumeData != null ? m_fogVolumeData.GameCamera : null;
if (m_camera == null) { return; }
if (m_boxCollider == null) { m_boxCollider = m_fogVolume.GetComponent<BoxCollider>(); }
if (m_pointOfInterestTf != null) { m_pointOfInterest = m_pointOfInterestTf.position; }
_UpdateBounds();
_FindLightsInFrustum();
if (m_lightsInFrustum.Count > MaxVisibleLights) { _SortLightsInFrustum(); }
_PrepareShaderArrays();
}
//=============================================================================================
/**
* @brief Draws the gizmos if the user wants to see debug data.
*
*********************************************************************************************/
public void OnDrawGizmos()
{
hideFlags = HideFlags.HideInInspector;
if (m_camera == null) { return; }
if (!DrawDebugData) { return; }
Color tempColor = Gizmos.color;
Gizmos.color = Color.green;
for (int i = 0; i < VisibleLightCount; i++)
{
Gizmos.DrawWireCube(m_lightsInFrustum[i].Bounds.center,
m_lightsInFrustum[i].Bounds.size);
}
Gizmos.color = Color.magenta;
Matrix4x4 currentMatrix = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.TRS(m_camera.transform.position,
m_camera.transform.rotation,
Vector3.one);
//Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawFrustum(m_camera.transform.position,
m_camera.fieldOfView,
m_camera.nearClipPlane,
m_fogVolume.PointLightingDistance2Camera,
m_camera.aspect);
Gizmos.color = tempColor;
Gizmos.matrix = currentMatrix;
}
//=============================================================================================
/**
* @brief Sets the multiplier that is applied to the bounding box size of point lights.
*
* @param _cullSizeMultiplier The multiplier that is applied to the AABB of point lights.
*
*********************************************************************************************/
public void SetPointLightCullSizeMultiplier(float _cullSizeMultiplier)
{
m_pointLightCullSizeMultiplier = _cullSizeMultiplier;
}
//=============================================================================================
/**
* @brief Sets the point of interest to a fixed position.
*
* @param _pointOfInterest The point that will be used for prioritizing which lights need to
* be rendered.
*
*********************************************************************************************/
public void SetPointOfInterest(Vector3 _pointOfInterest)
{
m_pointOfInterestTf = null;
m_pointOfInterest = _pointOfInterest;
}
//=============================================================================================
/**
* @brief Sets the point of interest to the specified transform.
*
* The point of interest will be updated from the position of the transform. It is therefore
* not necessary to call this method more than once.
*
*********************************************************************************************/
public void SetPointOfInterest(Transform _pointOfInterest)
{
Assert.IsTrue(_pointOfInterest != null, "_pointOfInterest must not be null!");
m_pointOfInterestTf = _pointOfInterest;
}
//=============================================================================================
/**
* @brief Returns the array that contains all light positions.
*
* Remember to call Update() before using this method, otherwise old data will be sent to the
* shaders.
*
*********************************************************************************************/
public Vector4[] GetLightPositionArray() { return m_lightPos; }
//=============================================================================================
/**
* @brief Returns the array that contains all light rotations.
*
* Remember to call Update() before using this method, otherwise old data will be sent to the
* shaders.
*
*********************************************************************************************/
public Vector4[] GetLightRotationArray() { return m_lightRot; }
//=============================================================================================
/**
* @brief Returns the array that contains all light colors.
*
* Remember to call Update() before using this method, otherwise old data will be sent to the
* shaders.
*********************************************************************************************/
public Color[] GetLightColorArray() { return m_lightColor; }
//=============================================================================================
/**
* @brief Returns the array that contains all light data (intensity, range, spotlight angle,
* none).
*
* Remember to call Update() before using this method, otherwise old data will be sent to the
* shaders.
*
*********************************************************************************************/
public Vector4[] GetLightData() { return m_lightData; }
//=============================================================================================
/**
* @brief Initializes the LightManager with default values.
*
*********************************************************************************************/
public void Initialize()
{
m_fogVolume = gameObject.GetComponent<FogVolume>();
m_fogVolumeData = FindObjectOfType<FogVolumeData>();
m_camera = null;
m_boxCollider = null;
CurrentLightCount = 0;
DrawDebugData = false;
if (m_lights == null)
{
m_lights = new List<LightData>(MaxLightCount);
m_lightsInFrustum = new List<LightData>(MaxLightCount);
for (int i = 0; i < MaxLightCount; i++)
{
m_lights.Add(new LightData());
m_lightsInFrustum.Add(new LightData());
}
}
}
//=============================================================================================
/**
* @brief Clears the LightManager and prepares it toi be reinitialized.
*
*********************************************************************************************/
public void Deinitialize()
{
VisibleLightCount = 0;
DrawDebugData = false;
}
public void SetFrustumPlanes(ref Plane[] _frustumPlanes) { FrustumPlanes = _frustumPlanes; }
#if UNITY_EDITOR
private void Update() { hideFlags = HideFlags.HideInInspector; }
#endif
//=============================================================================================
/**
* @brief Updates the axis aligned bounding boxes of all registered lights.
*
*********************************************************************************************/
private void _UpdateBounds()
{
int count = m_lights.Count;
for (int i = 0; i < count; i++)
{
LightData data = m_lights[i];
if (data.LightType == EFogVolumeLightType.None) { continue; }
switch (data.LightType)
{
case EFogVolumeLightType.None:
{
break;
}
case EFogVolumeLightType.PointLight:
{
data.Bounds = new Bounds(data.Transform.position,
Vector3.one * data.Light.range *
m_pointLightCullSizeMultiplier);
break;
}
case EFogVolumeLightType.SpotLight:
{
Vector3 center = data.Transform.position +
data.Transform.forward * data.Light.range * 0.5f;
data.Bounds = new Bounds(center,
Vector3.one * data.Light.range *
m_pointLightCullSizeMultiplier * 1.25f);
break;
}
case EFogVolumeLightType.FogVolumePointLight:
{
data.Bounds = new Bounds(data.Transform.position,
Vector3.one * data.FogVolumeLight.Range *
m_pointLightCullSizeMultiplier);
break;
}
case EFogVolumeLightType.FogVolumeSpotLight:
{
Vector3 center = data.Transform.position +
data.Transform.forward * data.FogVolumeLight.Range * 0.5f;
data.Bounds = new Bounds(center,
Vector3.one * data.FogVolumeLight.Range *
m_pointLightCullSizeMultiplier * 1.25f);
break;
}
}
}
}
//=============================================================================================
/**
* @brief Finds the first free light in the list of all lights.
*
* Note that this method is necessary to ensure that adding/removing lights does not allocate
* any garbage.
*
*********************************************************************************************/
private int _FindFirstFreeLight()
{
if (CurrentLightCount < MaxLightCount)
{
int count = m_lights.Count;
for (int i = 0; i < count; i++)
{
if (m_lights[i].LightType == EFogVolumeLightType.None) { return i; }
}
}
return InvalidIndex;
}
//=============================================================================================
/**
* @brief Finds all lights that are currently in the view frustum of the camera and calculates
* all necessary data for them.
*
*********************************************************************************************/
private void _FindLightsInFrustum()
{
m_inFrustumCount = 0;
Vector3 CameraPos = m_camera.gameObject.transform.position;
int count = m_lights.Count;
for (int i = 0; i < count; i++)
{
if (m_lights[i].Transform == null) { m_lights[i].LightType = EFogVolumeLightType.None; }
if (m_lights[i].LightType == EFogVolumeLightType.None) { continue; }
float distanceToCamera = (m_lights[i].Transform.position - CameraPos).magnitude;
if (distanceToCamera > m_fogVolume.PointLightingDistance2Camera) { continue; }
switch (m_lights[i].LightType)
{
case EFogVolumeLightType.None:
{
continue;
}
case EFogVolumeLightType.PointLight:
case EFogVolumeLightType.SpotLight:
{
if (m_lights[i].Light.enabled == false) { continue; }
break;
}
case EFogVolumeLightType.FogVolumePointLight:
{
if (m_lights[i].FogVolumeLight.Enabled == false) { continue; }
break;
}
case EFogVolumeLightType.FogVolumeSpotLight:
{
if (m_lights[i].FogVolumeLight.Enabled == false) { continue; }
break;
}
}
if (GeometryUtility.TestPlanesAABB(FrustumPlanes, m_lights[i].Bounds))
{
LightData light = m_lights[i];
Vector3 lightPos = light.Transform.position;
light.SqDistance = (lightPos - m_pointOfInterest).sqrMagnitude;
light.Distance2Camera = (lightPos - CameraPos).magnitude;
m_lightsInFrustum[m_inFrustumCount++] = light;
if (light.FogVolumeLight != null)
{
if (light.LightType == EFogVolumeLightType.FogVolumePointLight &&
!light.FogVolumeLight.IsPointLight)
{
light.LightType = EFogVolumeLightType.FogVolumeSpotLight;
}
else if (light.LightType == EFogVolumeLightType.FogVolumeSpotLight &&
light.FogVolumeLight.IsPointLight)
{
light.LightType = EFogVolumeLightType.FogVolumePointLight;
}
}
}
}
}
//=============================================================================================
/**
* @brief Sorts the lights that are currently within the view frustum of the camera by
* distance to the camera.
*
* This method will only be called when there are more than MaxVisibleLights in the view
* frustum of the camera.
*
*********************************************************************************************/
private void _SortLightsInFrustum()
{
bool finishedSorting = false;
do
{
finishedSorting = true;
for (int i = 0; i < m_inFrustumCount - 1; i++)
{
if (m_lightsInFrustum[i].SqDistance > m_lightsInFrustum[i + 1].SqDistance)
{
LightData tempData = m_lightsInFrustum[i];
m_lightsInFrustum[i] = m_lightsInFrustum[i + 1];
m_lightsInFrustum[i + 1] = tempData;
finishedSorting = false;
}
}
}
while (!finishedSorting);
}
//=============================================================================================
/**
* @brief Prepares the data of the currently visible light and writes it to the arrays that
* can then be sent to the shaders.
*
*********************************************************************************************/
private void _PrepareShaderArrays()
{
VisibleLightCount = 0;
for (int i = 0; i < MaxVisibleLights; i++)
{
if (i >= m_inFrustumCount) { break; }
LightData data = m_lightsInFrustum[i];
switch (data.LightType)
{
case EFogVolumeLightType.FogVolumePointLight:
{
FogVolumeLight light = data.FogVolumeLight;
m_lightPos[i] =
gameObject.transform.InverseTransformPoint(data.Transform.position);
m_lightRot[i] =
gameObject.transform.InverseTransformVector(data.Transform.forward);
m_lightColor[i] = light.Color;
m_lightData[i] =
new Vector4(light.Intensity * m_fogVolume.PointLightsIntensity *
(1.0f - Mathf.Clamp01(data.Distance2Camera / m_fogVolume
.PointLightingDistance2Camera)
),
light.Range / PointLightRangeDivider,
InvalidSpotLightAngle,
NoData);
VisibleLightCount++;
break;
}
case EFogVolumeLightType.FogVolumeSpotLight:
{
FogVolumeLight light = data.FogVolumeLight;
m_lightPos[i] =
gameObject.transform.InverseTransformPoint(data.Transform.position);
m_lightRot[i] =
gameObject.transform.InverseTransformVector(data.Transform.forward);
m_lightColor[i] = light.Color;
m_lightData[i] =
new Vector4(light.Intensity * m_fogVolume.PointLightsIntensity *
(1.0f - Mathf.Clamp01(data.Distance2Camera / m_fogVolume
.PointLightingDistance2Camera)
),
light.Range / SpotLightRangeDivider,
light.Angle,
NoData);
VisibleLightCount++;
break;
}
case EFogVolumeLightType.PointLight:
{
Light light = data.Light;
m_lightPos[i] =
gameObject.transform.InverseTransformPoint(data.Transform.position);
m_lightRot[i] =
gameObject.transform.InverseTransformVector(data.Transform.forward);
m_lightColor[i] = light.color;
m_lightData[i] =
new Vector4(light.intensity * m_fogVolume.PointLightsIntensity *
(1.0f - Mathf.Clamp01(data.Distance2Camera / m_fogVolume
.PointLightingDistance2Camera)
),
light.range / PointLightRangeDivider,
InvalidSpotLightAngle,
NoData);
VisibleLightCount++;
break;
}
case EFogVolumeLightType.SpotLight:
{
Light light = data.Light;
m_lightPos[i] =
gameObject.transform.InverseTransformPoint(data.Transform.position);
m_lightRot[i] =
gameObject.transform.InverseTransformVector(data.Transform.forward);
m_lightColor[i] = light.color;
m_lightData[i] =
new Vector4(light.intensity * m_fogVolume.PointLightsIntensity *
(1.0f - Mathf.Clamp01(data.Distance2Camera / m_fogVolume
.PointLightingDistance2Camera)
),
light.range / SpotLightRangeDivider,
light.spotAngle,
NoData);
VisibleLightCount++;
break;
}
case EFogVolumeLightType.None:
{
break;
}
}
}
}
private float m_pointLightCullSizeMultiplier = 1.0f;
private FogVolume m_fogVolume = null;
private FogVolumeData m_fogVolumeData = null;
private Camera m_camera = null;
private BoxCollider m_boxCollider = null;
private Transform m_pointOfInterestTf = null;
private Vector3 m_pointOfInterest = Vector3.zero;
private readonly Vector4[] m_lightPos = new Vector4[MaxVisibleLights];
private readonly Vector4[] m_lightRot = new Vector4[MaxVisibleLights];
private readonly Color[] m_lightColor = new Color[MaxVisibleLights];
private readonly Vector4[] m_lightData = new Vector4[MaxVisibleLights];
private List<LightData> m_lights = null;
private List<LightData> m_lightsInFrustum = null;
private int m_inFrustumCount = 0;
private Plane[] FrustumPlanes = null;
private const int InvalidIndex = -1;
private const int MaxVisibleLights = 64;
private const float InvalidSpotLightAngle = -1.0f;
private const float NoData = 0.0f;
private const float PointLightRangeDivider = 5.0f;
private const float SpotLightRangeDivider = 5.0f;
private const int MaxLightCount = 1000;
/// The assumed size of a light. Used by realtime search when searching for lights inside a FV.
private const float LightInVolumeBoundsSize = 5.0f;
protected class LightData
{
public LightData()
{
LightType = EFogVolumeLightType.None;
Light = null;
FogVolumeLight = null;
Transform = null;
SqDistance = 0.0f;
Distance2Camera = 0.0f;
Bounds = new Bounds();
}
public EFogVolumeLightType LightType { get; set; }
public Light Light { get; set; }
public FogVolumeLight FogVolumeLight { get; set; }
public Transform Transform { get; set; }
public float SqDistance { get; set; }
public float Distance2Camera { get; set; }
public Bounds Bounds { get; set; }
}
}