acdream/src/AcDream.App/Rendering/DirectionalShadowUniforms.cs

111 lines
4.6 KiB
C#

using System.Numerics;
using System.Runtime.InteropServices;
using AcDream.App.Rendering.Gpu;
namespace AcDream.App.Rendering;
/// <summary>
/// Shader ABI SSOT companion for opt-in set 3 binding 6. The matching GLSL block is
/// directional_shadow_common.glsl; both are pinned at 336 std140 bytes.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal readonly struct DirectionalShadowUniforms
{
internal const int SizeInBytes = 336;
public readonly Matrix4x4 WorldToClip0;
public readonly Matrix4x4 WorldToClip1;
public readonly Matrix4x4 WorldToClip2;
public readonly Matrix4x4 WorldToClip3;
public readonly Vector4 SplitFarMeters;
public readonly Vector4 Control;
public readonly Vector4 BiasMeters;
public readonly UInt4 TextureAndFlags;
public readonly Vector4 LightDirectionAndSource;
internal DirectionalShadowUniforms(
Matrix4x4 worldToClip0,
Matrix4x4 worldToClip1,
Matrix4x4 worldToClip2,
Matrix4x4 worldToClip3,
Vector4 splitFarMeters,
Vector4 control,
Vector4 biasMeters,
UInt4 textureAndFlags,
Vector4 lightDirectionAndSource)
{
WorldToClip0 = worldToClip0;
WorldToClip1 = worldToClip1;
WorldToClip2 = worldToClip2;
WorldToClip3 = worldToClip3;
SplitFarMeters = splitFarMeters;
Control = control;
BiasMeters = biasMeters;
TextureAndFlags = textureAndFlags;
LightDirectionAndSource = lightDirectionAndSource;
}
internal static DirectionalShadowUniforms Create(
ReadOnlySpan<DirectionalShadowCascade> cascades,
in DirectionalShadowEnvironmentState environment,
in DirectionalShadowQuality quality,
GpuTextureSlot textureSlot)
{
if (cascades.Length != quality.CascadeCount)
throw new ArgumentException("The cascade span must match the selected quality.", nameof(cascades));
if (!textureSlot.IsAssigned)
throw new ArgumentException("The directional depth array requires an assigned texture slot.", nameof(textureSlot));
Matrix4x4 matrix0 = cascades[0].WorldToShadowClip;
Matrix4x4 matrix1 = cascades.Length > 1 ? cascades[1].WorldToShadowClip : Matrix4x4.Identity;
Matrix4x4 matrix2 = cascades.Length > 2 ? cascades[2].WorldToShadowClip : Matrix4x4.Identity;
Matrix4x4 matrix3 = cascades.Length > 3 ? cascades[3].WorldToShadowClip : Matrix4x4.Identity;
float split0 = cascades[0].SplitFarMeters;
float split1 = cascades.Length > 1 ? cascades[1].SplitFarMeters : quality.MaximumReachMeters;
float split2 = cascades.Length > 2 ? cascades[2].SplitFarMeters : quality.MaximumReachMeters;
float split3 = cascades.Length > 3 ? cascades[3].SplitFarMeters : quality.MaximumReachMeters;
// The pinned v1 receiver block carries one world-space bias triple.
// Publish the conservative outer-cascade values; the receiver derives
// each inner cascade's relative texel footprint from its projection
// matrix before applying this triple. That preserves the bias portion
// of the v1 ABI while the selected-light vec4 is appended at byte 320
// without applying the outer map's visibly excessive offset nearby.
DirectionalShadowWorldBias bias = cascades[^1].Bias;
float effectiveReachMeters = cascades[^1].SplitFarMeters;
return new DirectionalShadowUniforms(
matrix0,
matrix1,
matrix2,
matrix3,
new Vector4(split0, split1, split2, split3),
new Vector4(
environment.Strength,
environment.SoftnessMultiplier,
effectiveReachMeters,
MathF.Max(1f, effectiveReachMeters * 0.02f)),
new Vector4(
bias.ConstantDepthMeters,
bias.SlopeDepthMeters,
bias.NormalOffsetMeters,
cascades[0].CasterDepthPaddingMeters),
new UInt4(
textureSlot.Index,
checked((uint)quality.CascadeCount),
checked((uint)quality.MapResolution),
1u | (checked((uint)quality.PcfRadiusTexels) << 8)),
new Vector4(
environment.SurfaceToLightDirection,
checked((uint)environment.SourceKind)));
}
}
/// <summary>Four uints with the exact 16-byte std140 uvec4 representation.</summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal readonly struct UInt4(uint x, uint y, uint z, uint w)
{
public readonly uint X = x;
public readonly uint Y = y;
public readonly uint Z = z;
public readonly uint W = w;
}