using System; using System.Numerics; using System.Runtime.InteropServices; using AcDream.Core.World; namespace AcDream.Core.Lighting; /// /// GPU-facing scene-lighting UBO layout. Matches the GLSL block in /// mesh.frag / mesh_instanced.vert / terrain.vert /// bound at binding=1. std140-compliant — each vec4 member /// lives on a 16-byte boundary, arrays of vec4 pack contiguously, /// and no pad elements are required because the struct's fields are /// already 16-byte-aligned. /// /// /// Layout (r13 §12.3 extended with R12 §13.2 sun+fog): /// /// struct Light { /// vec4 posAndKind; // xyz = world pos, w = kind (0=dir, 1=point, 2=spot) /// vec4 dirAndRange; // xyz = forward, w = range (metres, hard cutoff) /// vec4 colorAndIntensity; // xyz = RGB linear, w = intensity scalar /// vec4 coneAngleEtc; // x = cone (rad), y=unused, z=unused, w=unused /// }; /// /// layout(std140, binding = 1) uniform SceneLighting { /// Light uLights[8]; // 8 * 64 bytes = 512 bytes /// vec4 uCellAmbient; // xyz = ambient RGB, w = active light count /// vec4 uFogParams; // x = fogStart, y = fogEnd, z = lightningFlash, w = fogMode /// vec4 uFogColor; // xyz = fog RGB, w = unused /// vec4 uCameraAndTime; // xyz = camera world pos, w = day fraction (debug / sky shader) /// }; /// /// /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct UboLight { public Vector4 PosAndKind; public Vector4 DirAndRange; public Vector4 ColorAndIntensity; public Vector4 ConeAngleEtc; /// Pack a into UBO-ready bytes. public static UboLight FromSource(LightSource ls) { return new UboLight { PosAndKind = new Vector4(ls.WorldPosition, (float)(int)ls.Kind), DirAndRange = new Vector4(ls.WorldForward, ls.Range), ColorAndIntensity = new Vector4(ls.ColorLinear, ls.Intensity), ConeAngleEtc = new Vector4(ls.ConeAngle, 0f, 0f, 0f), }; } /// Packed "zero" light — stored in unused UBO slots so shaders /// don't read garbage. dirAndRange.w = 0 disables the light /// even if the active-count sentinel is wrong. public static UboLight Empty => new() { PosAndKind = Vector4.Zero, DirAndRange = Vector4.Zero, ColorAndIntensity = Vector4.Zero, ConeAngleEtc = Vector4.Zero, }; } /// /// Full CPU-side scene-lighting UBO buffer. One per frame; lives on the /// render thread. The GL-side wrapper (SceneLightingUboBinding /// in AcDream.App) uploads this to binding=1 once per frame. /// [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct SceneLightingUbo { // 8 lights × 64 bytes = 512 bytes public UboLight Light0; public UboLight Light1; public UboLight Light2; public UboLight Light3; public UboLight Light4; public UboLight Light5; public UboLight Light6; public UboLight Light7; public Vector4 CellAmbient; // xyz = ambient RGB, w = active count public Vector4 FogParams; // x = fogStart, y = fogEnd, z = flash, w = fogMode public Vector4 FogColor; // xyz = color, w = reserved public Vector4 CameraAndTime; // xyz = camera pos, w = day fraction public const int SizeInBytes = 8 * 64 + 4 * 16; // 576 public const int BindingPoint = 1; /// /// Build the full per-frame UBO payload from: /// /// An already-ticked . /// The current (sky + weather). /// The current camera world position (sky shader needs it, fog shader needs it). /// The current day fraction (sky shader needs it for scrolling clouds). /// /// public static SceneLightingUbo Build( LightManager lights, in AtmosphereSnapshot atmo, Vector3 cameraWorldPos, float dayFraction) { ArgumentNullException.ThrowIfNull(lights); var ubo = new SceneLightingUbo(); // Pack up to 8 lights. Empty slots stay zero. var active = lights.Active; int count = active.Length; if (count > 8) count = 8; for (int i = 0; i < 8; i++) { var packed = (i < count && active[i] is not null) ? UboLight.FromSource(active[i]!) : UboLight.Empty; SetLightAt(ref ubo, i, packed); } ubo.CellAmbient = new Vector4(lights.CurrentAmbient.AmbientColor, count); ubo.FogParams = new Vector4( atmo.FogStart, atmo.FogEnd, atmo.LightningFlash, (float)(int)atmo.FogMode); ubo.FogColor = new Vector4(atmo.FogColor, 0f); ubo.CameraAndTime = new Vector4(cameraWorldPos, dayFraction); return ubo; } private static void SetLightAt(ref SceneLightingUbo ubo, int i, in UboLight v) { switch (i) { case 0: ubo.Light0 = v; break; case 1: ubo.Light1 = v; break; case 2: ubo.Light2 = v; break; case 3: ubo.Light3 = v; break; case 4: ubo.Light4 = v; break; case 5: ubo.Light5 = v; break; case 6: ubo.Light6 = v; break; case 7: ubo.Light7 = v; break; } } }