feat: ImGui shadow parameters panel with clipboard copy
- Push constant block expanded: 160B → 176B (added vec4 shadowParams) - shadowParams: x=bias, y=sampleRadius, z=farPlane, w=unused - Fragment shader: bias and sampleRadius now read from push constants (dynamic, adjustable at runtime) - Shadow.frag: uses pc.shadowParams.z for farPlane (dynamic) - Slope-dependent bias: bias + bias*2*(1-NdotL) — adaptive - VulkanRenderer: public ShadowBias, ShadowSampleRadius, ShadowFarPlane properties - Program.cs: ImGui 'Shadow Parameters' panel with sliders: - Light Intensity (0-100), Range (5-100), RGB color - Shadow Bias (0.001-0.5), Sample Radius (0.001-0.1), Far Plane (10-120) - 'Copy Parameters to Clipboard' button — outputs all values as text - 227 tests, all pass
This commit is contained in:
@@ -23,3 +23,8 @@ Pos=60,60
|
||||
Size=226,196
|
||||
Collapsed=0
|
||||
|
||||
[Window][Shadow Parameters]
|
||||
Pos=60,60
|
||||
Size=318,263
|
||||
Collapsed=0
|
||||
|
||||
|
||||
@@ -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<Light>();
|
||||
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<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();
|
||||
|
||||
renderer.EndImGuiFrame();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -7,6 +7,7 @@ layout(push_constant) uniform PC {
|
||||
vec4 lightPos;
|
||||
vec4 lightColor;
|
||||
mat4 lightViewProj;
|
||||
vec4 shadowParams;
|
||||
} pc;
|
||||
|
||||
layout(location = 0) out vec3 fragPos;
|
||||
|
||||
Binary file not shown.
@@ -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;
|
||||
|
||||
|
||||
Binary file not shown.
@@ -17,6 +17,7 @@ layout(push_constant) uniform PC {
|
||||
vec4 lightPos;
|
||||
vec4 lightColor;
|
||||
mat4 lightViewProj;
|
||||
vec4 shadowParams;
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
|
||||
Binary file not shown.
@@ -154,7 +154,7 @@ internal sealed unsafe class VulkanPipeline : IDisposable
|
||||
{
|
||||
stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment,
|
||||
offset = 0,
|
||||
size = 160,
|
||||
size = 176,
|
||||
};
|
||||
|
||||
var descLayout = DescriptorSetLayout;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user