From bc0e4a9e623df6c127a06bfee5bb4c693f71be83 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Tue, 21 Apr 2026 00:25:29 +0200 Subject: [PATCH] Specgloss shaders improvements + experimental SSAO (debug only) --- gl/cubemap.cpp | 8 +- rendering/opengl33renderer.cpp | 76 +++++++++++- rendering/opengl33renderer.h | 13 ++ shaders/envmapping.glsl | 13 ++ shaders/light_common.glsl | 114 +++++++++--------- shaders/mat_default_specgloss.frag | 2 +- shaders/mat_detail_normalmap_specgloss.frag | 2 +- shaders/mat_detail_parallax_specgloss.frag | 4 +- shaders/mat_normalmap_specgloss.frag | 2 +- shaders/mat_parallax_specgloss.frag | 2 +- shaders/mat_reflmap_specgloss.frag | 2 +- .../mat_shadowlessnormalmap_specgloss.frag | 2 +- shaders/mat_sunlessnormalmap_specgloss.frag | 89 +++++++------- shaders/postfx_ssao.frag | 72 +++++++++++ shaders/postfx_ssao_apply.frag | 22 ++++ shaders/postfx_ssao_blur.frag | 14 +++ utilities/Globals.h | 1 + 17 files changed, 326 insertions(+), 112 deletions(-) create mode 100644 shaders/postfx_ssao.frag create mode 100644 shaders/postfx_ssao_apply.frag create mode 100644 shaders/postfx_ssao_blur.frag diff --git a/gl/cubemap.cpp b/gl/cubemap.cpp index 89ef9d1b..1b8d71b9 100644 --- a/gl/cubemap.cpp +++ b/gl/cubemap.cpp @@ -32,7 +32,9 @@ void gl::cubemap::bind(int unit) void gl::cubemap::generate_mipmaps() { - glBindTexture(GL_TEXTURE_CUBE_MAP, *this); - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); + glBindTexture(GL_TEXTURE_CUBE_MAP, *this); + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 44a0c7a5..7871cbdf 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -162,6 +162,35 @@ bool opengl33_renderer::Init(GLFWwindow *Window) m_pfx_tonemapping = std::make_unique("tonemapping"); m_pfx_chromaticaberration = std::make_unique( "chromaticaberration" ); + m_pfx_ssao = std::make_unique("ssao"); + m_pfx_ssao_blur = std::make_unique("ssao_blur"); + m_pfx_ssao_apply = std::make_unique("ssao_apply"); + + // Generate hemisphere kernel (z > 0 = toward surface normal) + std::uniform_real_distribution rnd(0.0f, 1.0f); + std::default_random_engine gen(42); // fixed seed for consistency + for (int i = 0; i < 32; i++) { + glm::vec3 s(rnd(gen)*2.0f-1.0f, rnd(gen)*2.0f-1.0f, rnd(gen)); + s = glm::normalize(s) * rnd(gen); + float scale = float(i) / 32.0f; + scale = glm::mix(0.1f, 1.0f, scale * scale); // accelerating interpolant + m_ssao_kernel[i] = s * scale; + } + + // Generate 4x4 noise texture (random rotation vectors in XY) + std::vector noise_data; + for (int i = 0; i < 16; i++) + noise_data.push_back(glm::normalize(glm::vec3(rnd(gen)*2.0f-1.0f, rnd(gen)*2.0f-1.0f, 0.0f))); + + // noise texture (4x4 random rotation vectors in tangent space) + // noise texture (4x4 random rotation vectors in tangent space) + m_ssao_noise_tex = std::make_unique(); + m_ssao_noise_tex->alloc_rendertarget(GL_RGB32F, GL_RGB, 4, 4, 1, 1, GL_REPEAT); + m_ssao_noise_tex->bind(0); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGB, GL_FLOAT, noise_data.data()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + m_empty_cubemap = std::make_unique(); m_empty_cubemap->alloc(Global.gfx_format_color, 16, 16, GL_RGB, GL_FLOAT); @@ -426,7 +455,7 @@ bool opengl33_renderer::init_viewport(viewport_config &vp) vp.msaa_fb->attach(*vp.msaa_rbc, GL_COLOR_ATTACHMENT0); vp.msaa_fb->attach(*vp.msaa_rbd, GL_DEPTH_ATTACHMENT); - if (Global.gfx_postfx_chromaticaberration_enabled || Global.gfx_postfx_motionblur_enabled) + if (Global.gfx_postfx_chromaticaberration_enabled || Global.gfx_postfx_motionblur_enabled || Global.gfx_postfx_ssao_enabled) { vp.main_tex = std::make_unique(); vp.main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, vp.width, vp.height, 1, 1, GL_CLAMP_TO_EDGE); @@ -470,6 +499,25 @@ bool opengl33_renderer::init_viewport(viewport_config &vp) vp.main2_fb = std::make_unique(); vp.main2_fb->attach(*vp.main2_tex, GL_COLOR_ATTACHMENT0); + + if (Global.gfx_postfx_ssao_enabled) + { + int ssao_w = std::max(1, vp.width / 2); + int ssao_h = std::max(1, vp.height / 2); + + vp.ssao_tex = std::make_unique(); + vp.ssao_tex->alloc_rendertarget(GL_R8, GL_RED, ssao_w, ssao_h, 1, 1, GL_CLAMP_TO_EDGE); + vp.ssao_fb = std::make_unique(); + vp.ssao_fb->attach(*vp.ssao_tex, GL_COLOR_ATTACHMENT0); + if (!vp.ssao_fb->is_complete()) { ErrorLog("ssao framebuffer setup failed"); return false; } + + vp.ssao_blur_tex = std::make_unique(); + vp.ssao_blur_tex->alloc_rendertarget(GL_R8, GL_RED, ssao_w, ssao_h, 1, 1, GL_CLAMP_TO_EDGE); + vp.ssao_blur_fb = std::make_unique(); + vp.ssao_blur_fb->attach(*vp.ssao_blur_tex, GL_COLOR_ATTACHMENT0); + if (!vp.ssao_blur_fb->is_complete()) { ErrorLog("ssao blur framebuffer setup failed"); return false; } + } + if (!vp.main2_fb->is_complete()) return false; } @@ -913,7 +961,7 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) if (!Global.gfx_usegles) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - if (!Global.gfx_skippipeline) +if (!Global.gfx_skippipeline) { if (Global.gfx_postfx_motionblur_enabled) { @@ -927,12 +975,36 @@ void opengl33_renderer::Render_pass(viewport_config &vp, rendermode const Mode) model_ubo->update(model_ubs); m_pfx_motionblur->apply({vp.main_tex.get(), vp.main_texv.get(), vp.main_texd.get()}, vp.main2_fb.get()); } + else if (Global.gfx_postfx_ssao_enabled) + { + // resolve color+depth so SSAO can sample the depth texture + vp.main_fb->clear(GL_COLOR_BUFFER_BIT); + vp.main_fb->setup_drawing(1); + vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); + vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_DEPTH_BUFFER_BIT, GL_DEPTH_ATTACHMENT); + } else { vp.main2_fb->clear(GL_COLOR_BUFFER_BIT); vp.msaa_fb->blit_to(vp.main2_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); } + // SSAO: depth -> occlusion -> blur -> modulate color into main2_fb + if (Global.gfx_postfx_ssao_enabled && !Global.gfx_postfx_motionblur_enabled) + { + int ssao_w = std::max(1, vp.width / 2); + int ssao_h = std::max(1, vp.height / 2); + + // (kernel is generated procedurally in the shader; no UBO upload needed) + glViewport(0, 0, ssao_w, ssao_h); + m_pfx_ssao ->apply({ vp.main_texd.get(), m_ssao_noise_tex.get() }, vp.ssao_fb.get()); + m_pfx_ssao_blur->apply({ vp.ssao_tex.get() }, vp.ssao_blur_fb.get()); + + glViewport(0, 0, vp.width, vp.height); + vp.main2_fb->clear(GL_COLOR_BUFFER_BIT); + m_pfx_ssao_apply->apply({ vp.main_tex.get(), vp.ssao_blur_tex.get() }, vp.main2_fb.get()); + } + if (!Global.gfx_usegles && !Global.gfx_shadergamma) glEnable(GL_FRAMEBUFFER_SRGB); diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index 04ead7f0..f3271154 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -227,6 +227,12 @@ class opengl33_renderer : public gfx_renderer { std::unique_ptr main2_fb; std::unique_ptr main2_tex; + // ssao + std::unique_ptr ssao_tex; + std::unique_ptr ssao_blur_tex; + std::unique_ptr ssao_fb; + std::unique_ptr ssao_blur_fb; + // LDR backbuffer for offscreen rendering std::unique_ptr backbuffer_fb; std::unique_ptr backbuffer_tex; @@ -396,6 +402,13 @@ class opengl33_renderer : public gfx_renderer { std::unique_ptr m_pfx_tonemapping; std::unique_ptr m_pfx_chromaticaberration; + // postfx ssao + std::unique_ptr m_pfx_ssao; + std::unique_ptr m_pfx_ssao_blur; + std::unique_ptr m_pfx_ssao_apply; + std::unique_ptr m_ssao_noise_tex; + glm::vec3 m_ssao_kernel[32]; + std::unique_ptr m_shadow_shader; std::unique_ptr m_alpha_shadow_shader; diff --git a/shaders/envmapping.glsl b/shaders/envmapping.glsl index 72a0ddc1..019d742b 100644 --- a/shaders/envmapping.glsl +++ b/shaders/envmapping.glsl @@ -13,3 +13,16 @@ vec3 envmap_color( vec3 normal ) #endif return envcolor; } + +// Roughness-aware env map lookup — uses mip levels for blurry reflections. +// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur. +vec3 envmap_color_lod(vec3 fragnormal, float lod) +{ +#if ENVMAP_ENABLED + vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly + refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing + return textureLod(envmap, refvec, lod).rgb; +#else + return vec3(0.5); // was vec3(0.0), match the non-LOD fallback +#endif +} \ No newline at end of file diff --git a/shaders/light_common.glsl b/shaders/light_common.glsl index fb10aeda..7fe909fb 100644 --- a/shaders/light_common.glsl +++ b/shaders/light_common.glsl @@ -60,7 +60,15 @@ vec2 calc_light(vec3 light_dir, vec3 fragnormal) vec3 halfway_dir = normalize(light_dir + view_dir); float diffuse_v = max(dot(fragnormal, light_dir), 0.0); - float specular_v = pow(max(dot(fragnormal, halfway_dir), 0.0), max(glossiness, 0.01)) * diffuse_v; + + // Energy-conserving Blinn-Phong normalization: + // (n+8)/(8*pi) ensures the specular lobe integrates to the same + // total energy regardless of glossiness — low glossiness stays dim + // and spreads wide (blurry), high glossiness is bright and tight (sharp). + float n = max(glossiness, 0.01); + float normalization = (n + 8.0) / (8.0 * 3.14159265); + float NdotH = max(dot(fragnormal, halfway_dir), 0.0); + float specular_v = normalization * pow(NdotH, n); return vec2(diffuse_v, specular_v); } @@ -121,71 +129,65 @@ vec2 calc_headlights(light_s light, vec3 fragnormal) // do magic here vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) { - vec3 basecolor = param[0].rgb; + vec3 basecolor = param[0].rgb; + fragcolor *= basecolor; - fragcolor *= basecolor; + vec3 emissioncolor = basecolor * emission; - vec3 emissioncolor = basecolor * emission; - vec3 envcolor = envmap_color(fragnormal); + vec3 view_dir = normalize(-f_pos.xyz); + float NdotV = max(dot(fragnormal, view_dir), 0.0); + vec3 F0 = mix(vec3(0.04), texturecolor, metalic); + vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0); -// yuv path - vec3 texturecoloryuv = rgb2yuv(texturecolor); - vec3 texturecolorfullv = yuv2rgb(vec3(0.2176, texturecoloryuv.gb)); -// hsl path -// vec3 texturecolorhsl = rgb2hsl(texturecolor); -// vec3 texturecolorfullv = hsl2rgb(vec3(texturecolorhsl.rg, 0.5)); + const float MAX_REFLECTION_LOD = 8.0; + float env_roughness = 1.0 - clamp(glossiness / max(abs(param[1].w), 1.0), 0.0, 1.0); + vec3 envcolor = envmap_color_lod(fragnormal, env_roughness * MAX_REFLECTION_LOD); - vec3 envyuv = rgb2yuv(envcolor); - texturecolor = mix(texturecolor, texturecolorfullv, envyuv.r * reflectivity); + // Tint texture toward fully-saturated under strong env, weighted by fresnel + vec3 texturecoloryuv = rgb2yuv(texturecolor); + vec3 texturecolorfullv = yuv2rgb(vec3(0.2176, texturecoloryuv.gb)); + vec3 envyuv = rgb2yuv(envcolor); + texturecolor = mix(texturecolor, texturecolorfullv, envyuv.r * reflectivity * fresnel.r); - if(lights_count == 0U) - return (fragcolor + emissioncolor + envcolor * reflectivity) * texturecolor; + if (lights_count == 0U) + return (fragcolor + emissioncolor) * texturecolor + + envcolor * fresnel * reflectivity; -// fragcolor *= lights[0].intensity; - vec2 sunlight = calc_dir_light(lights[0], fragnormal); + vec2 sunlight = calc_dir_light(lights[0], fragnormal); + float diffuseamount = sunlight.x * param[1].x * lights[0].intensity; - float diffuseamount = (sunlight.x * param[1].x) * lights[0].intensity; -// fragcolor += mix(lights[0].color * diffuseamount, envcolor, reflectivity); - float shadow1 = 0.0; - if (shadowtone < 1.0) - { - shadow1 = (1 - shadowtone) * clamp(calc_shadow(), 0.0, 1.00); - } - fragcolor += lights[0].color * 5.0 * (1.0 - shadow1) * diffuseamount; - fragcolor = mix(fragcolor * 0.35, envcolor, reflectivity); + float shadow1 = 0.0; + if (shadowtone < 1.0) + shadow1 = (1.0 - shadowtone) * clamp(calc_shadow(), 0.0, 1.0); - for (uint i = 1U; i < lights_count; i++) - { - light_s light = lights[i]; - vec2 part = vec2(0.0); + fragcolor += lights[0].color * 5.0 * (1.0 - shadow1) * diffuseamount; -// if (light.type == LIGHT_SPOT) -// part = calc_spot_light(light, fragnormal); -// else if (light.type == LIGHT_POINT) -// part = calc_point_light(light, fragnormal); -// else if (light.type == LIGHT_DIR) -// part = calc_dir_light(light, fragnormal); -// else if (light.type == LIGHT_HEADLIGHTS) - part = calc_headlights(light, fragnormal); + for (uint i = 1U; i < lights_count; i++) + { + light_s light = lights[i]; + vec2 part = calc_headlights(light, fragnormal); + fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity; + } - fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity; - } + float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity + * clamp(1.0 - shadowtone, 0.0, 1.0); + if (shadowtone < 1.0) + specularamount *= clamp(1.0 - shadow1, 0.0, 1.0); - float specularamount = (sunlight.y * param[1].y * specularity) * lights[0].intensity * clamp(1.0 - shadowtone, 0.0, 1.0); - if (shadowtone < 1.0) - { - //float shadow = calc_shadow(); - specularamount *= clamp(1.0 - shadow1, 0.0, 1.00); - //fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp((diffuseamount) * shadow1 , 0.0, 1.0)); - } - fragcolor += emissioncolor; - vec3 specularcolor = specularamount * lights[0].color; + fragcolor += emissioncolor; + vec3 specularcolor = specularamount * lights[0].color; - if (param[1].w < 0.0) - { - float metalic = 1.0; - } - fragcolor = mix(((fragcolor + specularcolor) * texturecolor),(fragcolor * texturecolor + specularcolor),metalic) ; + // Env reflection tracked separately — must NOT go through the albedo multiply below + vec3 env_reflection = envcolor * fresnel * reflectivity * (1.0 - shadow1 * 0.5); - return fragcolor; -} + // Diffuse + specular: albedo tints diffuse, metals also tint specular + vec3 result = mix( + (fragcolor + specularcolor) * texturecolor, + fragcolor * texturecolor + specularcolor, + metalic); + + // Env added after albedo multiply: raw for dielectrics, albedo-tinted for metals + result += mix(env_reflection, env_reflection * texturecolor, metalic); + + return result; +} \ No newline at end of file diff --git a/shaders/mat_default_specgloss.frag b/shaders/mat_default_specgloss.frag index cb7a2e5a..254a0cc7 100644 --- a/shaders/mat_default_specgloss.frag +++ b/shaders/mat_default_specgloss.frag @@ -43,7 +43,7 @@ void main() float reflectivity = param[1].z; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); diff --git a/shaders/mat_detail_normalmap_specgloss.frag b/shaders/mat_detail_normalmap_specgloss.frag index fbd90257..a3799ce1 100644 --- a/shaders/mat_detail_normalmap_specgloss.frag +++ b/shaders/mat_detail_normalmap_specgloss.frag @@ -68,7 +68,7 @@ void main() float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend; float specularity = specgloss_map.r; glossiness = specgloss_map.g * abs(param[1].w); - float metalic = specgloss_map.b; + metalic = specgloss_map.b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_detail_parallax_specgloss.frag b/shaders/mat_detail_parallax_specgloss.frag index 416a6854..fcc33ac3 100644 --- a/shaders/mat_detail_parallax_specgloss.frag +++ b/shaders/mat_detail_parallax_specgloss.frag @@ -73,8 +73,8 @@ void main() vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z))); float reflectivity = param[1].z * normal_map.a; float specularity = specgloss_map.r; - float glossiness = specgloss_map.g * abs(param[1].w); - float metalic = specgloss_map.b; + glossiness = specgloss_map.g * abs(param[1].w); + metalic = specgloss_map.b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_normalmap_specgloss.frag b/shaders/mat_normalmap_specgloss.frag index 86085df2..a4b18462 100644 --- a/shaders/mat_normalmap_specgloss.frag +++ b/shaders/mat_normalmap_specgloss.frag @@ -53,7 +53,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_parallax_specgloss.frag b/shaders/mat_parallax_specgloss.frag index 4ce17da7..8b590fae 100644 --- a/shaders/mat_parallax_specgloss.frag +++ b/shaders/mat_parallax_specgloss.frag @@ -61,7 +61,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord_p).a; float specularity = texture(specgloss, f_coord_p).r; glossiness = texture(specgloss, f_coord_p).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord_p).b; + metalic = texture(specgloss, f_coord_p).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_reflmap_specgloss.frag b/shaders/mat_reflmap_specgloss.frag index b5f9bc38..4c89083c 100644 --- a/shaders/mat_reflmap_specgloss.frag +++ b/shaders/mat_reflmap_specgloss.frag @@ -47,7 +47,7 @@ void main() float reflectivity = param[1].z * texture(reflmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); diff --git a/shaders/mat_shadowlessnormalmap_specgloss.frag b/shaders/mat_shadowlessnormalmap_specgloss.frag index 58175c7c..adc473ff 100644 --- a/shaders/mat_shadowlessnormalmap_specgloss.frag +++ b/shaders/mat_shadowlessnormalmap_specgloss.frag @@ -53,7 +53,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, 1.0); vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); diff --git a/shaders/mat_sunlessnormalmap_specgloss.frag b/shaders/mat_sunlessnormalmap_specgloss.frag index cf5a7433..f53381f2 100644 --- a/shaders/mat_sunlessnormalmap_specgloss.frag +++ b/shaders/mat_sunlessnormalmap_specgloss.frag @@ -36,59 +36,62 @@ uniform sampler2D specgloss; vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) { - vec3 basecolor = param[0].rgb; + vec3 basecolor = param[0].rgb; + fragcolor *= basecolor; - fragcolor *= basecolor; + vec3 emissioncolor = basecolor * emission; - vec3 emissioncolor = basecolor * emission; - vec3 envcolor = envmap_color(fragnormal); + vec3 view_dir = normalize(-f_pos.xyz); + float NdotV = max(dot(fragnormal, view_dir), 0.0); + vec3 F0 = mix(vec3(0.04), texturecolor, metalic); + vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - NdotV, 5.0); -// yuv path - vec3 texturecoloryuv = rgb2yuv(texturecolor); - vec3 texturecolorfullv = yuv2rgb(vec3(0.2176, texturecoloryuv.gb)); -// hsl path -// vec3 texturecolorhsl = rgb2hsl(texturecolor); -// vec3 texturecolorfullv = hsl2rgb(vec3(texturecolorhsl.rg, 0.5)); + const float MAX_REFLECTION_LOD = 8.0; + float env_roughness = 1.0 - clamp(glossiness / max(abs(param[1].w), 1.0), 0.0, 1.0); + vec3 envcolor = envmap_color_lod(fragnormal, env_roughness * MAX_REFLECTION_LOD); - vec3 envyuv = rgb2yuv(envcolor); - texturecolor = mix(texturecolor, texturecolorfullv, envyuv.r * reflectivity); + vec3 texturecoloryuv = rgb2yuv(texturecolor); + vec3 texturecolorfullv = yuv2rgb(vec3(0.2176, texturecoloryuv.gb)); + vec3 envyuv = rgb2yuv(envcolor); + texturecolor = mix(texturecolor, texturecolorfullv, envyuv.r * reflectivity * fresnel.r); - if(lights_count == 0U) - return (fragcolor + emissioncolor + envcolor * reflectivity) * texturecolor; + if (lights_count == 0U) + return (fragcolor + emissioncolor) * texturecolor + + envcolor * fresnel * reflectivity; - vec2 sunlight = calc_dir_light(lights[0], fragnormal); + vec2 sunlight = calc_dir_light(lights[0], fragnormal); + float diffuseamount = sunlight.x * param[1].x * lights[0].intensity; - float diffuseamount = (sunlight.x * param[1].x) * lights[0].intensity; - fragcolor += envcolor * reflectivity; - float specularamount = (sunlight.y * param[1].y * specularity) * lights[0].intensity; + float specularamount = sunlight.y * param[1].y * specularity * lights[0].intensity; - for (uint i = 1U; i < lights_count; i++) - { - light_s light = lights[i]; - vec2 part = vec2(0.0); + for (uint i = 1U; i < lights_count; i++) + { + light_s light = lights[i]; + vec2 part = calc_headlights(light, fragnormal); + fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity; + } -// if (light.type == LIGHT_SPOT) -// part = calc_spot_light(light, fragnormal); -// else if (light.type == LIGHT_POINT) -// part = calc_point_light(light, fragnormal); -// else if (light.type == LIGHT_DIR) -// part = calc_dir_light(light, fragnormal); -// else if (light.type == LIGHT_HEADLIGHTS) - part = calc_headlights(light, fragnormal); + if (shadowtone < 1.0) + { + float shadow = calc_shadow(); + specularamount *= clamp(1.0 - shadow, 0.0, 1.0); + fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * shadow, 0.0, 1.0)); + } - fragcolor += light.color * (part.x * param[1].x + part.y * param[1].y) * light.intensity; - } + fragcolor += emissioncolor; + vec3 specularcolor = specularamount * lights[0].color; - if (shadowtone < 1.0) - { - float shadow = calc_shadow(); - specularamount *= clamp(1.0 - shadow, 0.0, 1.0); - fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * shadow + specularamount, 0.0, 1.0)); - } - fragcolor += emissioncolor; - fragcolor *= texturecolor; - - return fragcolor; + // Env reflection separate from albedo multiply — same fix as apply_lights + vec3 env_reflection = envcolor * fresnel * reflectivity; + + vec3 result = mix( + (fragcolor + specularcolor) * texturecolor, + fragcolor * texturecolor + specularcolor, + metalic); + + result += mix(env_reflection, env_reflection * texturecolor, metalic); + + return result; } void main() @@ -110,7 +113,7 @@ void main() float reflectivity = param[1].z * texture(normalmap, f_coord).a; float specularity = texture(specgloss, f_coord).r; glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - float metalic = texture(specgloss, f_coord).b; + metalic = texture(specgloss, f_coord).b; fragcolor = apply_lights_sunless(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone); vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); diff --git a/shaders/postfx_ssao.frag b/shaders/postfx_ssao.frag new file mode 100644 index 00000000..05ff9692 --- /dev/null +++ b/shaders/postfx_ssao.frag @@ -0,0 +1,72 @@ +in vec2 f_coords; +layout(location = 0) out vec4 out_color; + +#texture (depth_tex, 0, R) +uniform sampler2D depth_tex; + +#texture (noise_tex, 1, RGB) +uniform sampler2D noise_tex; + +#include + +vec3 view_pos_from_depth(vec2 uv, float depth) { + vec4 clip = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + vec4 view = inverse(projection) * clip; + return view.xyz / view.w; +} + +// Deterministic hemisphere sample (z > 0), mimics the C++ kernel generator +vec3 kernel_sample(int i) { + float fi = float(i); + vec3 dir = normalize(vec3( + fract(sin(fi * 12.9898) * 43758.5453) * 2.0 - 1.0, + fract(sin(fi * 78.2330) * 43758.5453) * 2.0 - 1.0, + fract(sin(fi * 37.7190) * 43758.5453) // z >= 0 -> hemisphere + )); + float len = fract(sin(fi * 94.6720) * 43758.5453); + float scale = fi / 32.0; + scale = mix(0.1, 1.0, scale * scale); // bias samples inward + return dir * len * scale; +} + +void main() { + float d = texture(depth_tex, f_coords).r; + if (d >= 1.0) { out_color = vec4(1.0); return; } // skybox -> no occlusion + + vec3 pos = view_pos_from_depth(f_coords, d); + + vec3 ddx = dFdx(pos); + vec3 ddy = dFdy(pos); + vec3 n = normalize(cross(ddx, ddy)); + + // derive screen size from the depth texture instead of a UBO field + vec2 screen_size = vec2(textureSize(depth_tex, 0)); + vec2 noise_uv = f_coords * (screen_size / 4.0); + vec3 rvec = texture(noise_tex, noise_uv).xyz; + + vec3 t = normalize(rvec - n * dot(rvec, n)); + vec3 b = cross(n, t); + mat3 TBN = mat3(t, b, n); + + const int KERNEL = 32; + const float RADIUS = 0.5; + const float BIAS = 0.025; + + float occ = 0.0; + for (int i = 0; i < KERNEL; ++i) { + vec3 sp = TBN * kernel_sample(i); + sp = pos + sp * RADIUS; + + vec4 clip = projection * vec4(sp, 1.0); + vec3 ndc = clip.xyz / clip.w; + vec2 suv = ndc.xy * 0.5 + 0.5; + + float sd = texture(depth_tex, suv).r; + float szv = view_pos_from_depth(suv, sd).z; + + float range = smoothstep(0.0, 1.0, RADIUS / abs(pos.z - szv)); + occ += (szv >= sp.z + BIAS ? 1.0 : 0.0) * range; + } + + out_color = vec4(1.0 - occ / float(KERNEL)); +} \ No newline at end of file diff --git a/shaders/postfx_ssao_apply.frag b/shaders/postfx_ssao_apply.frag new file mode 100644 index 00000000..177c5b29 --- /dev/null +++ b/shaders/postfx_ssao_apply.frag @@ -0,0 +1,22 @@ +in vec2 f_coords; +layout(location = 0) out vec4 out_color; + +#texture (color_tex, 0, RGB) +uniform sampler2D color_tex; + +#texture (ssao_tex, 1, R) +uniform sampler2D ssao_tex; + +const float SSAO_STRENGTH = 0.8; +const float SSAO_MIN = 0.3; // floor on darkening so shadows don't go pitch black + +void main() { + vec3 c = texture(color_tex, f_coords).rgb; + float occ = texture(ssao_tex, f_coords).r; + + // remap occlusion with strength and a minimum floor + occ = pow(clamp(occ, 0.0, 1.0), SSAO_STRENGTH); + occ = mix(SSAO_MIN, 1.0, occ); + + out_color = vec4(c * occ, 1.0); +} \ No newline at end of file diff --git a/shaders/postfx_ssao_blur.frag b/shaders/postfx_ssao_blur.frag new file mode 100644 index 00000000..e3cbfb0b --- /dev/null +++ b/shaders/postfx_ssao_blur.frag @@ -0,0 +1,14 @@ +in vec2 f_coords; +layout(location = 0) out vec4 out_color; + +#texture (ssao_tex, 0, R) +uniform sampler2D ssao_tex; + +void main() { + vec2 texel = 1.0 / vec2(textureSize(ssao_tex, 0)); + float sum = 0.0; + for (int x = -2; x < 2; ++x) + for (int y = -2; y < 2; ++y) + sum += texture(ssao_tex, f_coords + vec2(x, y) * texel).r; + out_color = vec4(sum / 16.0); +} \ No newline at end of file diff --git a/utilities/Globals.h b/utilities/Globals.h index efbdc29d..590b9354 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -294,6 +294,7 @@ struct global_settings { std::string exec_on_exit; std::string prepend_scn; + bool gfx_postfx_ssao_enabled {false}; struct extraviewport_config { std::string monitor;