From 4771371021830049e8c5ab447ec237360d6b1853 Mon Sep 17 00:00:00 2001 From: emil28092005 Date: Thu, 18 Jun 2026 19:43:31 +0300 Subject: [PATCH] fix: restore MemoryCopy for UBO + PBR shader with point light MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UBO packing: MemoryCopy for vp (64B) + lightPos (16B) + lightColor (16B) - PBR fragment shader with Cook-Torrance BRDF + point light - No directional light, near-zero ambient — only point light illuminates - Guards against div-by-zero in attenuation and specular - Depth buffer properly configured in pipeline + rendering info --- .../Shaders/triangle.frag | 92 ++++++++++++++++-- .../Shaders/triangle.frag.spv | Bin 1448 -> 8552 bytes src/Engine.Graphics.Vulkan/VulkanRenderer.cs | 13 ++- 3 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag index dcc2058..9ed81eb 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag @@ -12,13 +12,91 @@ layout(set = 0, binding = 0) uniform CameraUBO { vec4 pointLightColor; }; +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() { - // Debug: just output the light position as color - vec3 lp = pointLightPos.xyz; - float intensity = pointLightPos.w; - - // Map position to visible range - vec3 debugColor = lp * 0.1 + vec3(0.5); - outColor = vec4(debugColor * fragAlbedo, 1.0); + vec3 N = normalize(fragNormal); + vec3 V = normalize(-fragWorldPos); + vec3 albedo = fragAlbedo; + float roughness = 0.5; + float metallic = 0.1; + + vec3 color = AMBIENT * albedo; + + vec3 lightPos = pointLightPos.xyz; + float lightIntensity = pointLightPos.w; + vec3 lightColor = pointLightColor.xyz; + float lightRange = pointLightColor.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; + + 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); + color += (kD * albedo / PI + specular) * radiance * NdotL; + + 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 b7116fbb54417be1e966868f6bb073c5fb4e11c4..ee7a612df1f8d0c398b561bacda9ba1ecf6e2e35 100644 GIT binary patch literal 8552 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 T<{PiyH?%L`Uf*vjJVX8fKKfQw diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs index f340232..93697ba 100644 --- a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs @@ -102,13 +102,12 @@ 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]; - var pFloat = (float*)uboData; - pFloat[0] = vp.M11; pFloat[1] = vp.M12; pFloat[2] = vp.M13; pFloat[3] = vp.M14; - pFloat[4] = vp.M21; pFloat[5] = vp.M22; pFloat[6] = vp.M23; pFloat[7] = vp.M24; - pFloat[8] = vp.M31; pFloat[9] = vp.M32; pFloat[10] = vp.M33; pFloat[11] = vp.M34; - pFloat[12] = vp.M41; pFloat[13] = vp.M42; pFloat[14] = vp.M43; pFloat[15] = vp.M44; - pFloat[16] = lightPos.X; pFloat[17] = lightPos.Y; pFloat[18] = lightPos.Z; pFloat[19] = lightPos.W; - pFloat[20] = lightColor.X; pFloat[21] = lightColor.Y; pFloat[22] = lightColor.Z; pFloat[23] = lightColor.W; + 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)>();