feat: multi-light system with per-light cubemap array shadows
Architecture: - SceneUBO (448B): mat4 vp + numLights + numShadowLights + LightData[8] + shadowParams[4] - LightData: vec4 posAndIntensity + vec4 colorAndRange (32B per light) - Push constants (main): mat4 model only (64B) — all light data in UBO - Push constants (shadow): model(64) + lightViewProj(64) + lightPos(16) + shadowParams(16) = 160B - Shadow: cubemap array (24 layers = 4 lights × 6 faces), samplerCubeArray in shader - Shadow passes: numShadowLights × 6 faces (2 lights = 12 passes) - Fragment shader: loop over lights, calcPBR per light, shadow from corresponding cubemap layer New OBJ files with verified CCW winding: - pyramid.obj, diamond.obj, torus.obj, cone.obj, sphere.obj Scene 'Chaos Architecture': - 28 sphere pyramid (physics) - 30 falling diamonds (physics) - 8 floating torus rings (decorative) - 4 pyramid decorations (static) - 4 cone pillars (static) - Central torus knot (floating) - 2 dynamic point lights (warm + cool), both cast shadows - Both lights orbit in chaotic circles ImGui panel: - Per-light intensity, range, RGB sliders - Shadow bias, sample radius, far plane sliders 227 tests, all pass
This commit is contained in:
@@ -133,17 +133,29 @@ class Program
|
|||||||
physicsWorld.Update((float)timing.DeltaTime);
|
physicsWorld.Update((float)timing.DeltaTime);
|
||||||
physicsWorld.SyncTransforms(world);
|
physicsWorld.SyncTransforms(world);
|
||||||
|
|
||||||
// Move light in a chaotic circle
|
// Move lights in chaotic circles
|
||||||
var lightEntity = world.Lookup("MainLight");
|
var mainLight = world.Lookup("MainLight");
|
||||||
if ((ulong)lightEntity.Id != 0)
|
if ((ulong)mainLight.Id != 0)
|
||||||
{
|
{
|
||||||
var t = totalTime;
|
var t = totalTime;
|
||||||
var lx = MathF.Sin(t * 0.7f) * 8f + MathF.Cos(t * 0.3f) * 3f;
|
var lx = MathF.Sin(t * 0.7f) * 8f + MathF.Cos(t * 0.3f) * 3f;
|
||||||
var ly = 12f + MathF.Sin(t * 0.5f) * 5f;
|
var ly = 12f + MathF.Sin(t * 0.5f) * 5f;
|
||||||
var lz = MathF.Cos(t * 0.6f) * 8f + MathF.Sin(t * 0.4f) * 3f;
|
var lz = MathF.Cos(t * 0.6f) * 8f + MathF.Sin(t * 0.4f) * 3f;
|
||||||
var lt = lightEntity.Get<Transform>();
|
var lt = mainLight.Get<Transform>();
|
||||||
lt.Position = new Vector3(lx, ly, lz);
|
lt.Position = new Vector3(lx, ly, lz);
|
||||||
lightEntity.Set(lt);
|
mainLight.Set(lt);
|
||||||
|
}
|
||||||
|
|
||||||
|
var secondLight = world.Lookup("SecondLight");
|
||||||
|
if ((ulong)secondLight.Id != 0)
|
||||||
|
{
|
||||||
|
var t = totalTime + MathF.PI;
|
||||||
|
var lx = MathF.Cos(t * 0.5f) * 10f + MathF.Sin(t * 0.2f) * 4f;
|
||||||
|
var ly = 8f + MathF.Cos(t * 0.4f) * 4f;
|
||||||
|
var lz = MathF.Sin(t * 0.6f) * 10f + MathF.Cos(t * 0.3f) * 3f;
|
||||||
|
var lt = secondLight.Get<Transform>();
|
||||||
|
lt.Position = new Vector3(lx, ly, lz);
|
||||||
|
secondLight.Set(lt);
|
||||||
}
|
}
|
||||||
|
|
||||||
var processed = queue.ProcessPending();
|
var processed = queue.ProcessPending();
|
||||||
@@ -179,59 +191,60 @@ class Program
|
|||||||
ImGui.End();
|
ImGui.End();
|
||||||
|
|
||||||
// Shadow parameters panel
|
// Shadow parameters panel
|
||||||
ImGui.Begin("Shadow Parameters");
|
ImGui.Begin("Shadow & Light Parameters");
|
||||||
|
|
||||||
var lightEntity2 = world.Lookup("MainLight");
|
// Main light controls
|
||||||
if ((ulong)lightEntity2.Id != 0)
|
var mainLight2 = world.Lookup("MainLight");
|
||||||
|
if ((ulong)mainLight2.Id != 0)
|
||||||
{
|
{
|
||||||
var lightComp = lightEntity2.Get<Light>();
|
var lc1 = mainLight2.Get<Light>();
|
||||||
var intensity = lightComp.Intensity;
|
var i1 = lc1.Intensity; var r1 = lc1.Range;
|
||||||
var range = lightComp.Range;
|
var cr1 = lc1.Color.X; var cg1 = lc1.Color.Y; var cb1 = lc1.Color.Z;
|
||||||
var colorR = lightComp.Color.X;
|
ImGui.Text("Main Light (warm)");
|
||||||
var colorG = lightComp.Color.Y;
|
ImGui.SliderFloat("1 Intensity", ref i1, 0.0f, 50.0f, "%.1f");
|
||||||
var colorB = lightComp.Color.Z;
|
ImGui.SliderFloat("1 Range", ref r1, 5.0f, 100.0f, "%.1f");
|
||||||
|
ImGui.SliderFloat("1 R", ref cr1, 0.0f, 1.0f, "%.2f");
|
||||||
ImGui.SliderFloat("Light Intensity", ref intensity, 0.0f, 100.0f, "%.1f");
|
ImGui.SliderFloat("1 G", ref cg1, 0.0f, 1.0f, "%.2f");
|
||||||
ImGui.SliderFloat("Light Range", ref range, 5.0f, 100.0f, "%.1f");
|
ImGui.SliderFloat("1 B", ref cb1, 0.0f, 1.0f, "%.2f");
|
||||||
ImGui.SliderFloat("Light R", ref colorR, 0.0f, 1.0f, "%.2f");
|
lc1.Intensity = i1; lc1.Range = r1;
|
||||||
ImGui.SliderFloat("Light G", ref colorG, 0.0f, 1.0f, "%.2f");
|
lc1.Color = new Vector3(cr1, cg1, cb1);
|
||||||
ImGui.SliderFloat("Light B", ref colorB, 0.0f, 1.0f, "%.2f");
|
mainLight2.Set(lc1);
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
lightComp.Intensity = intensity;
|
|
||||||
lightComp.Range = range;
|
|
||||||
lightComp.Color = new Vector3(colorR, colorG, colorB);
|
|
||||||
lightEntity2.Set(lightComp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
// Second light controls
|
||||||
|
var secondLight2 = world.Lookup("SecondLight");
|
||||||
|
if ((ulong)secondLight2.Id != 0)
|
||||||
|
{
|
||||||
|
var lc2 = secondLight2.Get<Light>();
|
||||||
|
var i2 = lc2.Intensity; var r2 = lc2.Range;
|
||||||
|
var cr2 = lc2.Color.X; var cg2 = lc2.Color.Y; var cb2 = lc2.Color.Z;
|
||||||
|
ImGui.Text("Second Light (cool)");
|
||||||
|
ImGui.SliderFloat("2 Intensity", ref i2, 0.0f, 50.0f, "%.1f");
|
||||||
|
ImGui.SliderFloat("2 Range", ref r2, 5.0f, 100.0f, "%.1f");
|
||||||
|
ImGui.SliderFloat("2 R", ref cr2, 0.0f, 1.0f, "%.2f");
|
||||||
|
ImGui.SliderFloat("2 G", ref cg2, 0.0f, 1.0f, "%.2f");
|
||||||
|
ImGui.SliderFloat("2 B", ref cb2, 0.0f, 1.0f, "%.2f");
|
||||||
|
lc2.Intensity = i2; lc2.Range = r2;
|
||||||
|
lc2.Color = new Vector3(cr2, cg2, cb2);
|
||||||
|
secondLight2.Set(lc2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
var bias = vkRenderer.ShadowBias;
|
var bias = vkRenderer.ShadowBias;
|
||||||
var sampleRadius = vkRenderer.ShadowSampleRadius;
|
var sampleRadius = vkRenderer.ShadowSampleRadius;
|
||||||
var farPlane = vkRenderer.ShadowFarPlane;
|
var farPlane = vkRenderer.ShadowFarPlane;
|
||||||
|
|
||||||
ImGui.SliderFloat("Shadow Bias", ref bias, 0.001f, 0.5f, "%.4f");
|
ImGui.SliderFloat("Shadow Bias", ref bias, 0.0001f, 0.1f, "%.4f");
|
||||||
ImGui.SliderFloat("Sample Radius", ref sampleRadius, 0.001f, 0.1f, "%.4f");
|
ImGui.SliderFloat("Sample Radius", ref sampleRadius, 0.001f, 0.1f, "%.4f");
|
||||||
ImGui.SliderFloat("Far Plane", ref farPlane, 10.0f, 120.0f, "%.1f");
|
ImGui.SliderFloat("Far Plane", ref farPlane, 10.0f, 120.0f, "%.1f");
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
vkRenderer.ShadowBias = bias;
|
vkRenderer.ShadowBias = bias;
|
||||||
vkRenderer.ShadowSampleRadius = sampleRadius;
|
vkRenderer.ShadowSampleRadius = sampleRadius;
|
||||||
vkRenderer.ShadowFarPlane = farPlane;
|
vkRenderer.ShadowFarPlane = farPlane;
|
||||||
|
|
||||||
ImGui.Text($"Current: bias={bias:F4} radius={sampleRadius:F4} far={farPlane:F1}");
|
|
||||||
|
|
||||||
if (ImGui.Button("Copy Parameters to Clipboard"))
|
|
||||||
{
|
|
||||||
var paramsText = $"bias={bias:F4}\nsampleRadius={sampleRadius:F4}\nfarPlane={farPlane:F1}\nshadowMapSize=2048";
|
|
||||||
var lightEntity3 = world.Lookup("MainLight");
|
|
||||||
if ((ulong)lightEntity3.Id != 0)
|
|
||||||
{
|
|
||||||
var lc = lightEntity3.Get<Light>();
|
|
||||||
paramsText += $"\nlightIntensity={lc.Intensity:F1}\nlightRange={lc.Range:F1}\nlightColor=({lc.Color.X:F2},{lc.Color.Y:F2},{lc.Color.Z:F2})";
|
|
||||||
}
|
|
||||||
ImGui.SetClipboardText(paramsText);
|
|
||||||
Console.WriteLine("[App] Parameters copied to clipboard:\n" + paramsText);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.End();
|
ImGui.End();
|
||||||
|
|
||||||
renderer.EndImGuiFrame();
|
renderer.EndImGuiFrame();
|
||||||
@@ -355,11 +368,15 @@ class Program
|
|||||||
.Set(coneMesh);
|
.Set(coneMesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Light
|
// Lights
|
||||||
world.Entity("MainLight")
|
world.Entity("MainLight")
|
||||||
.Set(new Transform(new Vector3(0, 20, 0), Quaternion.Identity, Vector3.One))
|
.Set(new Transform(new Vector3(0, 20, 0), Quaternion.Identity, Vector3.One))
|
||||||
.Set(Light.Point(new Vector3(0, 20, 0), new Vector3(1.0f, 0.95f, 0.85f), intensity: 15.0f, range: 60.0f));
|
.Set(Light.Point(new Vector3(0, 20, 0), new Vector3(1.0f, 0.95f, 0.85f), intensity: 15.0f, range: 60.0f));
|
||||||
|
|
||||||
|
world.Entity("SecondLight")
|
||||||
|
.Set(new Transform(new Vector3(-12, 10, 8), Quaternion.Identity, Vector3.One))
|
||||||
|
.Set(Light.Point(new Vector3(-12, 10, 8), new Vector3(0.3f, 0.6f, 1.0f), intensity: 10.0f, range: 40.0f));
|
||||||
|
|
||||||
var entityCount = 0;
|
var entityCount = 0;
|
||||||
world.Each((Entity e, ref Transform _) => entityCount++);
|
world.Each((Entity e, ref Transform _) => entityCount++);
|
||||||
Console.WriteLine($"[Scene] {entityCount} entities: sphere pyramid + diamond rain + 8 torus rings + 4 pyramids + 4 cones + floor + grid + light");
|
Console.WriteLine($"[Scene] {entityCount} entities: sphere pyramid + diamond rain + 8 torus rings + 4 pyramids + 4 cones + floor + grid + light");
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ layout(location = 0) out float outDepth;
|
|||||||
|
|
||||||
layout(push_constant) uniform PC {
|
layout(push_constant) uniform PC {
|
||||||
mat4 model;
|
mat4 model;
|
||||||
vec4 lightPos;
|
|
||||||
vec4 lightColor;
|
|
||||||
mat4 lightViewProj;
|
mat4 lightViewProj;
|
||||||
|
vec4 lightPos;
|
||||||
vec4 shadowParams;
|
vec4 shadowParams;
|
||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -4,9 +4,8 @@ layout(location = 0) in vec3 inPosition;
|
|||||||
|
|
||||||
layout(push_constant) uniform PC {
|
layout(push_constant) uniform PC {
|
||||||
mat4 model;
|
mat4 model;
|
||||||
vec4 lightPos;
|
|
||||||
vec4 lightColor;
|
|
||||||
mat4 lightViewProj;
|
mat4 lightViewProj;
|
||||||
|
vec4 lightPos;
|
||||||
vec4 shadowParams;
|
vec4 shadowParams;
|
||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -6,19 +6,21 @@ layout(location = 2) in vec3 fragAlbedo;
|
|||||||
|
|
||||||
layout(location = 0) out vec4 outColor;
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
layout(set = 0, binding = 0) uniform CameraUBO {
|
struct LightData {
|
||||||
mat4 vp;
|
vec4 posAndIntensity;
|
||||||
|
vec4 colorAndRange;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(set = 0, binding = 1) uniform samplerCube shadowCube;
|
layout(set = 0, binding = 0) uniform SceneUBO {
|
||||||
|
mat4 vp;
|
||||||
|
int numLights;
|
||||||
|
int numShadowLights;
|
||||||
|
vec2 padding;
|
||||||
|
LightData lights[8];
|
||||||
|
vec4 shadowParams[4];
|
||||||
|
} scene;
|
||||||
|
|
||||||
layout(push_constant) uniform PC {
|
layout(set = 0, binding = 1) uniform samplerCubeArray shadowArray;
|
||||||
mat4 model;
|
|
||||||
vec4 lightPos;
|
|
||||||
vec4 lightColor;
|
|
||||||
mat4 lightViewProj;
|
|
||||||
vec4 shadowParams;
|
|
||||||
} pc;
|
|
||||||
|
|
||||||
const vec3 AMBIENT = vec3(0.01, 0.01, 0.02);
|
const vec3 AMBIENT = vec3(0.01, 0.01, 0.02);
|
||||||
const float PI = 3.14159265359;
|
const float PI = 3.14159265359;
|
||||||
@@ -92,11 +94,11 @@ mat3 buildTangentBasis(vec3 dir)
|
|||||||
return mat3(tangent, bitangent, dir);
|
return mat3(tangent, bitangent, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
float calcShadow(vec3 worldPos, vec3 lightPos, vec3 N, vec3 L)
|
float calcShadow(vec3 worldPos, vec3 lightPos, vec3 N, vec3 L, int lightIndex)
|
||||||
{
|
{
|
||||||
float bias = pc.shadowParams.x;
|
float bias = scene.shadowParams[lightIndex].x;
|
||||||
float sampleRadius = pc.shadowParams.y;
|
float sampleRadius = scene.shadowParams[lightIndex].y;
|
||||||
float farPlane = pc.shadowParams.z;
|
float farPlane = scene.shadowParams[lightIndex].z;
|
||||||
|
|
||||||
vec3 dir = worldPos - lightPos;
|
vec3 dir = worldPos - lightPos;
|
||||||
float dist = length(dir);
|
float dist = length(dir);
|
||||||
@@ -112,7 +114,7 @@ float calcShadow(vec3 worldPos, vec3 lightPos, vec3 N, vec3 L)
|
|||||||
for (int i = 0; i < 16; i++)
|
for (int i = 0; i < 16; i++)
|
||||||
{
|
{
|
||||||
vec3 sampleDir = normalize(dirNorm + basis * (POISSON_DISK[i] * radius));
|
vec3 sampleDir = normalize(dirNorm + basis * (POISSON_DISK[i] * radius));
|
||||||
float sampleDepth = texture(shadowCube, sampleDir).r;
|
float sampleDepth = texture(shadowArray, vec4(sampleDir, float(lightIndex))).r;
|
||||||
float sampleMapped = sampleDepth * farPlane;
|
float sampleMapped = sampleDepth * farPlane;
|
||||||
shadow += (dist - slopeBias < sampleMapped) ? 1.0 : 0.0;
|
shadow += (dist - slopeBias < sampleMapped) ? 1.0 : 0.0;
|
||||||
}
|
}
|
||||||
@@ -121,28 +123,8 @@ float calcShadow(vec3 worldPos, vec3 lightPos, vec3 N, vec3 L)
|
|||||||
return shadow;
|
return shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
void main()
|
vec3 calcPBR(vec3 N, vec3 V, vec3 L, vec3 radiance, vec3 albedo, float roughness, float metallic)
|
||||||
{
|
{
|
||||||
vec3 N = normalize(fragNormal);
|
|
||||||
vec3 V = normalize(-fragWorldPos);
|
|
||||||
vec3 albedo = fragAlbedo;
|
|
||||||
float roughness = 0.5;
|
|
||||||
float metallic = 0.1;
|
|
||||||
|
|
||||||
vec3 lightPos = pc.lightPos.xyz;
|
|
||||||
float lightIntensity = pc.lightPos.w;
|
|
||||||
vec3 lightColor = pc.lightColor.xyz;
|
|
||||||
float lightRange = pc.lightColor.w;
|
|
||||||
|
|
||||||
vec3 toLight = lightPos - fragWorldPos;
|
|
||||||
float dist = length(toLight);
|
|
||||||
vec3 L = toLight / max(dist, 0.001);
|
|
||||||
|
|
||||||
float attenuation = pow(clamp(1.0 - dist / max(lightRange, 0.001), 0.0, 1.0), 2.0);
|
|
||||||
vec3 radiance = lightColor * lightIntensity * attenuation;
|
|
||||||
|
|
||||||
float shadow = calcShadow(fragWorldPos, lightPos, N, L);
|
|
||||||
|
|
||||||
vec3 H = normalize(V + L);
|
vec3 H = normalize(V + L);
|
||||||
vec3 F0 = mix(vec3(0.04), albedo, metallic);
|
vec3 F0 = mix(vec3(0.04), albedo, metallic);
|
||||||
|
|
||||||
@@ -158,9 +140,41 @@ void main()
|
|||||||
vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);
|
vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);
|
||||||
|
|
||||||
float NdotL = max(dot(N, L), 0.0);
|
float NdotL = max(dot(N, L), 0.0);
|
||||||
vec3 lighting = (kD * albedo / PI + specular) * radiance * NdotL * shadow;
|
return (kD * albedo / PI + specular) * radiance * NdotL;
|
||||||
|
}
|
||||||
|
|
||||||
vec3 color = AMBIENT * albedo + lighting;
|
void main()
|
||||||
|
{
|
||||||
|
vec3 N = normalize(fragNormal);
|
||||||
|
vec3 V = normalize(-fragWorldPos);
|
||||||
|
vec3 albedo = fragAlbedo;
|
||||||
|
float roughness = 0.5;
|
||||||
|
float metallic = 0.1;
|
||||||
|
|
||||||
|
vec3 color = AMBIENT * albedo;
|
||||||
|
|
||||||
|
for (int i = 0; i < scene.numLights; i++)
|
||||||
|
{
|
||||||
|
vec3 lightPos = scene.lights[i].posAndIntensity.xyz;
|
||||||
|
float lightIntensity = scene.lights[i].posAndIntensity.w;
|
||||||
|
vec3 lightColor = scene.lights[i].colorAndRange.xyz;
|
||||||
|
float lightRange = scene.lights[i].colorAndRange.w;
|
||||||
|
|
||||||
|
vec3 toLight = lightPos - fragWorldPos;
|
||||||
|
float dist = length(toLight);
|
||||||
|
vec3 L = toLight / max(dist, 0.001);
|
||||||
|
|
||||||
|
float attenuation = pow(clamp(1.0 - dist / max(lightRange, 0.001), 0.0, 1.0), 2.0);
|
||||||
|
vec3 radiance = lightColor * lightIntensity * attenuation;
|
||||||
|
|
||||||
|
float shadow = 1.0;
|
||||||
|
if (i < scene.numShadowLights)
|
||||||
|
{
|
||||||
|
shadow = calcShadow(fragWorldPos, lightPos, N, L, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
color += calcPBR(N, V, L, radiance, albedo, roughness, metallic) * shadow;
|
||||||
|
}
|
||||||
|
|
||||||
color = acesTonemap(color);
|
color = acesTonemap(color);
|
||||||
color = pow(color, vec3(1.0 / 2.2));
|
color = pow(color, vec3(1.0 / 2.2));
|
||||||
|
|||||||
Binary file not shown.
@@ -8,21 +8,27 @@ layout(location = 0) out vec3 fragWorldPos;
|
|||||||
layout(location = 1) out vec3 fragNormal;
|
layout(location = 1) out vec3 fragNormal;
|
||||||
layout(location = 2) out vec3 fragAlbedo;
|
layout(location = 2) out vec3 fragAlbedo;
|
||||||
|
|
||||||
layout(set = 0, binding = 0) uniform CameraUBO {
|
struct LightData {
|
||||||
mat4 vp;
|
vec4 posAndIntensity; // xyz = position, w = intensity
|
||||||
|
vec4 colorAndRange; // xyz = color, w = range
|
||||||
};
|
};
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform SceneUBO {
|
||||||
|
mat4 vp;
|
||||||
|
int numLights;
|
||||||
|
int numShadowLights;
|
||||||
|
vec2 padding;
|
||||||
|
LightData lights[8];
|
||||||
|
vec4 shadowParams[4]; // x=bias, y=sampleRadius, z=farPlane, w=unused
|
||||||
|
} scene;
|
||||||
|
|
||||||
layout(push_constant) uniform PC {
|
layout(push_constant) uniform PC {
|
||||||
mat4 model;
|
mat4 model;
|
||||||
vec4 lightPos;
|
|
||||||
vec4 lightColor;
|
|
||||||
mat4 lightViewProj;
|
|
||||||
vec4 shadowParams;
|
|
||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 worldPos = pc.model * vec4(inPosition, 1.0);
|
vec4 worldPos = pc.model * vec4(inPosition, 1.0);
|
||||||
gl_Position = vp * worldPos;
|
gl_Position = scene.vp * worldPos;
|
||||||
fragWorldPos = worldPos.xyz;
|
fragWorldPos = worldPos.xyz;
|
||||||
fragNormal = mat3(pc.model) * inNormal;
|
fragNormal = mat3(pc.model) * inNormal;
|
||||||
fragAlbedo = inColor;
|
fragAlbedo = inColor;
|
||||||
|
|||||||
Binary file not shown.
@@ -5,7 +5,7 @@ namespace Engine.Graphics.Vulkan;
|
|||||||
internal sealed unsafe class VulkanFrameResources : IDisposable
|
internal sealed unsafe class VulkanFrameResources : IDisposable
|
||||||
{
|
{
|
||||||
public const int MaxFramesInFlight = 2;
|
public const int MaxFramesInFlight = 2;
|
||||||
public const ulong UboSize = 64;
|
public const ulong UboSize = 448;
|
||||||
|
|
||||||
public VkCommandPool CommandPool;
|
public VkCommandPool CommandPool;
|
||||||
public VkCommandBuffer[] CommandBuffers = new VkCommandBuffer[MaxFramesInFlight];
|
public VkCommandBuffer[] CommandBuffers = new VkCommandBuffer[MaxFramesInFlight];
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ internal sealed unsafe class VulkanPipeline : IDisposable
|
|||||||
{
|
{
|
||||||
stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment,
|
stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
size = 176,
|
size = 64,
|
||||||
};
|
};
|
||||||
|
|
||||||
var descLayout = DescriptorSetLayout;
|
var descLayout = DescriptorSetLayout;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
_shadowMap = new VulkanShadowMap(ctx.Device, ctx, _pipeline.DescriptorSetLayout);
|
_shadowMap = new VulkanShadowMap(ctx.Device, ctx, _pipeline.DescriptorSetLayout);
|
||||||
|
|
||||||
for (int i = 0; i < VulkanFrameResources.MaxFramesInFlight; i++)
|
for (int i = 0; i < VulkanFrameResources.MaxFramesInFlight; i++)
|
||||||
_frameResources.UpdateShadowDescriptor(i, _shadowMap.ShadowSampler, _shadowMap.CubeColorView);
|
_frameResources.UpdateShadowDescriptor(i, _shadowMap.ShadowSampler, _shadowMap.CubeArrayColorView);
|
||||||
|
|
||||||
_imGui = new VulkanImGui(ctx, _frameResources.CommandPool, swapchain.Format, swapchain.DepthFormat);
|
_imGui = new VulkanImGui(ctx, _frameResources.CommandPool, swapchain.Format, swapchain.DepthFormat);
|
||||||
}
|
}
|
||||||
@@ -87,40 +87,56 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
vp = Matrix4x4.CreateLookAt(new Vector3(0, 0, -6), Vector3.Zero, Vector3.UnitY) * proj;
|
vp = Matrix4x4.CreateLookAt(new Vector3(0, 0, -6), Vector3.Zero, Vector3.UnitY) * proj;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect point light from ECS — prefer light attached to a moving entity
|
// Collect all point lights from ECS
|
||||||
var lightPos = new Vector4(0, 5, 0, 0);
|
var lights = new List<(Vector3 pos, float intensity, Vector3 color, float range)>();
|
||||||
var lightColor = new Vector4(1, 1, 1, 0);
|
|
||||||
world.Each((Entity e, ref Light l, ref Transform lt) =>
|
world.Each((Entity e, ref Light l, ref Transform lt) =>
|
||||||
{
|
{
|
||||||
if (l.IsPoint && lightPos.W == 0)
|
if (l.IsPoint)
|
||||||
{
|
lights.Add((lt.Position, l.Intensity, l.Color, l.Range));
|
||||||
lightPos = new Vector4(lt.Position, l.Intensity);
|
|
||||||
lightColor = new Vector4(l.Color, l.Range);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (lightPos.W == 0)
|
if (lights.Count == 0)
|
||||||
{
|
{
|
||||||
world.Each((Entity e, ref Light l) =>
|
world.Each((Entity e, ref Light l) =>
|
||||||
{
|
{
|
||||||
if (l.IsPoint && lightPos.W == 0)
|
if (l.IsPoint)
|
||||||
{
|
lights.Add((l.Position, l.Intensity, l.Color, l.Range));
|
||||||
lightPos = new Vector4(l.Position, l.Intensity);
|
|
||||||
lightColor = new Vector4(l.Color, l.Range);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack UBO: just mat4 vp (64 bytes)
|
int numLights = Math.Min(lights.Count, 8);
|
||||||
var uboData = stackalloc byte[64];
|
int numShadowLights = Math.Min(numLights, VulkanShadowMap.MaxShadowLights);
|
||||||
|
|
||||||
|
// Pack SceneUBO: mat4 vp(64) + int numLights(4) + int numShadowLights(4) + vec2 pad(8) + LightData[8](256) + vec4 shadowParams[4](64) = 400B → pad to 448
|
||||||
|
var uboData = stackalloc byte[448];
|
||||||
var vpCopy = vp;
|
var vpCopy = vp;
|
||||||
System.Buffer.MemoryCopy(&vpCopy, uboData, 64, 64);
|
System.Buffer.MemoryCopy(&vpCopy, uboData, 64, 64);
|
||||||
|
*(int*)(uboData + 64) = numLights;
|
||||||
|
*(int*)(uboData + 68) = numShadowLights;
|
||||||
|
|
||||||
// Compute light view-projection matrix for shadow mapping
|
for (int i = 0; i < numLights; i++)
|
||||||
var lightPosVec = new Vector3(lightPos.X, lightPos.Y, lightPos.Z);
|
{
|
||||||
var lightView = Matrix4x4.CreateLookAt(lightPosVec, Vector3.Zero, Vector3.UnitY);
|
var lp = new Vector4(lights[i].pos, lights[i].intensity);
|
||||||
var lightProj = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 3f, 1.0f, 0.1f, 60f);
|
var lc = new Vector4(lights[i].color, lights[i].range);
|
||||||
lightProj.M22 *= -1;
|
var lightOffset = 80 + i * 32;
|
||||||
var lightViewProj = lightView * lightProj;
|
System.Buffer.MemoryCopy(&lp, uboData + lightOffset, 16, 16);
|
||||||
|
System.Buffer.MemoryCopy(&lc, uboData + lightOffset + 16, 16, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shadow params per shadow light
|
||||||
|
for (int i = 0; i < numShadowLights; i++)
|
||||||
|
{
|
||||||
|
var sp = new Vector4(ShadowBias, ShadowSampleRadius, ShadowFarPlane, 0);
|
||||||
|
System.Buffer.MemoryCopy(&sp, uboData + 336 + i * 16, 16, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute light view-proj for each shadow light
|
||||||
|
var lightViewProjs = new Matrix4x4[numShadowLights];
|
||||||
|
for (int i = 0; i < numShadowLights; i++)
|
||||||
|
{
|
||||||
|
var lightView = Matrix4x4.CreateLookAt(lights[i].pos, Vector3.Zero, Vector3.UnitY);
|
||||||
|
var lightProj = Matrix4x4.CreatePerspectiveFieldOfView(MathF.PI / 2f, 1.0f, 0.1f, ShadowFarPlane);
|
||||||
|
lightViewProjs[i] = lightView * lightProj;
|
||||||
|
}
|
||||||
|
|
||||||
var drawCalls = new List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model, bool castShadow)>();
|
var drawCalls = new List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model, bool castShadow)>();
|
||||||
|
|
||||||
@@ -142,10 +158,10 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
drawCalls.Add((entry.vb.Buffer, entry.ib.Buffer, entry.indexCount, t.GetMatrix(), castShadow));
|
drawCalls.Add((entry.vb.Buffer, entry.ib.Buffer, entry.indexCount, t.GetMatrix(), castShadow));
|
||||||
});
|
});
|
||||||
|
|
||||||
Render(vp, drawCalls, uboData, lightPos, lightColor, lightViewProj);
|
Render(vp, drawCalls, uboData, lights, lightViewProjs, numShadowLights);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Render(Matrix4x4 vp, List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model, bool castShadow)> drawCalls, byte* uboData, Vector4 lightPos, Vector4 lightColor, Matrix4x4 lightViewProj)
|
private void Render(Matrix4x4 vp, List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model, bool castShadow)> drawCalls, byte* uboData, List<(Vector3 pos, float intensity, Vector3 color, float range)> lights, Matrix4x4[] lightViewProjs, int numShadowLights)
|
||||||
{
|
{
|
||||||
_frameResources.WaitFrame(_frameIndex);
|
_frameResources.WaitFrame(_frameIndex);
|
||||||
|
|
||||||
@@ -157,7 +173,7 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
{
|
{
|
||||||
_swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width,
|
_swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width,
|
||||||
_ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
|
_ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height);
|
||||||
Render(vp, drawCalls, uboData, lightPos, lightColor, lightViewProj);
|
Render(vp, drawCalls, uboData, lights, lightViewProjs, numShadowLights);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,122 +194,123 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
};
|
};
|
||||||
Vk.vkBeginCommandBuffer(cmd, &beginInfo);
|
Vk.vkBeginCommandBuffer(cmd, &beginInfo);
|
||||||
|
|
||||||
// === SHADOW CUBEMAP PASSES (6 faces) ===
|
// === SHADOW CUBEMAP ARRAY PASSES (numShadowLights × 6 faces) ===
|
||||||
// Transition color cube: Undefined → ColorAttachmentOptimal
|
int totalLayers = numShadowLights * 6;
|
||||||
TransitionImageLayout(cmd, _shadowMap.ColorImage,
|
TransitionImageLayout(cmd, _shadowMap.ColorImage,
|
||||||
VkImageLayout.Undefined, VkImageLayout.ColorAttachmentOptimal,
|
VkImageLayout.Undefined, VkImageLayout.ColorAttachmentOptimal,
|
||||||
0, 0, 0x400, 0x100, 6);
|
0, 0, 0x400, 0x100, (uint)totalLayers);
|
||||||
// Transition depth cube: Undefined → DepthStencilAttachmentOptimal
|
|
||||||
TransitionImageLayoutDepth(cmd, _shadowMap.DepthImage,
|
TransitionImageLayoutDepth(cmd, _shadowMap.DepthImage,
|
||||||
VkImageLayout.Undefined, VkImageLayout.DepthStencilAttachmentOptimal,
|
VkImageLayout.Undefined, VkImageLayout.DepthStencilAttachmentOptimal,
|
||||||
0, 0, 0x100, 0x200, 6);
|
0, 0, 0x100, 0x200, (uint)totalLayers);
|
||||||
|
|
||||||
var lightPosVec = new Vector3(lightPos.X, lightPos.Y, lightPos.Z);
|
for (int lightIdx = 0; lightIdx < numShadowLights; lightIdx++)
|
||||||
|
|
||||||
for (int face = 0; face < 6; face++)
|
|
||||||
{
|
{
|
||||||
var (faceView, faceProj) = VulkanShadowMap.GetFaceViewProj(lightPosVec, face);
|
var lightPosVec = lights[lightIdx].pos;
|
||||||
var faceViewProj = faceView * faceProj;
|
|
||||||
|
|
||||||
var colorClear = new VkClearValue
|
for (int face = 0; face < 6; face++)
|
||||||
{
|
{
|
||||||
Color = new VkClearColorValue { Float0 = 1.0f, Float1 = 0, Float2 = 0, Float3 = 0 },
|
var (faceView, faceProj) = VulkanShadowMap.GetFaceViewProj(lightPosVec, face);
|
||||||
};
|
var faceViewProj = faceView * faceProj;
|
||||||
|
int faceIndex = lightIdx * 6 + face;
|
||||||
|
|
||||||
var shadowColorAttachment = new VkRenderingAttachmentInfo
|
var colorClear = new VkClearValue
|
||||||
{
|
{
|
||||||
sType = VkStructureType.RenderingAttachmentInfo,
|
Color = new VkClearColorValue { Float0 = 1.0f, Float1 = 0, Float2 = 0, Float3 = 0 },
|
||||||
imageView = _shadowMap.FaceColorViews[face],
|
};
|
||||||
imageLayout = VkImageLayout.ColorAttachmentOptimal,
|
|
||||||
loadOp = VkAttachmentLoadOp.Clear,
|
|
||||||
storeOp = VkAttachmentStoreOp.Store,
|
|
||||||
clearValue = colorClear,
|
|
||||||
};
|
|
||||||
|
|
||||||
var shadowDepthClear = new VkClearValue
|
var shadowColorAttachment = new VkRenderingAttachmentInfo
|
||||||
{
|
{
|
||||||
DepthStencil = new VkClearDepthStencilValue { Depth = 1.0f, Stencil = 0 },
|
sType = VkStructureType.RenderingAttachmentInfo,
|
||||||
};
|
imageView = _shadowMap.FaceColorViews[faceIndex],
|
||||||
|
imageLayout = VkImageLayout.ColorAttachmentOptimal,
|
||||||
|
loadOp = VkAttachmentLoadOp.Clear,
|
||||||
|
storeOp = VkAttachmentStoreOp.Store,
|
||||||
|
clearValue = colorClear,
|
||||||
|
};
|
||||||
|
|
||||||
var shadowDepthAttachment = new VkRenderingAttachmentInfo
|
var shadowDepthClear = new VkClearValue
|
||||||
{
|
{
|
||||||
sType = VkStructureType.RenderingAttachmentInfo,
|
DepthStencil = new VkClearDepthStencilValue { Depth = 1.0f, Stencil = 0 },
|
||||||
imageView = _shadowMap.FaceDepthViews[face],
|
};
|
||||||
imageLayout = VkImageLayout.DepthStencilAttachmentOptimal,
|
|
||||||
loadOp = VkAttachmentLoadOp.Clear,
|
|
||||||
storeOp = VkAttachmentStoreOp.Store,
|
|
||||||
clearValue = shadowDepthClear,
|
|
||||||
};
|
|
||||||
|
|
||||||
var shadowRenderingInfo = new VkRenderingInfo
|
var shadowDepthAttachment = new VkRenderingAttachmentInfo
|
||||||
{
|
{
|
||||||
sType = VkStructureType.RenderingInfo,
|
sType = VkStructureType.RenderingAttachmentInfo,
|
||||||
renderArea = new VkRect2D
|
imageView = _shadowMap.FaceDepthViews[faceIndex],
|
||||||
|
imageLayout = VkImageLayout.DepthStencilAttachmentOptimal,
|
||||||
|
loadOp = VkAttachmentLoadOp.Clear,
|
||||||
|
storeOp = VkAttachmentStoreOp.Store,
|
||||||
|
clearValue = shadowDepthClear,
|
||||||
|
};
|
||||||
|
|
||||||
|
var shadowRenderingInfo = new VkRenderingInfo
|
||||||
|
{
|
||||||
|
sType = VkStructureType.RenderingInfo,
|
||||||
|
renderArea = new VkRect2D
|
||||||
|
{
|
||||||
|
Offset = new VkOffset2D { X = 0, Y = 0 },
|
||||||
|
Extent = new VkExtent2D { Width = VulkanShadowMap.ShadowMapSize, Height = VulkanShadowMap.ShadowMapSize },
|
||||||
|
},
|
||||||
|
layerCount = 1,
|
||||||
|
colorAttachmentCount = 1,
|
||||||
|
pColorAttachments = &shadowColorAttachment,
|
||||||
|
pDepthAttachment = &shadowDepthAttachment,
|
||||||
|
};
|
||||||
|
|
||||||
|
Vk.vkCmdBeginRendering(cmd, &shadowRenderingInfo);
|
||||||
|
Vk.vkCmdBindPipeline(cmd, VkPipelineBindPoint.Graphics, _shadowMap.Pipeline);
|
||||||
|
|
||||||
|
var shadowViewport = new VkViewport
|
||||||
|
{
|
||||||
|
X = 0, Y = 0,
|
||||||
|
Width = VulkanShadowMap.ShadowMapSize,
|
||||||
|
Height = VulkanShadowMap.ShadowMapSize,
|
||||||
|
MinDepth = 0, MaxDepth = 1,
|
||||||
|
};
|
||||||
|
Vk.vkCmdSetViewport(cmd, 0, 1, &shadowViewport);
|
||||||
|
|
||||||
|
var shadowScissor = new VkRect2D
|
||||||
{
|
{
|
||||||
Offset = new VkOffset2D { X = 0, Y = 0 },
|
Offset = new VkOffset2D { X = 0, Y = 0 },
|
||||||
Extent = new VkExtent2D { Width = VulkanShadowMap.ShadowMapSize, Height = VulkanShadowMap.ShadowMapSize },
|
Extent = new VkExtent2D { Width = VulkanShadowMap.ShadowMapSize, Height = VulkanShadowMap.ShadowMapSize },
|
||||||
},
|
};
|
||||||
layerCount = 1,
|
Vk.vkCmdSetScissor(cmd, 0, 1, &shadowScissor);
|
||||||
colorAttachmentCount = 1,
|
Vk.vkCmdSetDepthBias(cmd, 1.75f, 0.0f, 2.5f);
|
||||||
pColorAttachments = &shadowColorAttachment,
|
|
||||||
pDepthAttachment = &shadowDepthAttachment,
|
|
||||||
};
|
|
||||||
|
|
||||||
Vk.vkCmdBeginRendering(cmd, &shadowRenderingInfo);
|
var lpVec4 = new Vector4(lightPosVec, lights[lightIdx].intensity);
|
||||||
|
|
||||||
Vk.vkCmdBindPipeline(cmd, VkPipelineBindPoint.Graphics, _shadowMap.Pipeline);
|
|
||||||
|
|
||||||
var shadowViewport = new VkViewport
|
|
||||||
{
|
|
||||||
X = 0, Y = 0,
|
|
||||||
Width = VulkanShadowMap.ShadowMapSize,
|
|
||||||
Height = VulkanShadowMap.ShadowMapSize,
|
|
||||||
MinDepth = 0, MaxDepth = 1,
|
|
||||||
};
|
|
||||||
Vk.vkCmdSetViewport(cmd, 0, 1, &shadowViewport);
|
|
||||||
|
|
||||||
var shadowScissor = new VkRect2D
|
|
||||||
{
|
|
||||||
Offset = new VkOffset2D { X = 0, Y = 0 },
|
|
||||||
Extent = new VkExtent2D { Width = VulkanShadowMap.ShadowMapSize, Height = VulkanShadowMap.ShadowMapSize },
|
|
||||||
};
|
|
||||||
Vk.vkCmdSetScissor(cmd, 0, 1, &shadowScissor);
|
|
||||||
|
|
||||||
Vk.vkCmdSetDepthBias(cmd, 1.75f, 0.0f, 2.5f);
|
|
||||||
|
|
||||||
foreach (var dc in drawCalls)
|
|
||||||
{
|
|
||||||
if (!dc.castShadow) continue;
|
|
||||||
|
|
||||||
var vertexBuf = dc.vertexBuf;
|
|
||||||
ulong offset = 0;
|
|
||||||
Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuf, &offset);
|
|
||||||
Vk.vkCmdBindIndexBuffer(cmd, dc.indexBuf, 0, 1);
|
|
||||||
|
|
||||||
var pcData = stackalloc byte[176];
|
|
||||||
var modelCopy = dc.model;
|
|
||||||
System.Buffer.MemoryCopy(&modelCopy, pcData, 64, 64);
|
|
||||||
var lpCopy = lightPos;
|
|
||||||
System.Buffer.MemoryCopy(&lpCopy, pcData + 64, 16, 16);
|
|
||||||
var lcCopy = lightColor;
|
|
||||||
System.Buffer.MemoryCopy(&lcCopy, pcData + 80, 16, 16);
|
|
||||||
var lvpCopy = faceViewProj;
|
|
||||||
System.Buffer.MemoryCopy(&lvpCopy, pcData + 96, 64, 64);
|
|
||||||
var sp = new Vector4(ShadowBias, ShadowSampleRadius, ShadowFarPlane, 0);
|
var sp = new Vector4(ShadowBias, ShadowSampleRadius, ShadowFarPlane, 0);
|
||||||
System.Buffer.MemoryCopy(&sp, pcData + 160, 16, 16);
|
|
||||||
|
|
||||||
Vk.vkCmdPushConstants(cmd, _shadowMap.PipelineLayout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 0, 176, pcData);
|
foreach (var dc in drawCalls)
|
||||||
|
{
|
||||||
|
if (!dc.castShadow) continue;
|
||||||
|
|
||||||
Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0);
|
var vertexBuf = dc.vertexBuf;
|
||||||
|
ulong offset = 0;
|
||||||
|
Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuf, &offset);
|
||||||
|
Vk.vkCmdBindIndexBuffer(cmd, dc.indexBuf, 0, 1);
|
||||||
|
|
||||||
|
// Pack shadow push constants: model(64) + lightViewProj(64) + lightPos(16) + shadowParams(16) = 160
|
||||||
|
var pcData = stackalloc byte[160];
|
||||||
|
var modelCopy = dc.model;
|
||||||
|
System.Buffer.MemoryCopy(&modelCopy, pcData, 64, 64);
|
||||||
|
var lvpCopy = faceViewProj;
|
||||||
|
System.Buffer.MemoryCopy(&lvpCopy, pcData + 64, 64, 64);
|
||||||
|
System.Buffer.MemoryCopy(&lpVec4, pcData + 128, 16, 16);
|
||||||
|
System.Buffer.MemoryCopy(&sp, pcData + 144, 16, 16);
|
||||||
|
|
||||||
|
Vk.vkCmdPushConstants(cmd, _shadowMap.PipelineLayout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 0, 160, pcData);
|
||||||
|
|
||||||
|
Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vk.vkCmdEndRendering(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vk.vkCmdEndRendering(cmd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transition shadow color cube: ColorAttachmentOptimal → ShaderReadOnlyOptimal
|
// Transition shadow color array: ColorAttachmentOptimal → ShaderReadOnlyOptimal
|
||||||
TransitionImageLayout(cmd, _shadowMap.ColorImage,
|
TransitionImageLayout(cmd, _shadowMap.ColorImage,
|
||||||
VkImageLayout.ColorAttachmentOptimal, VkImageLayout.ShaderReadOnlyOptimal,
|
VkImageLayout.ColorAttachmentOptimal, VkImageLayout.ShaderReadOnlyOptimal,
|
||||||
0x400, 0x100, 0x8, 0x20, 6);
|
0x400, 0x100, 0x8, 0x20, (uint)totalLayers);
|
||||||
|
|
||||||
// === MAIN PASS ===
|
// === MAIN PASS ===
|
||||||
TransitionImageLayout(cmd, _swapchain.Images[imageIndex],
|
TransitionImageLayout(cmd, _swapchain.Images[imageIndex],
|
||||||
@@ -381,20 +398,9 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
|||||||
Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuf, &offset);
|
Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuf, &offset);
|
||||||
Vk.vkCmdBindIndexBuffer(cmd, dc.indexBuf, 0, 1);
|
Vk.vkCmdBindIndexBuffer(cmd, dc.indexBuf, 0, 1);
|
||||||
|
|
||||||
// Pack all push constants: model(64) + lightPos(16) + lightColor(16) + lightViewProj(64) = 160
|
// Main pass push constants: model only (64B)
|
||||||
var pcData = stackalloc byte[176];
|
var model = dc.model;
|
||||||
var modelCopy = dc.model;
|
Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 0, 64, &model);
|
||||||
System.Buffer.MemoryCopy(&modelCopy, pcData, 64, 64);
|
|
||||||
var lpCopy = lightPos;
|
|
||||||
System.Buffer.MemoryCopy(&lpCopy, pcData + 64, 16, 16);
|
|
||||||
var lcCopy = lightColor;
|
|
||||||
System.Buffer.MemoryCopy(&lcCopy, pcData + 80, 16, 16);
|
|
||||||
var lvpCopy = lightViewProj;
|
|
||||||
System.Buffer.MemoryCopy(&lvpCopy, pcData + 96, 64, 64);
|
|
||||||
var sp = new Vector4(ShadowBias, ShadowSampleRadius, ShadowFarPlane, 0);
|
|
||||||
System.Buffer.MemoryCopy(&sp, pcData + 160, 16, 16);
|
|
||||||
|
|
||||||
Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 0, 176, pcData);
|
|
||||||
|
|
||||||
Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0);
|
Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ namespace Engine.Graphics.Vulkan;
|
|||||||
|
|
||||||
internal sealed unsafe class VulkanShadowMap : IDisposable
|
internal sealed unsafe class VulkanShadowMap : IDisposable
|
||||||
{
|
{
|
||||||
public const uint ShadowMapSize = 2048;
|
public const uint ShadowMapSize = 1024;
|
||||||
public const float FarPlane = 60.0f;
|
public const int MaxShadowLights = 4;
|
||||||
|
|
||||||
public VkImage ColorImage;
|
public VkImage ColorImage;
|
||||||
public VkDeviceMemory ColorImageMemory;
|
public VkDeviceMemory ColorImageMemory;
|
||||||
public VkImageView CubeColorView;
|
public VkImageView CubeArrayColorView;
|
||||||
public VkImageView[] FaceColorViews = new VkImageView[6];
|
public VkImageView[] FaceColorViews = new VkImageView[MaxShadowLights * 6];
|
||||||
|
|
||||||
public VkImage DepthImage;
|
public VkImage DepthImage;
|
||||||
public VkDeviceMemory DepthImageMemory;
|
public VkDeviceMemory DepthImageMemory;
|
||||||
public VkImageView[] FaceDepthViews = new VkImageView[6];
|
public VkImageView[] FaceDepthViews = new VkImageView[MaxShadowLights * 6];
|
||||||
|
|
||||||
public VkSampler ShadowSampler;
|
public VkSampler ShadowSampler;
|
||||||
public VkPipeline Pipeline;
|
public VkPipeline Pipeline;
|
||||||
@@ -35,12 +35,14 @@ internal sealed unsafe class VulkanShadowMap : IDisposable
|
|||||||
CreateShadowSampler();
|
CreateShadowSampler();
|
||||||
CreateShadowPipeline();
|
CreateShadowPipeline();
|
||||||
|
|
||||||
Console.WriteLine("[Vulkan] Shadow cubemap created (1024x1024x6, R32_SFLOAT + D32_SFLOAT)");
|
Console.WriteLine($"[Vulkan] Shadow cubemap array created ({ShadowMapSize}x{ShadowMapSize}x{MaxShadowLights*6} layers)");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateShadowImages()
|
private void CreateShadowImages()
|
||||||
{
|
{
|
||||||
// Color cube (R32_SFLOAT) — stores linear distance
|
int totalLayers = MaxShadowLights * 6;
|
||||||
|
|
||||||
|
// Color cube array (R32_SFLOAT) — stores linear distance
|
||||||
var colorInfo = new VkImageCreateInfo
|
var colorInfo = new VkImageCreateInfo
|
||||||
{
|
{
|
||||||
sType = VkStructureType.ImageCreateInfo,
|
sType = VkStructureType.ImageCreateInfo,
|
||||||
@@ -49,7 +51,7 @@ internal sealed unsafe class VulkanShadowMap : IDisposable
|
|||||||
format = VkFormat.R32Sfloat,
|
format = VkFormat.R32Sfloat,
|
||||||
extent = new VkExtent3D { Width = ShadowMapSize, Height = ShadowMapSize, Depth = 1 },
|
extent = new VkExtent3D { Width = ShadowMapSize, Height = ShadowMapSize, Depth = 1 },
|
||||||
mipLevels = 1,
|
mipLevels = 1,
|
||||||
arrayLayers = 6,
|
arrayLayers = (uint)totalLayers,
|
||||||
samples = VkSampleCountFlags.Count1,
|
samples = VkSampleCountFlags.Count1,
|
||||||
tiling = VkImageTiling.Optimal,
|
tiling = VkImageTiling.Optimal,
|
||||||
usage = VkImageUsageFlags.ColorAttachment | VkImageUsageFlags.Sampled,
|
usage = VkImageUsageFlags.ColorAttachment | VkImageUsageFlags.Sampled,
|
||||||
@@ -70,22 +72,22 @@ internal sealed unsafe class VulkanShadowMap : IDisposable
|
|||||||
ColorImageMemory = colorMem;
|
ColorImageMemory = colorMem;
|
||||||
Vk.vkBindImageMemory(_device, ColorImage, ColorImageMemory, 0);
|
Vk.vkBindImageMemory(_device, ColorImage, ColorImageMemory, 0);
|
||||||
|
|
||||||
// Cube color view for sampling
|
// Cube array color view for sampling
|
||||||
var cubeColorViewInfo = new VkImageViewCreateInfo
|
var cubeArrayViewInfo = new VkImageViewCreateInfo
|
||||||
{
|
{
|
||||||
sType = VkStructureType.ImageViewCreateInfo,
|
sType = VkStructureType.ImageViewCreateInfo,
|
||||||
image = ColorImage,
|
image = ColorImage,
|
||||||
viewType = VkImageViewType.TypeCube,
|
viewType = VkImageViewType.TypeCubeArray,
|
||||||
format = VkFormat.R32Sfloat,
|
format = VkFormat.R32Sfloat,
|
||||||
components = new VkComponentMapping { R = VkComponentSwizzle.Identity, G = VkComponentSwizzle.Identity, B = VkComponentSwizzle.Identity, A = VkComponentSwizzle.Identity },
|
components = new VkComponentMapping { R = VkComponentSwizzle.Identity, G = VkComponentSwizzle.Identity, B = VkComponentSwizzle.Identity, A = VkComponentSwizzle.Identity },
|
||||||
subresourceRange = new VkImageSubresourceRange { AspectMask = VkImageAspectFlags.Color, BaseMipLevel = 0, LevelCount = 1, BaseArrayLayer = 0, LayerCount = 6 },
|
subresourceRange = new VkImageSubresourceRange { AspectMask = VkImageAspectFlags.Color, BaseMipLevel = 0, LevelCount = 1, BaseArrayLayer = 0, LayerCount = (uint)totalLayers },
|
||||||
};
|
};
|
||||||
var cubeCV = VkImageView.Null;
|
var cubeArrayView = VkImageView.Null;
|
||||||
Vk.vkCreateImageView(_device, &cubeColorViewInfo, 0, &cubeCV);
|
Vk.vkCreateImageView(_device, &cubeArrayViewInfo, 0, &cubeArrayView);
|
||||||
CubeColorView = cubeCV;
|
CubeArrayColorView = cubeArrayView;
|
||||||
|
|
||||||
// 6 face color views
|
// Face color views for rendering
|
||||||
for (int i = 0; i < 6; i++)
|
for (int i = 0; i < totalLayers; i++)
|
||||||
{
|
{
|
||||||
var faceViewInfo = new VkImageViewCreateInfo
|
var faceViewInfo = new VkImageViewCreateInfo
|
||||||
{
|
{
|
||||||
@@ -101,7 +103,7 @@ internal sealed unsafe class VulkanShadowMap : IDisposable
|
|||||||
FaceColorViews[i] = fv;
|
FaceColorViews[i] = fv;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Depth cube (D32_SFLOAT) — for depth testing during shadow render
|
// Depth cube array (D32_SFLOAT)
|
||||||
var depthInfo = new VkImageCreateInfo
|
var depthInfo = new VkImageCreateInfo
|
||||||
{
|
{
|
||||||
sType = VkStructureType.ImageCreateInfo,
|
sType = VkStructureType.ImageCreateInfo,
|
||||||
@@ -110,7 +112,7 @@ internal sealed unsafe class VulkanShadowMap : IDisposable
|
|||||||
format = VkFormat.D32Sfloat,
|
format = VkFormat.D32Sfloat,
|
||||||
extent = new VkExtent3D { Width = ShadowMapSize, Height = ShadowMapSize, Depth = 1 },
|
extent = new VkExtent3D { Width = ShadowMapSize, Height = ShadowMapSize, Depth = 1 },
|
||||||
mipLevels = 1,
|
mipLevels = 1,
|
||||||
arrayLayers = 6,
|
arrayLayers = (uint)totalLayers,
|
||||||
samples = VkSampleCountFlags.Count1,
|
samples = VkSampleCountFlags.Count1,
|
||||||
tiling = VkImageTiling.Optimal,
|
tiling = VkImageTiling.Optimal,
|
||||||
usage = VkImageUsageFlags.DepthStencilAttachment,
|
usage = VkImageUsageFlags.DepthStencilAttachment,
|
||||||
@@ -131,8 +133,8 @@ internal sealed unsafe class VulkanShadowMap : IDisposable
|
|||||||
DepthImageMemory = depthMem;
|
DepthImageMemory = depthMem;
|
||||||
Vk.vkBindImageMemory(_device, DepthImage, DepthImageMemory, 0);
|
Vk.vkBindImageMemory(_device, DepthImage, DepthImageMemory, 0);
|
||||||
|
|
||||||
// 6 face depth views
|
// Face depth views
|
||||||
for (int i = 0; i < 6; i++)
|
for (int i = 0; i < totalLayers; i++)
|
||||||
{
|
{
|
||||||
var faceDepthViewInfo = new VkImageViewCreateInfo
|
var faceDepthViewInfo = new VkImageViewCreateInfo
|
||||||
{
|
{
|
||||||
@@ -323,7 +325,7 @@ internal sealed unsafe class VulkanShadowMap : IDisposable
|
|||||||
pDynamicStates = dynamicStates,
|
pDynamicStates = dynamicStates,
|
||||||
};
|
};
|
||||||
|
|
||||||
var pushConstantRange = new VkPushConstantRange { stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, offset = 0, size = 176 };
|
var pushConstantRange = new VkPushConstantRange { stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, offset = 0, size = 160 };
|
||||||
|
|
||||||
var layoutInfo = new VkPipelineLayoutCreateInfo
|
var layoutInfo = new VkPipelineLayoutCreateInfo
|
||||||
{
|
{
|
||||||
@@ -415,8 +417,8 @@ internal sealed unsafe class VulkanShadowMap : IDisposable
|
|||||||
if (PipelineLayout.Handle != 0) Vk.vkDestroyPipelineLayout(_device, PipelineLayout, 0);
|
if (PipelineLayout.Handle != 0) Vk.vkDestroyPipelineLayout(_device, PipelineLayout, 0);
|
||||||
if (VertModule.Handle != 0) Vk.vkDestroyShaderModule(_device, VertModule, 0);
|
if (VertModule.Handle != 0) Vk.vkDestroyShaderModule(_device, VertModule, 0);
|
||||||
if (ShadowSampler.Handle != 0) Vk.vkDestroySampler(_device, ShadowSampler, 0);
|
if (ShadowSampler.Handle != 0) Vk.vkDestroySampler(_device, ShadowSampler, 0);
|
||||||
if (CubeColorView.Handle != 0) Vk.vkDestroyImageView(_device, CubeColorView, 0);
|
if (CubeArrayColorView.Handle != 0) Vk.vkDestroyImageView(_device, CubeArrayColorView, 0);
|
||||||
for (int i = 0; i < 6; i++)
|
for (int i = 0; i < MaxShadowLights * 6; i++)
|
||||||
{
|
{
|
||||||
if (FaceColorViews[i].Handle != 0) Vk.vkDestroyImageView(_device, FaceColorViews[i], 0);
|
if (FaceColorViews[i].Handle != 0) Vk.vkDestroyImageView(_device, FaceColorViews[i], 0);
|
||||||
if (FaceDepthViews[i].Handle != 0) Vk.vkDestroyImageView(_device, FaceDepthViews[i], 0);
|
if (FaceDepthViews[i].Handle != 0) Vk.vkDestroyImageView(_device, FaceDepthViews[i], 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user