diff --git a/imgui.ini b/imgui.ini index 4e39eef..b286810 100644 --- a/imgui.ini +++ b/imgui.ini @@ -23,3 +23,8 @@ Pos=60,60 Size=226,196 Collapsed=0 +[Window][Shadow Parameters] +Pos=60,60 +Size=318,263 +Collapsed=0 + diff --git a/src/CortexEngine.App/Program.cs b/src/CortexEngine.App/Program.cs index 704a5be..1cd0bad 100644 --- a/src/CortexEngine.App/Program.cs +++ b/src/CortexEngine.App/Program.cs @@ -59,6 +59,7 @@ class Program CreateScene(world); var hasImGui = renderer.GetType().Name == "VulkanRenderer"; + VulkanRenderer? vkRenderer = hasImGui ? (VulkanRenderer)renderer : null; if (hasImGui) { ImGui.GetIO().DisplaySize = new System.Numerics.Vector2(window.Width, window.Height); @@ -170,6 +171,62 @@ class Program ImGui.Text("WASD: move | RMB: look | Q/E: up/down | Shift: boost"); ImGui.End(); + // Shadow parameters panel + ImGui.Begin("Shadow Parameters"); + + var lightEntity2 = world.Lookup("MainLight"); + if ((ulong)lightEntity2.Id != 0) + { + var lightComp = lightEntity2.Get(); + var intensity = lightComp.Intensity; + var range = lightComp.Range; + var colorR = lightComp.Color.X; + var colorG = lightComp.Color.Y; + var colorB = lightComp.Color.Z; + + ImGui.SliderFloat("Light Intensity", ref intensity, 0.0f, 100.0f, "%.1f"); + ImGui.SliderFloat("Light Range", ref range, 5.0f, 100.0f, "%.1f"); + ImGui.SliderFloat("Light R", ref colorR, 0.0f, 1.0f, "%.2f"); + ImGui.SliderFloat("Light G", ref colorG, 0.0f, 1.0f, "%.2f"); + ImGui.SliderFloat("Light B", ref colorB, 0.0f, 1.0f, "%.2f"); + ImGui.Separator(); + + lightComp.Intensity = intensity; + lightComp.Range = range; + lightComp.Color = new Vector3(colorR, colorG, colorB); + lightEntity2.Set(lightComp); + } + + var bias = vkRenderer.ShadowBias; + var sampleRadius = vkRenderer.ShadowSampleRadius; + var farPlane = vkRenderer.ShadowFarPlane; + + ImGui.SliderFloat("Shadow Bias", ref bias, 0.001f, 0.5f, "%.4f"); + ImGui.SliderFloat("Sample Radius", ref sampleRadius, 0.001f, 0.1f, "%.4f"); + ImGui.SliderFloat("Far Plane", ref farPlane, 10.0f, 120.0f, "%.1f"); + ImGui.Separator(); + + vkRenderer.ShadowBias = bias; + vkRenderer.ShadowSampleRadius = sampleRadius; + 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(); + 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(); + renderer.EndImGuiFrame(); } diff --git a/src/Engine.Graphics.Vulkan/Shaders/shadow.frag b/src/Engine.Graphics.Vulkan/Shaders/shadow.frag index 0e4f049..a3b124d 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/shadow.frag +++ b/src/Engine.Graphics.Vulkan/Shaders/shadow.frag @@ -8,10 +8,11 @@ layout(push_constant) uniform PC { vec4 lightPos; vec4 lightColor; mat4 lightViewProj; + vec4 shadowParams; } pc; void main() { vec3 toLight = fragPos - pc.lightPos.xyz; float dist = length(toLight); - outDepth = dist / 60.0; + outDepth = dist / pc.shadowParams.z; } diff --git a/src/Engine.Graphics.Vulkan/Shaders/shadow.frag.spv b/src/Engine.Graphics.Vulkan/Shaders/shadow.frag.spv index cea493a..b4fd00e 100644 Binary files a/src/Engine.Graphics.Vulkan/Shaders/shadow.frag.spv and b/src/Engine.Graphics.Vulkan/Shaders/shadow.frag.spv differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/shadow.vert b/src/Engine.Graphics.Vulkan/Shaders/shadow.vert index 764078e..43c43de 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/shadow.vert +++ b/src/Engine.Graphics.Vulkan/Shaders/shadow.vert @@ -7,6 +7,7 @@ layout(push_constant) uniform PC { vec4 lightPos; vec4 lightColor; mat4 lightViewProj; + vec4 shadowParams; } pc; layout(location = 0) out vec3 fragPos; diff --git a/src/Engine.Graphics.Vulkan/Shaders/shadow.vert.spv b/src/Engine.Graphics.Vulkan/Shaders/shadow.vert.spv index a8c8bec..3e5b97b 100644 Binary files a/src/Engine.Graphics.Vulkan/Shaders/shadow.vert.spv and b/src/Engine.Graphics.Vulkan/Shaders/shadow.vert.spv differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag index 8793544..dd9681d 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag @@ -17,11 +17,11 @@ layout(push_constant) uniform PC { vec4 lightPos; vec4 lightColor; mat4 lightViewProj; + vec4 shadowParams; // x=bias, y=sampleRadius, z=farPlane, w=unused } pc; const vec3 AMBIENT = vec3(0.01, 0.01, 0.02); const float PI = 3.14159265359; -const float FAR_PLANE = 60.0; const vec3 POISSON_DISK[16] = vec3[16]( vec3( 0.0000, 0.0000, 0.0000), @@ -94,25 +94,27 @@ mat3 buildTangentBasis(vec3 dir) float calcShadow(vec3 worldPos, vec3 lightPos, vec3 N, vec3 L) { + float bias = pc.shadowParams.x; + float sampleRadius = pc.shadowParams.y; + float farPlane = pc.shadowParams.z; + vec3 dir = worldPos - lightPos; float dist = length(dir); vec3 dirNorm = normalize(dir); - // Slope-dependent bias: small base + larger at grazing angles float NdotL = max(dot(N, L), 0.0); - float bias = 0.08 + 0.15 * (1.0 - NdotL); + float slopeBias = bias + bias * 2.0 * (1.0 - NdotL); - // 16-tap Poisson disk PCF float shadow = 0.0; mat3 basis = buildTangentBasis(dirNorm); - float sampleRadius = clamp(0.015 * (dist / 20.0), 0.003, 0.05); + float radius = clamp(sampleRadius * (dist / 20.0), 0.001, 0.1); for (int i = 0; i < 16; i++) { - vec3 sampleDir = normalize(dirNorm + basis * (POISSON_DISK[i] * sampleRadius)); + vec3 sampleDir = normalize(dirNorm + basis * (POISSON_DISK[i] * radius)); float sampleDepth = texture(shadowCube, sampleDir).r; - float sampleMapped = sampleDepth * FAR_PLANE; - shadow += (dist - bias < sampleMapped) ? 1.0 : 0.0; + float sampleMapped = sampleDepth * farPlane; + shadow += (dist - slopeBias < sampleMapped) ? 1.0 : 0.0; } shadow /= 16.0; diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv index 715eb7e..5751d5b 100644 Binary files a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv and b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv differ diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert index 2c20c93..343bf6b 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert @@ -17,6 +17,7 @@ layout(push_constant) uniform PC { vec4 lightPos; vec4 lightColor; mat4 lightViewProj; + vec4 shadowParams; } pc; void main() { diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv index 9c4591d..8ff8a8d 100644 Binary files a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv and b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv differ diff --git a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs index 5448e95..6807495 100644 --- a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs +++ b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs @@ -154,7 +154,7 @@ internal sealed unsafe class VulkanPipeline : IDisposable { stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, offset = 0, - size = 160, + size = 176, }; var descLayout = DescriptorSetLayout; diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs index fd8552d..f6b5d59 100644 --- a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs @@ -7,7 +7,7 @@ using System.Runtime.InteropServices; namespace Engine.Graphics.Vulkan; -internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreenshotProvider +public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreenshotProvider { private readonly VulkanContext _ctx; private readonly VulkanSwapchain _swapchain; @@ -24,10 +24,14 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen private string? _screenshotPath; private float _totalTime; + public float ShadowBias { get; set; } = 0.08f; + public float ShadowSampleRadius { get; set; } = 0.015f; + public float ShadowFarPlane { get; set; } = 60.0f; + public bool IsScreenshotRequested => _screenshotRequested; public IScreenshotProvider ScreenshotProvider => this; - public VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain) + internal VulkanRenderer(VulkanContext ctx, VulkanSwapchain swapchain) { _ctx = ctx; _swapchain = swapchain; @@ -266,7 +270,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &vertexBuf, &offset); Vk.vkCmdBindIndexBuffer(cmd, dc.indexBuf, 0, 1); - var pcData = stackalloc byte[160]; + var pcData = stackalloc byte[176]; var modelCopy = dc.model; System.Buffer.MemoryCopy(&modelCopy, pcData, 64, 64); var lpCopy = lightPos; @@ -275,8 +279,10 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen 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); + System.Buffer.MemoryCopy(&sp, pcData + 160, 16, 16); - Vk.vkCmdPushConstants(cmd, _shadowMap.PipelineLayout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 0, 160, pcData); + Vk.vkCmdPushConstants(cmd, _shadowMap.PipelineLayout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 0, 176, pcData); Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0); } @@ -376,7 +382,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen Vk.vkCmdBindIndexBuffer(cmd, dc.indexBuf, 0, 1); // Pack all push constants: model(64) + lightPos(16) + lightColor(16) + lightViewProj(64) = 160 - var pcData = stackalloc byte[160]; + var pcData = stackalloc byte[176]; var modelCopy = dc.model; System.Buffer.MemoryCopy(&modelCopy, pcData, 64, 64); var lpCopy = lightPos; @@ -385,8 +391,10 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen 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, 160, pcData); + Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, 0, 176, pcData); Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0); } diff --git a/src/Engine.Graphics.Vulkan/VulkanShadowMap.cs b/src/Engine.Graphics.Vulkan/VulkanShadowMap.cs index 206b508..66d438b 100644 --- a/src/Engine.Graphics.Vulkan/VulkanShadowMap.cs +++ b/src/Engine.Graphics.Vulkan/VulkanShadowMap.cs @@ -323,7 +323,7 @@ internal sealed unsafe class VulkanShadowMap : IDisposable pDynamicStates = dynamicStates, }; - var pushConstantRange = new VkPushConstantRange { stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, offset = 0, size = 160 }; + var pushConstantRange = new VkPushConstantRange { stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, offset = 0, size = 176 }; var layoutInfo = new VkPipelineLayoutCreateInfo {