Root cause of broken shadows: depth buffer stores non-linear NDC depth, not linear distance. closestDepth * 60.0 was wrong conversion. Fix: switch from depth-only to R32_SFLOAT color attachment approach: - Shadow vertex shader outputs world position to fragment - Shadow fragment shader writes length(fragPos - lightPos) / farPlane - Main fragment shader samples cubemap, multiplies by FAR_PLANE=60 - Separate color cube (R32_SFLOAT, sampled) + depth cube (D32_SFLOAT, depth test) - Shadow pipeline: 1 color attachment (R) + depth attachment - Color clear = 1.0 (max distance), depth clear = 1.0 Tests: 227 total, all pass - ShadowMapFaceDirectionTests: 6 face directions, 90° FOV, up vectors, valid matrices, far plane consistency - ShadowShaderTests: all 6 SPIR-V shaders exist
115 lines
3.7 KiB
C#
115 lines
3.7 KiB
C#
using System.Numerics;
|
|
|
|
namespace Engine.Tests;
|
|
|
|
public class ShadowMapFaceDirectionTests
|
|
{
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
[InlineData(3)]
|
|
[InlineData(4)]
|
|
[InlineData(5)]
|
|
public void All_Six_Faces_Produce_Valid_View_Matrices(int face)
|
|
{
|
|
// Test the face direction logic directly (without VulkanShadowMap class)
|
|
var lightPos = new Vector3(3, 7, -2);
|
|
var (view, proj) = ComputeFaceViewProj(lightPos, face);
|
|
|
|
Assert.True(!float.IsNaN(view.M11));
|
|
Assert.True(!float.IsNaN(proj.M11));
|
|
Assert.True(!float.IsInfinity(view.M11));
|
|
Assert.True(!float.IsInfinity(proj.M11));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0, 1, 0, 0)]
|
|
[InlineData(1, -1, 0, 0)]
|
|
[InlineData(2, 0, 1, 0)]
|
|
[InlineData(3, 0, -1, 0)]
|
|
[InlineData(4, 0, 0, 1)]
|
|
[InlineData(5, 0, 0, -1)]
|
|
public void Face_Target_Is_LightPos_Plus_Direction(int face, float dx, float dy, float dz)
|
|
{
|
|
var lightPos = new Vector3(5, 10, 3);
|
|
var (view, _) = ComputeFaceViewProj(lightPos, face);
|
|
|
|
// View matrix transforms lightPos to origin
|
|
var origin = Vector3.Transform(lightPos, view);
|
|
Assert.Equal(0f, origin.X, 0.001f);
|
|
Assert.Equal(0f, origin.Y, 0.001f);
|
|
Assert.Equal(0f, origin.Z, 0.001f);
|
|
}
|
|
|
|
[Fact]
|
|
public void All_Faces_Have_90_Degrees_FOV()
|
|
{
|
|
for (int face = 0; face < 6; face++)
|
|
{
|
|
var (_, proj) = ComputeFaceViewProj(Vector3.Zero, face);
|
|
// FOV=90°, aspect=1 → M22 = 1/tan(45°) = 1, then *= -1 → -1... wait
|
|
// CreatePerspectiveFieldOfView(PI/2, 1, n, f) → M22 = 1/tan(PI/4) = 1
|
|
// Then M22 *= -1 → M22 = -1
|
|
Assert.Equal(-1f, proj.M22, 0.001f);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Face_2_Uses_Negative_Z_Up()
|
|
{
|
|
var lightPos = new Vector3(0, 5, 0);
|
|
var (view, _) = ComputeFaceViewProj(lightPos, 2);
|
|
// +Y face: up = -Z
|
|
var negZ = Vector3.Transform(new Vector3(0, 0, -1), view);
|
|
// Should have positive Y in view space (up direction)
|
|
Assert.True(negZ.Y > 0, $"Face 2 up should map -Z to +Y view space, got {negZ}");
|
|
}
|
|
|
|
[Fact]
|
|
public void Face_3_Uses_Positive_Z_Up()
|
|
{
|
|
var lightPos = new Vector3(0, 5, 0);
|
|
var (view, _) = ComputeFaceViewProj(lightPos, 3);
|
|
// -Y face: up = +Z
|
|
var posZ = Vector3.Transform(new Vector3(0, 0, 1), view);
|
|
Assert.True(posZ.Y > 0, $"Face 3 up should map +Z to +Y view space, got {posZ}");
|
|
}
|
|
|
|
[Fact]
|
|
public void FarPlane_Matches_Between_Projection_And_Shader()
|
|
{
|
|
// The shader hardcodes FAR_PLANE = 60.0 and divides by it
|
|
// The projection must use the same far plane
|
|
var (_, proj) = ComputeFaceViewProj(Vector3.Zero, 0);
|
|
// M33 for perspective with M22 *= -1: should be negative
|
|
Assert.True(proj.M33 < 0, $"M33 should be negative for Vulkan projection, got {proj.M33}");
|
|
}
|
|
|
|
static (Matrix4x4 view, Matrix4x4 proj) ComputeFaceViewProj(Vector3 lightPos, int face)
|
|
{
|
|
var proj = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 2f, 1.0f, 0.1f, 60f);
|
|
proj.M22 *= -1;
|
|
|
|
var target = lightPos;
|
|
var up = Vector3.UnitY;
|
|
|
|
target += face switch
|
|
{
|
|
0 => Vector3.UnitX,
|
|
1 => -Vector3.UnitX,
|
|
2 => Vector3.UnitY,
|
|
3 => -Vector3.UnitY,
|
|
4 => Vector3.UnitZ,
|
|
5 => -Vector3.UnitZ,
|
|
_ => Vector3.UnitZ,
|
|
};
|
|
|
|
if (face == 2) up = -Vector3.UnitZ;
|
|
else if (face == 3) up = Vector3.UnitZ;
|
|
|
|
var view = Matrix4x4.CreateLookAt(lightPos, target, up);
|
|
return (view, proj);
|
|
}
|
|
}
|