feat(render): Phase G.1/G.2 — SceneLighting UBO + sky renderer + shader integration
Wire the existing LightManager + WorldTimeService state into visible
rendering. Every draw call (terrain, static mesh, instanced mesh, sky)
now shares one SceneLighting UBO at binding=1 carrying:
- 8 Light slots (Directional / Point / Spot, retail hard-cutoff)
- Ambient RGB + active light count
- Fog start/end/mode + color + lightning flash scalar
- Camera world position + day fraction
The CPU side (SceneLightingUbo in Core.Lighting) is a POD struct that
gets BufferSubData'd once per frame from GameWindow.OnRender. Shaders
read the block via `layout(std140, binding = 1) uniform SceneLighting`
— no per-program uniform uploads.
Shader changes:
- mesh.frag + mesh_instanced.frag accumulate 8 dynamic lights per
fragment using the retail no-attenuation hard-cutoff model
(r13 §10.2 / §13.1). Sun reads slot 0; spots use hard cos-cone test.
Additive lightning flash + linear fog layered on top. Saturate
clamps per-channel to 1.0.
- terrain.vert bakes AdjustPlanes sun+ambient per vertex using the
retail MIN_FACTOR = 0.08 ambient floor (r13 §7). terrain.frag adds
fog + flash on top of the baked vertex color.
- mesh.vert + mesh_instanced.vert emit vWorldPos so the fragment
stage can do per-pixel lighting against world-space positions.
- New sky.vert / sky.frag pair — unlit, scroll-UV, camera-centered,
with its own 0.1..1e6 far plane. Ports WorldBuilder's skybox.
SkyRenderer (new file in App/Rendering/Sky/) ports WorldBuilder's
SkyboxRenderManager verbatim for the C# idiom: zeroed view translation,
dedicated projection, depth mask off, iterate each visible SkyObject
in the day group, apply arc transform (Z rot for heading + Y rot for
arc sweep), feed TexVelocityX/Y as a scrolling UV offset, apply
per-keyframe SkyObjectReplace overrides (mesh swap + transparency +
luminosity) for overcast / dusk cloud variants.
GameWindow integration:
- OnLoad parses Region (0x13000000) into LoadedSkyDesc and hot-swaps
WorldTime's provider to the dat-accurate keyframes. Seeds to noon
for offline rendering. Creates the SceneLightingUboBinding and the
SkyRenderer.
- OnRender: set clear color from atmosphere fog, tick WeatherSystem,
spawn/stop rain/snow camera-local emitters on kind change, feed
sun to LightManager (zero intensity indoors — r13 §13.7), tick
LightManager against viewer pos, build + upload the UBO, draw
sky before terrain, draw terrain + static + instanced using the
shared UBO.
5 new UBO packing tests (struct sizes, slot population, 8-light cap,
directional slot 0).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0df1c5b4a6
commit
9957070cab
15 changed files with 1255 additions and 91 deletions
67
src/AcDream.App/Rendering/SceneLightingUboBinding.cs
Normal file
67
src/AcDream.App/Rendering/SceneLightingUboBinding.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using AcDream.Core.Lighting;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// GL wrapper that owns the SceneLighting UBO buffer, updates its
|
||||
/// contents each frame, and keeps it bound at binding=1 so every
|
||||
/// shader sampling <c>uLights[]</c> / <c>uFogColor</c> / etc reads
|
||||
/// consistent data without per-shader re-upload.
|
||||
///
|
||||
/// <para>
|
||||
/// Usage (r12 §13.2 + r13 §12.3):
|
||||
/// <list type="number">
|
||||
/// <item><description>Instantiate once at startup, after the GL context exists.</description></item>
|
||||
/// <item><description>Each frame, after <see cref="LightManager.Tick"/>, call <see cref="Upload"/> with a freshly-built <see cref="SceneLightingUbo"/>.</description></item>
|
||||
/// <item><description>Shader programs that declare <c>layout(std140, binding = 1) uniform SceneLighting { ... }</c> automatically pick up the data.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed unsafe class SceneLightingUboBinding : IDisposable
|
||||
{
|
||||
private readonly GL _gl;
|
||||
private readonly uint _ubo;
|
||||
private bool _disposed;
|
||||
|
||||
public SceneLightingUboBinding(GL gl)
|
||||
{
|
||||
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
_ubo = _gl.GenBuffer();
|
||||
_gl.BindBuffer(BufferTargetARB.UniformBuffer, _ubo);
|
||||
// Pre-allocate with the final size; BufferSubData each frame.
|
||||
_gl.BufferData(
|
||||
BufferTargetARB.UniformBuffer,
|
||||
(nuint)SceneLightingUbo.SizeInBytes,
|
||||
(void*)0,
|
||||
BufferUsageARB.DynamicDraw);
|
||||
_gl.BindBuffer(BufferTargetARB.UniformBuffer, 0);
|
||||
|
||||
// Bind the buffer to the chosen binding point exactly once — shaders
|
||||
// that declare this binding in their layout block will read from it
|
||||
// on every draw without further intervention.
|
||||
_gl.BindBufferBase(BufferTargetARB.UniformBuffer,
|
||||
SceneLightingUbo.BindingPoint, _ubo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push the current frame's UBO contents to the GPU. Cheap (576 bytes)
|
||||
/// so fine to call unconditionally every frame.
|
||||
/// </summary>
|
||||
public void Upload(SceneLightingUbo data)
|
||||
{
|
||||
_gl.BindBuffer(BufferTargetARB.UniformBuffer, _ubo);
|
||||
_gl.BufferSubData(BufferTargetARB.UniformBuffer,
|
||||
(nint)0, (nuint)SceneLightingUbo.SizeInBytes, &data);
|
||||
_gl.BindBuffer(BufferTargetARB.UniformBuffer, 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_gl.DeleteBuffer(_ubo);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue