From 22748ed9ba6694eaa118a2e7bf3b89cea01f9439 Mon Sep 17 00:00:00 2001 From: emil28092005 Date: Thu, 18 Jun 2026 19:18:01 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20PBR=20shading=20=E2=80=94=20Cook-Torran?= =?UTF-8?q?ce=20BRDF=20with=20ACES=20tonemapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Vertex shader: passes world position, world normal, albedo to fragment - Fragment shader: full PBR implementation - D term: Trowbridge-Reitz GGX distribution - G term: Smith geometry with Schlick-GGX - F term: Schlick Fresnel approximation - kD/kS split based on metallic - Directional light (0.5, 0.8, 0.3) with warm color - Ambient term (0.15, 0.18, 0.22) for fill light - ACES filmic tonemapping - Gamma 2.2 correction - Fixed roughness=0.5, metallic=0.1 (per-object materials = future) - No C# code changes — pure shader upgrade --- .../Shaders/triangle.frag | 91 ++++++++++++++++-- .../Shaders/triangle.frag.spv | Bin 1148 -> 7392 bytes .../Shaders/triangle.vert | 9 +- .../Shaders/triangle.vert.spv | Bin 1880 -> 2080 bytes 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag index 8d5bf0d..6995f34 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/triangle.frag +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.frag @@ -1,14 +1,93 @@ #version 450 -layout(location = 0) in vec3 fragColor; +layout(location = 0) in vec3 fragWorldPos; layout(location = 1) in vec3 fragNormal; +layout(location = 2) in vec3 fragAlbedo; + layout(location = 0) out vec4 outColor; -void main() { +const vec3 LIGHT_DIR = normalize(vec3(0.5, 0.8, 0.3)); +const vec3 LIGHT_COLOR = vec3(1.0, 0.95, 0.85); +const vec3 AMBIENT = vec3(0.15, 0.18, 0.22); + +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); + float ggx2 = geometrySchlickGGX(NdotV, roughness); + float ggx1 = geometrySchlickGGX(NdotL, roughness); + return ggx2 * ggx1; +} + +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); - vec3 L = normalize(vec3(0.5, 0.8, 0.3)); - float diff = max(dot(N, L), 0.0); - float ambient = 0.25; - vec3 color = fragColor * (ambient + diff * 0.75); + vec3 V = normalize(-fragWorldPos); + + vec3 albedo = fragAlbedo; + float roughness = 0.5; + float metallic = 0.1; + + vec3 F0 = mix(vec3(0.04), albedo, metallic); + + vec3 L = LIGHT_DIR; + vec3 H = normalize(V + L); + + 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); + vec3 Lo = (kD * albedo / PI + specular) * LIGHT_COLOR * NdotL; + + vec3 ambient = AMBIENT * albedo; + vec3 color = ambient + Lo; + + 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 cb92ce1a6be7802c4e5d26af453fd45d4a691548..33d1e5f33a14ce1451c4453578a492deff583b24 100644 GIT binary patch literal 7392 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 EFKZq=mH+?% diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert index 736c74d..e19ff90 100644 --- a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert +++ b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert @@ -4,8 +4,9 @@ layout(location = 0) in vec3 inPosition; layout(location = 1) in vec3 inColor; layout(location = 2) in vec3 inNormal; -layout(location = 0) out vec3 fragColor; +layout(location = 0) out vec3 fragWorldPos; layout(location = 1) out vec3 fragNormal; +layout(location = 2) out vec3 fragAlbedo; layout(set = 0, binding = 0) uniform CameraUBO { mat4 vp; @@ -16,7 +17,9 @@ layout(push_constant) uniform PC { } pc; void main() { - gl_Position = vp * pc.model * vec4(inPosition, 1.0); - fragColor = inColor; + vec4 worldPos = pc.model * vec4(inPosition, 1.0); + gl_Position = vp * worldPos; + fragWorldPos = worldPos.xyz; fragNormal = mat3(pc.model) * inNormal; + fragAlbedo = inColor; } diff --git a/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv b/src/Engine.Graphics.Vulkan/Shaders/triangle.vert.spv index 834e27ad574172770979739a41d481cb3db94d5d..f511e3428896c4a3335b251ddd7f95ca15a062ca 100644 GIT binary patch literal 2080 zcmZ9M+foxj5QYbm4R`_(Ij9H=ct#Nw5JeymtKfo?Sd@BcMY2*`waHRTJd{_yg0JLL zd1ICTH@gjyN&V^WzYo*XGfAm;w$Hg9cfs|$Etjt$*DJ<}t9ZG+yT3bs+-c4)E-er- z;7UG_m_c{Z>t%hn8gUzAM3@ogg(t!*VMC}2C4G7&e=9j~WqnTb0x{=ge@nBZSx=9p ziYciN=jt`5ZUV2vx6)>m6#h$+M+d|Pe($)g3>LyTDDF0#du^wgbqpTBsmnk;q z(eYVA#r64k&gS{J-PwpbXOe!I@wC7)dnPx%&D>AVQ}*-5rdQ&b zH8y=-423b}(=Rq>4E5bF2?xeypD%R$lgectaK!Q} zFjv@v(pltluf$T1UWO&@ z``wflVrsD;w#{xr_LOXL;3lPol6HQ4fPJSW^B(ejvAH8SW-u#hGq@$4xx&#SHg^n1 ztviKo@pH1N!@l?*6}Fubn>RoV^|1Lr!{$B~C5bgZGvQ722o}$q==N6&|FYgceQ}p8 zV*ZHX4U@~=b074!DVF;g61Y?DZA~$ppzAG^M?q2T|JiSumzCfPUd?1||cb2L6`Zf>PGf(}| zJImAGg7#LoAH>~{Z}#O^)G-+A+YnC4Ev8}OugLGT;CT+8)gK%MJ?(HWX&)Y@wSD{?qZEOImT=w=;(k|!!O%903~{clyU>#P-|0O4LVVA3 z8HRRX?1qs8hCYVjgYmtnjRtA$6Fod(@J$~*DtCSgt1D);RSDf{NpupgPvTOdpWR_v z+8q|>?=UQXhp&3oNzHPiZ$9R25;H$Np=~}!S7Q2D)GvEY^P>m;vb>6RU$&xr`XNTA zCH>B7_hqa`7N3&OeQSzOYljb3m50VwgEPz*@2qzF2D@3zI|bvOxrCS22#nenv@;7BwTbyQV4PpfeE8&|v*oVJr*|-NiFxB-<)L5|6bJjBaw>acnm5~Qd9eS$C zeAx$a(4j5^V;Oc`K3E}#-N-O{??@@Mx+x?}jjN`sU6@GBDP@{!5tmu?!qzF%vUb&Zc~Fz%6G>J~+mj z&e;}54s|T&iF|O3WjP(G%zN08(F?WV+RV@7^ES{eeQzlj936O9yRv_(xFh=yE=X(<