red-sim-light-volumesudon/VRCLightVolumes/LightVolumeManager.cs
2025-09-17 01:38:27 +01:00

538 lines
No EOL
20 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace VRCLightVolumes
{
public class LightVolumeManager : MonoBehaviour
{
public const float Version = 2f;
[Tooltip("Combined texture containing all Light Volumes' textures.")]
public Texture LightVolumeAtlas;
[Tooltip("Combined Texture3D containing all baked Light Volume data. This field is not used at runtime, see LightVolumeAtlas instead. It specifies the base for the post process chain, if given.")]
public Texture3D LightVolumeAtlasBase;
[Tooltip("Custom Render Textures that will be applied top to bottom to the Light Volume Atlas at runtime. External scripts can register themselves here using `RegisterPostProcessorCRT`. You probably don't want to mess with this field manually.")]
public CustomRenderTexture[] AtlasPostProcessors;
[Tooltip("When enabled, areas outside Light Volumes fall back to light probes. Otherwise, the Light Volume with the smallest weight is used as fallback. It also improves performance.")]
public bool LightProbesBlending = true;
[Tooltip("Disables smooth blending with areas outside Light Volumes. Use it if your entire scene's play area is covered by Light Volumes. It also improves performance.")]
public bool SharpBounds = true;
[Tooltip("Automatically updates most of the volumes properties in runtime. Enabling/Disabling, Color and Intensity updates automatically even without this option enabled. Position, Rotation and Scale gets updated only for volumes that are marked dynamic.")]
public bool AutoUpdateVolumes = false;
[Tooltip("Limits the maximum number of additive volumes that can affect a single pixel. If you have many dynamic additive volumes that may overlap, it's good practice to limit overdraw to maintain performance.")]
public int AdditiveMaxOverdraw = 4;
[Tooltip("The minimum brightness at a point due to lighting from a Point Light Volume, before the light is culled. Larger values will result in better performance, but light attenuation will be less physically correct.")]
public float LightsBrightnessCutoff = 0.35f;
[Tooltip("All Light Volume instances sorted in decreasing order by weight. You can enable or disable volumes game objects at runtime. Manually disabling unnecessary volumes improves performance.")]
public LightVolumeInstance[] LightVolumeInstances = new LightVolumeInstance[0];
[Tooltip("All Point Light Volume instances. You can enable or disable point light volumes game objects at runtime. Manually disabling unnecessary point light volumes improves performance.")]
public PointLightVolumeInstance[] PointLightVolumeInstances = new PointLightVolumeInstance[0];
[Tooltip("A texture array that can be used for as Cubemaps, LUT or Cookies")]
public Texture CustomTextures;
[Tooltip("Cubemaps count that stored in CustomTextures. Cubemap array elements starts from the beginning, 6 elements each.")]
public int CubemapsCount = 0;
[HideInInspector]
public bool IsRangeDirty = false;
private bool _isInitialized = false;
private float _prevLightsBrightnessCutoff = 0.35f;
private Coroutine _updateCoroutine = null;
private int _enabledCount = 0;
private int _lastEnabledCount = -1;
private int _additiveCount = 0;
private int _occlusionCount = 0;
private Vector4[] _invLocalEdgeSmooth = new Vector4[0];
private Vector4[] _colors = new Vector4[0];
private Vector4[] _boundsUvwScale = new Vector4[0];
private Vector4[] _boundsOcclusionUvw = new Vector4[0];
private Vector4[] _relativeRotationQuaternion = new Vector4[0];
private int _pointLightCount = 0;
private int _lastPointLightCount = -1;
private int[] _enabledPointIDs = new Int32[128];
private Vector4[] _pointLightPosition;
private Vector4[] _pointLightColor;
private Vector4[] _pointLightDirection;
private Vector4[] _pointLightCustomId;
private Matrix4x4[] _invWorldMatrix = new Matrix4x4[0];
private Vector4[] _boundsUvw = new Vector4[0];
private Vector4[] _relativeRotation = new Vector4[0];
private int[] _enabledIDs = new Int32[32];
private Vector4[] _boundsScale = new Vector4[3];
private Vector4[] _bounds = new Vector4[6];
private int lightVolumeInvLocalEdgeSmoothID;
private int lightVolumeColorID;
private int lightVolumeCountID;
private int lightVolumeAdditiveCountID;
private int lightVolumeAdditiveMaxOverdrawID;
private int lightVolumeEnabledID;
private int lightVolumeVersionID;
private int lightVolumeProbesBlendID;
private int lightVolumeSharpBoundsID;
private int lightVolumeID;
private int lightVolumeRotationQuaternionID;
private int lightVolumeInvWorldMatrixID;
private int lightVolumeUvwScaleID;
private int lightVolumeOcclusionUvwID;
private int lightVolumeOcclusionCountID;
private int _pointLightPositionID;
private int _pointLightColorID;
private int _pointLightDirectionID;
private int _pointLightCustomIdID;
private int _pointLightCountID;
private int _pointLightCubeCountID;
private int _pointLightTextureID;
private int _lightBrightnessCutoffID;
private int _areaLightBrightnessCutoffID;
private int lightVolumeRotationID;
private int lightVolumeUvwID;
public int EnabledCount
{
get
{
return this._enabledCount;
}
}
public int[] EnabledIDs
{
get
{
return this._enabledIDs;
}
}
public LightVolumeManager()
{
}
public void InitializeLightVolume(LightVolumeInstance lightVolume)
{
int length = (int)this.LightVolumeInstances.Length;
int num = 0;
while (true)
{
if (num >= length)
{
LightVolumeInstance[] lightVolumeInstanceArray = new LightVolumeInstance[length + 1];
Array.Copy(this.LightVolumeInstances, lightVolumeInstanceArray, length);
lightVolumeInstanceArray[length] = lightVolume;
lightVolume.IsInitialized = true;
this.LightVolumeInstances = lightVolumeInstanceArray;
break;
}
else if (this.LightVolumeInstances[num] != null)
{
num++;
}
else
{
this.LightVolumeInstances[num] = lightVolume;
lightVolume.IsInitialized = true;
break;
}
}
}
public void InitializePointLightVolume(PointLightVolumeInstance pointLightVolume)
{
int length = (int)this.PointLightVolumeInstances.Length;
int num = 0;
while (true)
{
if (num >= length)
{
PointLightVolumeInstance[] pointLightVolumeInstanceArray = new PointLightVolumeInstance[length + 1];
Array.Copy(this.PointLightVolumeInstances, pointLightVolumeInstanceArray, length);
pointLightVolumeInstanceArray[length] = pointLightVolume;
pointLightVolume.IsInitialized = true;
this.PointLightVolumeInstances = pointLightVolumeInstanceArray;
break;
}
else if (this.PointLightVolumeInstances[num] != null)
{
num++;
}
else
{
this.PointLightVolumeInstances[num] = pointLightVolume;
pointLightVolume.IsInitialized = true;
break;
}
}
}
private void OnDisable()
{
this.TryInitialize();
if ((object)this._updateCoroutine != (object)null)
{
base.StopCoroutine(this._updateCoroutine);
this._updateCoroutine = null;
}
Shader.SetGlobalFloat(this.lightVolumeEnabledID, 0f);
}
private void OnEnable()
{
this.RequestUpdateVolumes();
}
public void RequestUpdateVolumes()
{
if ((this._updateCoroutine != null ? false : base.get_isActiveAndEnabled()))
{
this._updateCoroutine = base.StartCoroutine(this.UpdateVolumesCoroutine());
}
}
private void Start()
{
this._isInitialized = false;
this.UpdateVolumes();
}
private void TryInitialize()
{
if (!this._isInitialized)
{
this.lightVolumeInvLocalEdgeSmoothID = Shader.PropertyToID("_UdonLightVolumeInvLocalEdgeSmooth");
this.lightVolumeInvWorldMatrixID = Shader.PropertyToID("_UdonLightVolumeInvWorldMatrix");
this.lightVolumeColorID = Shader.PropertyToID("_UdonLightVolumeColor");
this.lightVolumeCountID = Shader.PropertyToID("_UdonLightVolumeCount");
this.lightVolumeAdditiveCountID = Shader.PropertyToID("_UdonLightVolumeAdditiveCount");
this.lightVolumeAdditiveMaxOverdrawID = Shader.PropertyToID("_UdonLightVolumeAdditiveMaxOverdraw");
this.lightVolumeEnabledID = Shader.PropertyToID("_UdonLightVolumeEnabled");
this.lightVolumeVersionID = Shader.PropertyToID("_UdonLightVolumeVersion");
this.lightVolumeProbesBlendID = Shader.PropertyToID("_UdonLightVolumeProbesBlend");
this.lightVolumeSharpBoundsID = Shader.PropertyToID("_UdonLightVolumeSharpBounds");
this.lightVolumeID = Shader.PropertyToID("_UdonLightVolume");
this.lightVolumeRotationQuaternionID = Shader.PropertyToID("_UdonLightVolumeRotationQuaternion");
this.lightVolumeUvwScaleID = Shader.PropertyToID("_UdonLightVolumeUvwScale");
this.lightVolumeOcclusionUvwID = Shader.PropertyToID("_UdonLightVolumeOcclusionUvw");
this.lightVolumeOcclusionCountID = Shader.PropertyToID("_UdonLightVolumeOcclusionCount");
this._pointLightPositionID = Shader.PropertyToID("_UdonPointLightVolumePosition");
this._pointLightColorID = Shader.PropertyToID("_UdonPointLightVolumeColor");
this._pointLightDirectionID = Shader.PropertyToID("_UdonPointLightVolumeDirection");
this._pointLightCountID = Shader.PropertyToID("_UdonPointLightVolumeCount");
this._pointLightCustomIdID = Shader.PropertyToID("_UdonPointLightVolumeCustomID");
this._pointLightCubeCountID = Shader.PropertyToID("_UdonPointLightVolumeCubeCount");
this._pointLightTextureID = Shader.PropertyToID("_UdonPointLightVolumeTexture");
this._lightBrightnessCutoffID = Shader.PropertyToID("_UdonLightBrightnessCutoff");
this._areaLightBrightnessCutoffID = Shader.PropertyToID("_UdonAreaLightBrightnessCutoff");
this.lightVolumeRotationID = Shader.PropertyToID("_UdonLightVolumeRotation");
this.lightVolumeUvwID = Shader.PropertyToID("_UdonLightVolumeUvw");
Shader.SetGlobalVectorArray(this.lightVolumeInvLocalEdgeSmoothID, new Vector4[32]);
Shader.SetGlobalVectorArray(this.lightVolumeColorID, new Vector4[32]);
Shader.SetGlobalMatrixArray(this.lightVolumeInvWorldMatrixID, new Matrix4x4[32]);
Shader.SetGlobalVectorArray(this.lightVolumeRotationQuaternionID, new Vector4[32]);
Shader.SetGlobalVectorArray(this.lightVolumeUvwScaleID, new Vector4[96]);
Shader.SetGlobalVectorArray(this.lightVolumeOcclusionUvwID, new Vector4[32]);
Shader.SetGlobalVectorArray(this._pointLightPositionID, new Vector4[128]);
Shader.SetGlobalVectorArray(this._pointLightColorID, new Vector4[128]);
Shader.SetGlobalVectorArray(this._pointLightDirectionID, new Vector4[128]);
Shader.SetGlobalVectorArray(this._pointLightCustomIdID, new Vector4[128]);
Shader.SetGlobalVectorArray(this.lightVolumeRotationID, new Vector4[64]);
Shader.SetGlobalVectorArray(this.lightVolumeUvwID, new Vector4[192]);
this._isInitialized = true;
}
}
public void UpdateVolumes()
{
this.TryInitialize();
if ((!base.get_enabled() ? false : base.get_gameObject().get_activeInHierarchy()))
{
if (this._prevLightsBrightnessCutoff != this.LightsBrightnessCutoff)
{
this._prevLightsBrightnessCutoff = this.LightsBrightnessCutoff;
this.IsRangeDirty = true;
}
this._enabledCount = 0;
this._additiveCount = 0;
this._occlusionCount = 0;
int num = 0;
while (true)
{
if ((num >= (int)this.LightVolumeInstances.Length ? true : this._enabledCount >= 32))
{
break;
}
LightVolumeInstance lightVolumeInstances = this.LightVolumeInstances[num];
if (lightVolumeInstances != null)
{
if ((!lightVolumeInstances.get_gameObject().get_activeInHierarchy() || lightVolumeInstances.Intensity == 0f || !(lightVolumeInstances.Color != Color.get_black()) ? true : lightVolumeInstances.IsIterartedThrough))
{
lightVolumeInstances.IsIterartedThrough = false;
}
else
{
if (!Application.get_isPlaying())
{
lightVolumeInstances.UpdateTransform();
}
else if (lightVolumeInstances.IsDynamic)
{
lightVolumeInstances.UpdateTransform();
}
if (lightVolumeInstances.IsAdditive)
{
this._additiveCount++;
}
if (lightVolumeInstances.BakeOcclusion)
{
this._occlusionCount++;
}
this._enabledIDs[this._enabledCount] = num;
this._enabledCount++;
lightVolumeInstances.IsIterartedThrough = true;
}
}
num++;
}
if (this._enabledCount != this._lastEnabledCount)
{
this._invLocalEdgeSmooth = new Vector4[this._enabledCount];
this._relativeRotationQuaternion = new Vector4[this._enabledCount];
this._boundsUvwScale = new Vector4[this._enabledCount * 3];
this._boundsOcclusionUvw = new Vector4[this._enabledCount];
this._colors = new Vector4[this._enabledCount];
this._invWorldMatrix = new Matrix4x4[this._enabledCount];
this._relativeRotation = new Vector4[this._enabledCount * 2];
this._boundsUvw = new Vector4[this._enabledCount * 6];
this._lastEnabledCount = this._enabledCount;
}
for (int i = 0; i < this._enabledCount; i++)
{
int num1 = this._enabledIDs[i];
int num2 = i * 2;
int num3 = i * 3;
int num4 = i * 6;
LightVolumeInstance lightVolumeInstance = this.LightVolumeInstances[num1];
lightVolumeInstance.IsIterartedThrough = false;
this._invWorldMatrix[i] = lightVolumeInstance.InvWorldMatrix;
this._invLocalEdgeSmooth[i] = lightVolumeInstance.InvLocalEdgeSmoothing;
Vector4 _linear = lightVolumeInstance.Color.get_linear() * lightVolumeInstance.Intensity;
_linear.w = (float)((lightVolumeInstance.IsRotated ? 1 : 0));
this._colors[i] = _linear;
this._relativeRotationQuaternion[i] = lightVolumeInstance.RelativeRotation;
this._relativeRotation[num2] = lightVolumeInstance.RelativeRotationRow0;
this._relativeRotation[num2 + 1] = lightVolumeInstance.RelativeRotationRow1;
this._boundsScale[0] = lightVolumeInstance.BoundsUvwMin0;
this._boundsScale[1] = lightVolumeInstance.BoundsUvwMin1;
this._boundsScale[2] = lightVolumeInstance.BoundsUvwMin2;
this._boundsOcclusionUvw[i] = (lightVolumeInstance.BakeOcclusion ? lightVolumeInstance.BoundsUvwMinOcclusion : -Vector4.get_one());
this._bounds[0] = lightVolumeInstance.BoundsUvwMin0;
this._bounds[1] = lightVolumeInstance.BoundsUvwMax0;
this._bounds[2] = lightVolumeInstance.BoundsUvwMin1;
this._bounds[3] = lightVolumeInstance.BoundsUvwMax1;
this._bounds[4] = lightVolumeInstance.BoundsUvwMin2;
this._bounds[5] = lightVolumeInstance.BoundsUvwMax2;
Array.Copy(this._boundsScale, 0, this._boundsUvwScale, num3, 3);
Array.Copy(this._bounds, 0, this._boundsUvw, num4, 6);
}
this._pointLightCount = 0;
int num5 = 0;
while (true)
{
if ((num5 >= (int)this.PointLightVolumeInstances.Length ? true : this._pointLightCount >= 128))
{
break;
}
PointLightVolumeInstance pointLightVolumeInstances = this.PointLightVolumeInstances[num5];
if (pointLightVolumeInstances != null)
{
if (this.IsRangeDirty)
{
pointLightVolumeInstances.UpdateRange();
}
if ((!pointLightVolumeInstances.get_gameObject().get_activeInHierarchy() || pointLightVolumeInstances.Intensity == 0f || !(pointLightVolumeInstances.Color != Color.get_black()) ? true : pointLightVolumeInstances.IsIterartedThrough))
{
pointLightVolumeInstances.IsIterartedThrough = false;
}
else
{
if (!Application.get_isPlaying())
{
pointLightVolumeInstances.UpdateTransform();
}
else if (pointLightVolumeInstances.IsDynamic)
{
pointLightVolumeInstances.UpdateTransform();
}
this._enabledPointIDs[this._pointLightCount] = num5;
this._pointLightCount++;
pointLightVolumeInstances.IsIterartedThrough = true;
}
}
num5++;
}
this.IsRangeDirty = false;
if (this._pointLightCount != this._lastPointLightCount)
{
this._pointLightPosition = new Vector4[this._pointLightCount];
this._pointLightColor = new Vector4[this._pointLightCount];
this._pointLightDirection = new Vector4[this._pointLightCount];
this._pointLightCustomId = new Vector4[this._pointLightCount];
this._lastPointLightCount = this._pointLightCount;
}
for (int j = 0; j < this._pointLightCount; j++)
{
PointLightVolumeInstance pointLightVolumeInstance = this.PointLightVolumeInstances[this._enabledPointIDs[j]];
if ((this.IsRangeDirty ? true : pointLightVolumeInstance.IsRangeDirty))
{
pointLightVolumeInstance.UpdateRange();
}
pointLightVolumeInstance.IsIterartedThrough = false;
Vector4 positionData = pointLightVolumeInstance.PositionData;
if (!pointLightVolumeInstance.IsAreaLight())
{
if (!pointLightVolumeInstance.IsLut())
{
positionData.w *= pointLightVolumeInstance.SquaredScale;
}
else
{
positionData.w /= pointLightVolumeInstance.SquaredScale;
}
}
this._pointLightPosition[j] = positionData;
Vector4 angleData = pointLightVolumeInstance.Color.get_linear() * pointLightVolumeInstance.Intensity;
angleData.w = pointLightVolumeInstance.AngleData;
this._pointLightColor[j] = angleData;
this._pointLightDirection[j] = pointLightVolumeInstance.DirectionData;
this._pointLightCustomId[j].x = pointLightVolumeInstance.CustomID;
this._pointLightCustomId[j].y = (float)pointLightVolumeInstance.ShadowmaskIndex;
this._pointLightCustomId[j].z = pointLightVolumeInstance.SquaredRange;
}
bool lightVolumeAtlas = this.LightVolumeAtlas != null;
Shader.SetGlobalFloat(this.lightVolumeVersionID, 2f);
if ((!lightVolumeAtlas || this._enabledCount == 0 ? this._pointLightCount != 0 : true))
{
if (lightVolumeAtlas)
{
Shader.SetGlobalTexture(this.lightVolumeID, this.LightVolumeAtlas);
}
Shader.SetGlobalFloat(this.lightVolumeCountID, (float)this._enabledCount);
Shader.SetGlobalFloat(this.lightVolumeAdditiveCountID, (float)this._additiveCount);
Shader.SetGlobalFloat(this.lightVolumeOcclusionCountID, (float)this._occlusionCount);
Shader.SetGlobalFloat(this.lightVolumeProbesBlendID, (float)((this.LightProbesBlending ? 1 : 0)));
Shader.SetGlobalFloat(this.lightVolumeSharpBoundsID, (float)((this.SharpBounds ? 1 : 0)));
Shader.SetGlobalFloat(this.lightVolumeAdditiveMaxOverdrawID, (float)this.AdditiveMaxOverdraw);
if (this._enabledCount != 0)
{
Shader.SetGlobalVectorArray(this.lightVolumeInvLocalEdgeSmoothID, this._invLocalEdgeSmooth);
Shader.SetGlobalVectorArray(this.lightVolumeUvwScaleID, this._boundsUvwScale);
Shader.SetGlobalVectorArray(this.lightVolumeOcclusionUvwID, this._boundsOcclusionUvw);
Shader.SetGlobalMatrixArray(this.lightVolumeInvWorldMatrixID, this._invWorldMatrix);
Shader.SetGlobalVectorArray(this.lightVolumeRotationQuaternionID, this._relativeRotationQuaternion);
Shader.SetGlobalVectorArray(this.lightVolumeColorID, this._colors);
Shader.SetGlobalVectorArray(this.lightVolumeUvwID, this._boundsUvw);
Shader.SetGlobalVectorArray(this.lightVolumeRotationID, this._relativeRotation);
}
Shader.SetGlobalFloat(this._pointLightCountID, (float)this._pointLightCount);
Shader.SetGlobalFloat(this._pointLightCubeCountID, (float)this.CubemapsCount);
if (this._pointLightCount != 0)
{
Shader.SetGlobalVectorArray(this._pointLightColorID, this._pointLightColor);
Shader.SetGlobalVectorArray(this._pointLightPositionID, this._pointLightPosition);
Shader.SetGlobalVectorArray(this._pointLightDirectionID, this._pointLightDirection);
Shader.SetGlobalVectorArray(this._pointLightCustomIdID, this._pointLightCustomId);
Shader.SetGlobalFloat(this._lightBrightnessCutoffID, this.LightsBrightnessCutoff);
Shader.SetGlobalFloat(this._areaLightBrightnessCutoffID, this.LightsBrightnessCutoff);
}
if (this.CustomTextures != null)
{
Shader.SetGlobalTexture(this._pointLightTextureID, this.CustomTextures);
}
Shader.SetGlobalFloat(this.lightVolumeEnabledID, 1f);
}
else
{
Shader.SetGlobalFloat(this.lightVolumeEnabledID, 0f);
}
}
else
{
Shader.SetGlobalFloat(this.lightVolumeEnabledID, 0f);
}
}
private IEnumerator UpdateVolumesCoroutine()
{
do
{
yield return null;
this.UpdateVolumes();
}
while (this.AutoUpdateVolumes);
this._updateCoroutine = null;
}
}
}