merge: A7 lighting (Fix A point-light shape + Fix B per-object selection) into main
Brings the worktree branch claude/thirsty-goldberg-51bb9b into main: -aa94cedper-vertex Gouraud + faithful calc_point_light (wrap + norm) -4345e77per-OBJECT point-light selection (minimize_object_lighting) Auto-merged cleanly against the D.2b retail-UI line (only GameWindow.cs overlapped, resolved by git). Merged tree builds green; 35/35 Core lighting tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
37911ed510
7 changed files with 517 additions and 42 deletions
|
|
@ -157,4 +157,125 @@ public sealed class LightManager
|
|||
|
||||
_activeCount = baseSlot + filled;
|
||||
}
|
||||
|
||||
// ── Fix B (A7 #3): per-OBJECT light selection — minimize_object_lighting ──
|
||||
//
|
||||
// The single global nearest-8-to-VIEWER set above (Tick) is camera-relative:
|
||||
// a wall's brightness changes as the camera moves because the wall's torches
|
||||
// swap in/out of that global top-8. Retail instead picks up-to-8 lights PER
|
||||
// OBJECT by the OBJECT's own position (minimize_object_lighting, 0x0054d480),
|
||||
// so a torch always lights the wall it sits on, camera-independent. The two
|
||||
// members below feed the per-instance light path in WbDrawDispatcher; Tick
|
||||
// remains the source of the legacy single-UBO path + the sun slot.
|
||||
|
||||
/// <summary>Max point/spot lights any one object can be lit by — retail's
|
||||
/// D3D fixed-function 8-light cap (<c>minimize_object_lighting</c>). The sun
|
||||
/// is global, not part of an object's per-object set, so all 8 are point/spot.</summary>
|
||||
public const int MaxLightsPerObject = 8;
|
||||
|
||||
/// <summary>Hard cap on the per-frame global point-light snapshot the shader
|
||||
/// indexes. AC scenes rarely exceed a few dozen lit point lights in view; 128
|
||||
/// is generous. If exceeded, the nearest-to-camera are kept (cold path).</summary>
|
||||
public const int MaxGlobalLights = 128;
|
||||
|
||||
private readonly List<LightSource> _pointSnapshot = new();
|
||||
|
||||
/// <summary>
|
||||
/// Per-frame snapshot of lit point/spot lights, stable-indexed for the global
|
||||
/// shader light buffer and for per-object selection: the index of a light here
|
||||
/// IS the index the per-instance light-set SSBO references. Built by
|
||||
/// <see cref="BuildPointLightSnapshot"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<LightSource> PointSnapshot => _pointSnapshot;
|
||||
|
||||
/// <summary>
|
||||
/// Rebuild <see cref="PointSnapshot"/> from the registered lit point/spot
|
||||
/// lights. The sun and unlit lights are excluded (the sun is global ambient-
|
||||
/// path; unlit torches contribute nothing). When more than
|
||||
/// <see cref="MaxGlobalLights"/> qualify, keeps the nearest the camera so the
|
||||
/// most relevant lights survive the cap. Call once per frame before
|
||||
/// per-object selection.
|
||||
/// </summary>
|
||||
public void BuildPointLightSnapshot(Vector3 cameraWorldPos)
|
||||
{
|
||||
_pointSnapshot.Clear();
|
||||
foreach (var light in _all)
|
||||
{
|
||||
if (!light.IsLit || light.Kind == LightKind.Directional) continue;
|
||||
light.DistSq = (light.WorldPosition - cameraWorldPos).LengthSquared();
|
||||
_pointSnapshot.Add(light);
|
||||
}
|
||||
if (_pointSnapshot.Count > MaxGlobalLights)
|
||||
{
|
||||
_pointSnapshot.Sort(static (a, b) => a.DistSq.CompareTo(b.DistSq));
|
||||
_pointSnapshot.RemoveRange(MaxGlobalLights, _pointSnapshot.Count - MaxGlobalLights);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Select up to <see cref="MaxLightsPerObject"/> point/spot lights from
|
||||
/// <paramref name="snapshot"/> that reach the object sphere
|
||||
/// (<paramref name="center"/>, <paramref name="radius"/>), nearest-first.
|
||||
/// Faithful to retail's <c>minimize_object_lighting</c> (0x0054d480): a light
|
||||
/// is a candidate iff its falloff sphere overlaps the object sphere —
|
||||
/// <c>(light.pos − center)² < (light.Range + radius)²</c> — and when more
|
||||
/// than 8 candidates qualify, the 8 NEAREST the object centre are kept (the
|
||||
/// farthest fall off). <paramref name="light.Range"/> already folds
|
||||
/// <c>static_light_factor</c> (1.3), matching the per-vertex cutoff so a
|
||||
/// selected light always actually contributes in the shader.
|
||||
/// <para>
|
||||
/// Writes indices INTO <paramref name="snapshot"/> to
|
||||
/// <paramref name="outIndices"/> (ascending by distance) and returns the count.
|
||||
/// Pure + static: camera-INDEPENDENT (depends only on the object centre), so a
|
||||
/// static object's set is stable and may be computed once. Unit-testable
|
||||
/// without GL.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static int SelectForObject(
|
||||
IReadOnlyList<LightSource> snapshot,
|
||||
Vector3 center,
|
||||
float radius,
|
||||
Span<int> outIndices)
|
||||
{
|
||||
int cap = Math.Min(outIndices.Length, MaxLightsPerObject);
|
||||
if (cap <= 0) return 0;
|
||||
|
||||
Span<float> keptDistSq = stackalloc float[MaxLightsPerObject];
|
||||
int count = 0;
|
||||
|
||||
for (int li = 0; li < snapshot.Count; li++)
|
||||
{
|
||||
var light = snapshot[li];
|
||||
float reach = light.Range + radius;
|
||||
float dsq = (light.WorldPosition - center).LengthSquared();
|
||||
if (dsq >= reach * reach) continue; // light's sphere doesn't reach the object
|
||||
|
||||
if (count < cap)
|
||||
{
|
||||
int j = count;
|
||||
while (j > 0 && keptDistSq[j - 1] > dsq)
|
||||
{
|
||||
keptDistSq[j] = keptDistSq[j - 1];
|
||||
outIndices[j] = outIndices[j - 1];
|
||||
j--;
|
||||
}
|
||||
keptDistSq[j] = dsq;
|
||||
outIndices[j] = li;
|
||||
count++;
|
||||
}
|
||||
else if (dsq < keptDistSq[cap - 1])
|
||||
{
|
||||
int j = cap - 1;
|
||||
while (j > 0 && keptDistSq[j - 1] > dsq)
|
||||
{
|
||||
keptDistSq[j] = keptDistSq[j - 1];
|
||||
outIndices[j] = outIndices[j - 1];
|
||||
j--;
|
||||
}
|
||||
keptDistSq[j] = dsq;
|
||||
outIndices[j] = li;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue