diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag index 58ea62f..164e00c 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag @@ -10,22 +10,97 @@ layout(set = 0, binding = 0) uniform CameraUBO { mat4 vp; }; +layout(push_constant) uniform PC { + mat4 model; + vec4 lightPos; + vec4 lightColor; +} pc; + +const vec3 AMBIENT = vec3(0.01, 0.01, 0.02); +const float PI = 3.14159265359; + +float distributionGGX(vec3 N, vec3 H, float roughness) +{ + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0); + float NdotH2 = NdotH * NdotH; + float num = a2; + float denom = NdotH2 * (a2 - 1.0) + 1.0; + denom = PI * denom * denom; + return num / denom; +} + +float geometrySchlickGGX(float NdotV, float roughness) +{ + float r = roughness + 1.0; + float k = (r * r) / 8.0; + return NdotV / (NdotV * (1.0 - k) + k); +} + +float geometrySmith(vec3 N, vec3 V, vec3 L, float roughness) +{ + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + return geometrySchlickGGX(NdotV, roughness) * geometrySchlickGGX(NdotL, roughness); +} + +vec3 fresnelSchlick(float cosTheta, vec3 F0) +{ + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + +vec3 acesTonemap(vec3 color) +{ + float a = 2.51; + float b = 0.03; + float c = 2.43; + float d = 0.59; + float e = 0.14; + return clamp((color * (a * color + b)) / (color * (c * color + d) + e), 0.0, 1.0); +} + void main() { vec3 N = normalize(fragNormal); - - // Hardcoded light at (0, 10, 0) — test if lighting works at all - vec3 lightPos = vec3(0.0, 10.0, 0.0); + vec3 V = normalize(-fragWorldPos); + vec3 albedo = fragAlbedo; + float roughness = 0.5; + float metallic = 0.1; + + vec3 color = AMBIENT * albedo; + + 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 = normalize(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; + + vec3 H = normalize(V + L); + vec3 F0 = mix(vec3(0.04), albedo, metallic); + + float NDF = distributionGGX(N, H, roughness); + float G = geometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + + vec3 numerator = NDF * G * F; + float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; + vec3 specular = numerator / denominator; + + vec3 kS = F; + vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic); float NdotL = max(dot(N, L), 0.0); - float atten = 1.0 / (1.0 + dist * dist * 0.05); - float intensity = 30.0; + color += (kD * albedo / PI + specular) * radiance * NdotL; - vec3 color = fragAlbedo * vec3(1.0, 0.9, 0.7) * NdotL * intensity * atten; - color += fragAlbedo * 0.02; + color = acesTonemap(color); + color = pow(color, vec3(1.0 / 2.2)); outColor = vec4(color, 1.0); } diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag.spv index 886d561..bd86092 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 e59270c..0182991 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert @@ -10,16 +10,17 @@ layout(location = 2) out vec3 fragAlbedo; layout(set = 0, binding = 0) uniform CameraUBO { mat4 vp; - vec4 pointLightPos; // xyz = position, w = intensity - vec4 pointLightColor; // xyz = color, w = range }; layout(push_constant) uniform PC { mat4 model; + vec4 lightPos; + vec4 lightColor; } pc; void main() { vec4 worldPos = pc.model * vec4(inPosition, 1.0); + gl_Position = pc.model * vec4(inPosition, 1.0); gl_Position = vp * worldPos; fragWorldPos = worldPos.xyz; fragNormal = mat3(pc.model) * inNormal; diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv index 658f908..f3fc4bd 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/VulkanFrameResources.cs b/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs index a203f0f..229f830 100644 --- a/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs +++ b/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs @@ -5,7 +5,7 @@ namespace Engine.Graphics.Vulkan; internal sealed unsafe class VulkanFrameResources : IDisposable { public const int MaxFramesInFlight = 2; - public const ulong UboSize = 128; + public const ulong UboSize = 64; public VkCommandPool CommandPool; public VkCommandBuffer[] CommandBuffers = new VkCommandBuffer[MaxFramesInFlight]; diff --git a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs index 340454e..6cfe5af 100644 --- a/src/Engine.Graphics.Vulkan/VulkanPipeline.cs +++ b/src/Engine.Graphics.Vulkan/VulkanPipeline.cs @@ -150,12 +150,19 @@ internal sealed unsafe class VulkanPipeline : IDisposable pDynamicStates = dynamicStates, }; - var pushConstantRange = new VkPushConstantRange + var pushConstantRanges = stackalloc VkPushConstantRange[2]; + pushConstantRanges[0] = new VkPushConstantRange { - stageFlags = VkShaderStageFlags.Vertex | VkShaderStageFlags.Fragment, + stageFlags = VkShaderStageFlags.Vertex, offset = 0, size = 64, }; + pushConstantRanges[1] = new VkPushConstantRange + { + stageFlags = VkShaderStageFlags.Fragment, + offset = 64, + size = 32, + }; var descLayout = DescriptorSetLayout; var layoutInfo = new VkPipelineLayoutCreateInfo @@ -163,8 +170,8 @@ internal sealed unsafe class VulkanPipeline : IDisposable sType = VkStructureType.PipelineLayoutCreateInfo, setLayoutCount = 1, pSetLayouts = &descLayout, - pushConstantRangeCount = 1, - pPushConstantRanges = (nint)(&pushConstantRange), + pushConstantRangeCount = 2, + pPushConstantRanges = (nint)pushConstantRanges, }; fixed (VkPipelineLayout* layoutPtr = &PipelineLayout) diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs index 93697ba..659bee2 100644 --- a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs @@ -100,14 +100,10 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen }); } - // Pack UBO: mat4 vp (64 bytes) + vec4 lightPos (16) + vec4 lightColor (16) = 96 bytes - var uboData = stackalloc byte[128]; + // Pack UBO: just mat4 vp (64 bytes) + var uboData = stackalloc byte[64]; var vpCopy = vp; System.Buffer.MemoryCopy(&vpCopy, uboData, 64, 64); - var lpCopy = lightPos; - System.Buffer.MemoryCopy(&lpCopy, uboData + 64, 16, 16); - var lcCopy = lightColor; - System.Buffer.MemoryCopy(&lcCopy, uboData + 80, 16, 16); var drawCalls = new List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model)>(); @@ -127,10 +123,10 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen drawCalls.Add((entry.vb.Buffer, entry.ib.Buffer, entry.indexCount, t.GetMatrix())); }); - Render(vp, drawCalls, uboData); + Render(vp, drawCalls, uboData, lightPos, lightColor); } - private void Render(Matrix4x4 vp, List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model)> drawCalls, byte* uboData) + private void Render(Matrix4x4 vp, List<(VkBuffer vertexBuf, VkBuffer indexBuf, uint indexCount, Matrix4x4 model)> drawCalls, byte* uboData, Vector4 lightPos, Vector4 lightColor) { _frameResources.WaitFrame(_frameIndex); @@ -142,7 +138,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen { _swapchain.Recreate(_ctx.SurfaceExtent.Width == 0 ? 1280 : (int)_ctx.SurfaceExtent.Width, _ctx.SurfaceExtent.Height == 0 ? 720 : (int)_ctx.SurfaceExtent.Height); - Render(vp, drawCalls, uboData); + Render(vp, drawCalls, uboData, lightPos, lightColor); return; } @@ -250,6 +246,13 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen var model = dc.model; Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Vertex, 0, 64, &model); + + // Light data as push constants at offset 64 (fragment stage) + var lpCopy = lightPos; + Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Fragment, 64, 16, &lpCopy); + var lcCopy = lightColor; + Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Fragment, 80, 16, &lcCopy); + Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0); }