Files
2026-04-16 04:58:10 +09:00

301 lines
11 KiB
C#

using System.Collections.Generic;
using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
namespace BadDog.Rendering.AreaLight
{
/// <summary>
/// Per-light area light data sent to shader.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct BGAreaLightRenderingData
{
public Vector4 positionWS; // float4 (xyz: position, w: isAreaLight flag, 1.0=area light, 0.0=not area light)
public Vector4 right; // float4 (matches GPU float3 + padding)
public Vector4 up; // float4 (matches GPU float3 + padding)
public Vector4 forward; // float4 (matches GPU float3 + padding)
public Vector4 size; // float4 (matches GPU float2 + padding)
public Vector4 colorIntensity; // float4 (matches GPU float3 color + float intensity)
public Vector4 rangeParams; // float4 (range, rangeAttenuationScale, rangeAttenuationBias, isRectLight)
public Vector4 renderingLayerMask; // float4 (x stores uint bitmask via bit reinterpretation)
}
/// <summary>
/// Static manager for all BGAreaLight components in the scene.
/// </summary>
public static class BGAreaLightManager
{
static Dictionary<Light, BGAreaLight> s_AreaLightDictionary = new Dictionary<Light, BGAreaLight>();
static NativeArray<BGAreaLightRenderingData> s_AreaLightRenderingDataArray;
static GraphicsBuffer s_AreaLightDataBuffer = null;
static GraphicsBuffer s_EmptyBuffer = null;
static readonly Dictionary<Light, int> s_LightToAreaIndex = new Dictionary<Light, int>();
#if UNITY_EDITOR
[UnityEditor.InitializeOnLoadMethod]
#else
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#endif
static void Initialize()
{
Cleanup();
}
static BGAreaLightManager()
{
Application.quitting -= Cleanup;
Application.quitting += Cleanup;
}
public static void RegisterAreaLight(BGAreaLight areaLight)
{
if (areaLight == null)
{
return;
}
Light light = areaLight.GetLight();
if (light == null)
{
Debug.LogWarning("BGAreaLight component does not have a Light component attached.", areaLight);
return;
}
if (s_AreaLightDictionary.ContainsKey(light))
{
return;
}
s_AreaLightDictionary.Add(light, areaLight);
}
public static void UnRegisterAreaLight(BGAreaLight areaLight)
{
if (areaLight == null)
{
return;
}
Light light = areaLight.GetLight();
if (light != null && s_AreaLightDictionary.ContainsKey(light))
{
s_AreaLightDictionary.Remove(light);
}
}
private static bool TryGetAreaLight(Light light, out BGAreaLight areaLight)
{
return TryGetRegisteredAreaLight(light, out areaLight);
}
internal static bool TryGetRegisteredAreaLight(Light light, out BGAreaLight areaLight)
{
if (light == null)
{
areaLight = null;
return false;
}
return s_AreaLightDictionary.TryGetValue(light, out areaLight);
}
public static NativeArray<BGAreaLightRenderingData> BuildAreaLightDataArray(NativeArray<VisibleLight> visibleLights, int mainLightIndex, int maxAreaLights = 8)
{
s_LightToAreaIndex.Clear();
int visibleCount = visibleLights.Length;
int areaLightCount = 0;
for (int i = 0; i < visibleCount && areaLightCount < maxAreaLights; ++i)
{
if (i == mainLightIndex)
{
continue;
}
Light light = visibleLights[i].light;
if (light != null && light.type == LightType.Rectangle && TryGetAreaLight(light, out _))
{
areaLightCount++;
}
}
if (areaLightCount <= 0)
{
if (s_AreaLightRenderingDataArray.IsCreated)
{
s_AreaLightRenderingDataArray.Dispose();
}
return default;
}
if (!s_AreaLightRenderingDataArray.IsCreated || s_AreaLightRenderingDataArray.Length != areaLightCount)
{
if (s_AreaLightRenderingDataArray.IsCreated)
{
s_AreaLightRenderingDataArray.Dispose();
}
s_AreaLightRenderingDataArray = new NativeArray<BGAreaLightRenderingData>(
areaLightCount,
Allocator.Persistent,
NativeArrayOptions.UninitializedMemory);
}
BGAreaLightRenderingData defaultData = new BGAreaLightRenderingData
{
positionWS = Vector4.zero,
right = new Vector4(1, 0, 0, 0),
up = new Vector4(0, 1, 0, 0),
forward = new Vector4(0, 0, 1, 0),
size = Vector4.zero,
colorIntensity = Vector4.zero,
rangeParams = new Vector4(0, 1, 0, 0),
renderingLayerMask = Vector4.zero
};
int areaIndex = 0;
for (int i = 0; i < visibleCount && areaIndex < areaLightCount; ++i)
{
if (i == mainLightIndex)
{
continue;
}
Light light = visibleLights[i].light;
if (light == null || light.type != LightType.Rectangle || !TryGetAreaLight(light, out BGAreaLight areaLight))
{
continue;
}
BGAreaLightRenderingData renderData = defaultData;
// Mark as area light by default
Transform lightTransform = light.transform;
Vector3 pos = lightTransform.position;
renderData.positionWS = new Vector4(pos.x, pos.y, pos.z, 1.0f);
Vector3 rightVec = lightTransform.right.normalized;
Vector3 upVec = lightTransform.up.normalized;
Vector3 forwardVec = lightTransform.forward.normalized;
renderData.right = new Vector4(rightVec.x, rightVec.y, rightVec.z, 0);
renderData.up = new Vector4(upVec.x, upVec.y, upVec.z, 0);
renderData.forward = new Vector4(forwardVec.x, forwardVec.y, forwardVec.z, 0);
Vector2 size = areaLight.GetSize();
float attenuationScale = areaLight.GetRangeAttenuationScale();
float attenuationBias = areaLight.GetRangeAttenuationBias();
float isRect = areaLight.GetIsRectLight() ? 1f : 0f;
renderData.size = new Vector4(size.x, size.y, 0, 0);
Color linearColor = light.color.linear;
renderData.colorIntensity = new Vector4(linearColor.r, linearColor.g, linearColor.b, light.intensity);
renderData.rangeParams = new Vector4(
light.range,
attenuationScale,
attenuationBias,
isRect);
renderData.renderingLayerMask = new Vector4(math.asfloat((uint)light.renderingLayerMask), 0f, 0f, 0f);
s_AreaLightRenderingDataArray[areaIndex] = renderData;
s_LightToAreaIndex[light] = areaIndex;
areaIndex++;
}
return s_AreaLightRenderingDataArray;
}
public static unsafe GraphicsBuffer UpdateGraphicsBuffer(NativeArray<BGAreaLightRenderingData> areaLightDataArray)
{
if (!areaLightDataArray.IsCreated || areaLightDataArray.Length == 0)
{
CleanupGraphicsBuffer();
return null;
}
int count = areaLightDataArray.Length;
int structSize = UnsafeUtility.SizeOf<BGAreaLightRenderingData>();
if (s_AreaLightDataBuffer == null || !s_AreaLightDataBuffer.IsValid() || s_AreaLightDataBuffer.count != count)
{
CleanupGraphicsBuffer();
s_AreaLightDataBuffer = new GraphicsBuffer(
GraphicsBuffer.Target.Structured,
count,
structSize);
}
s_AreaLightDataBuffer.SetData(areaLightDataArray);
return s_AreaLightDataBuffer;
}
public static GraphicsBuffer GetEmptyBuffer()
{
if (s_EmptyBuffer == null || !s_EmptyBuffer.IsValid())
{
s_EmptyBuffer = new GraphicsBuffer(
GraphicsBuffer.Target.Structured,
1,
UnsafeUtility.SizeOf<BGAreaLightRenderingData>());
var defaultData = new BGAreaLightRenderingData
{
positionWS = Vector4.zero, // w=0 means not an area light
right = new Vector4(1, 0, 0, 0),
up = new Vector4(0, 1, 0, 0),
forward = new Vector4(0, 0, 1, 0),
size = Vector4.zero,
colorIntensity = Vector4.zero, // intensity = 0 (w component)
rangeParams = new Vector4(0, 1, 0, 0), // range=0, scale=1, bias=0, isRect=0
renderingLayerMask = Vector4.zero
};
s_EmptyBuffer.SetData(new[] { defaultData });
}
return s_EmptyBuffer;
}
static void CleanupGraphicsBuffer()
{
if (s_AreaLightDataBuffer != null && s_AreaLightDataBuffer.IsValid())
{
s_AreaLightDataBuffer.Release();
s_AreaLightDataBuffer = null;
}
if (s_EmptyBuffer != null && s_EmptyBuffer.IsValid())
{
s_EmptyBuffer.Release();
s_EmptyBuffer = null;
}
}
static void Cleanup()
{
s_AreaLightDictionary.Clear();
if (s_AreaLightRenderingDataArray.IsCreated)
{
s_AreaLightRenderingDataArray.Dispose();
}
CleanupGraphicsBuffer();
}
public static void Clear()
{
Cleanup();
}
}
}