Lighting: directional sun shadows from the day/night sun angle
DayNight.SunShadow returns a cell offset opposite the sun's east-west position, longest near sunrise/sunset (low sun) and zero at noon/night, tilted slightly south so shadows fall in front of occluders. LightmapBuilder gains a directional shadow pass (MaxShadowCells length cap, SunShadowStrength darkening); LightmapSystem feeds it the current sun shadow each rebuild. Tests cover SunShadow's day-arc behaviour and the builder's directional darkening.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace MrGameEng.Lighting;
|
||||
|
||||
/// <summary>One point light projected onto the light grid: a cell position, a radius in cells and an intensity.</summary>
|
||||
@@ -16,8 +18,10 @@ public static class LightmapBuilder
|
||||
|
||||
/// <summary>
|
||||
/// Fills <paramref name="light"/> (length <paramref name="width"/>×<paramref name="height"/>) with
|
||||
/// ambient light, shading occluder cells, then adds each point light with grid-traced occlusion.
|
||||
/// Values end clamped to <c>[0, 1]</c>.
|
||||
/// ambient light, shading occluder cells, casts each occluder's directional sun shadow along
|
||||
/// <paramref name="sunShadow"/> (cells), then adds each point light with grid-traced occlusion.
|
||||
/// Values end clamped to <c>[0, 1]</c>. A zero <paramref name="sunShadow"/> or
|
||||
/// <paramref name="sunShadowStrength"/> skips the directional pass (e.g. at night/noon).
|
||||
/// </summary>
|
||||
public static void Build(
|
||||
float[] light,
|
||||
@@ -25,7 +29,9 @@ public static class LightmapBuilder
|
||||
int height,
|
||||
float ambient,
|
||||
ReadOnlySpan<bool> occluders,
|
||||
IReadOnlyList<LightSample> lights
|
||||
IReadOnlyList<LightSample> lights,
|
||||
Vector2 sunShadow = default,
|
||||
float sunShadowStrength = 0f
|
||||
)
|
||||
{
|
||||
for (var i = 0; i < light.Length; i++)
|
||||
@@ -33,6 +39,8 @@ public static class LightmapBuilder
|
||||
light[i] = occluders[i] ? ambient * OccluderShade : ambient;
|
||||
}
|
||||
|
||||
CastSunShadows(light, width, height, occluders, sunShadow, sunShadowStrength);
|
||||
|
||||
foreach (var l in lights)
|
||||
{
|
||||
if (l.Radius <= 0f || l.Intensity <= 0f)
|
||||
@@ -73,6 +81,62 @@ public static class LightmapBuilder
|
||||
}
|
||||
}
|
||||
|
||||
// Направленная тень от солнца: каждый окклюдер (гора/зрелая крона) отбрасывает тень вдоль
|
||||
// вектора sunShadow (в клетках). Затемнение гуще у основания и тает к концу тени; сами клетки-
|
||||
// окклюдеры не трогаем (они уже затенены). Окклюдеры разрежены, так что проход дёшев.
|
||||
private static void CastSunShadows(
|
||||
float[] light,
|
||||
int width,
|
||||
int height,
|
||||
ReadOnlySpan<bool> occluders,
|
||||
Vector2 sunShadow,
|
||||
float strength
|
||||
)
|
||||
{
|
||||
if (strength <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var steps = (int)MathF.Ceiling(sunShadow.Length());
|
||||
if (steps <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var stepX = sunShadow.X / steps;
|
||||
var stepY = sunShadow.Y / steps;
|
||||
for (var oy = 0; oy < height; oy++)
|
||||
{
|
||||
for (var ox = 0; ox < width; ox++)
|
||||
{
|
||||
if (!occluders[oy * width + ox])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var s = 1; s <= steps; s++)
|
||||
{
|
||||
var cx = ox + (int)MathF.Round(stepX * s);
|
||||
var cy = oy + (int)MathF.Round(stepY * s);
|
||||
if (cx < 0 || cx >= width || cy < 0 || cy >= height)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var index = cy * width + cx;
|
||||
if (occluders[index])
|
||||
{
|
||||
continue; // тень проходит над другими окклюдерами — они и так тёмные
|
||||
}
|
||||
|
||||
var falloff = 1f - (float)(s - 1) / steps; // гуще у основания тени
|
||||
light[index] *= 1f - strength * falloff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Есть ли прямая видимость между клетками: проводим линию (Брезенхем) и проверяем
|
||||
// промежуточные клетки на окклюдер (концы исключены).
|
||||
private static bool Visible(
|
||||
|
||||
Reference in New Issue
Block a user