From a24f9fae3ff3b81423ad41638c61ed232bf1eadb Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 16:56:38 +0200 Subject: [PATCH] feat(render): ground truth ambient occlusion darkens creases, recesses and the space under desks and frames, computed at half resolution from the reversed depth buffer with a depth-aware blur and multiplied into the image before motion blur, so it works with the default settings; thin parts in front of a surface, such as a steering wheel, shade it only from close by, and gfx.postfx.gtao.enabled, radius, intensity, directions and steps set it from the ini --- gl/postfx.cpp | 5 +- gl/postfx.h | 2 +- rendering/opengl33renderer.cpp | 69 ++++++++++++++++++++-- rendering/opengl33renderer.h | 9 +++ shaders/postfx_gtao.frag | 102 +++++++++++++++++++++++++++++++++ shaders/postfx_gtao_apply.frag | 13 +++++ shaders/postfx_gtao_blur.frag | 45 +++++++++++++++ utilities/Globals.cpp | 30 ++++++++++ utilities/Globals.h | 5 ++ 9 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 shaders/postfx_gtao.frag create mode 100644 shaders/postfx_gtao_apply.frag create mode 100644 shaders/postfx_gtao_blur.frag diff --git a/gl/postfx.cpp b/gl/postfx.cpp index 6587475a..be54a0fc 100644 --- a/gl/postfx.cpp +++ b/gl/postfx.cpp @@ -26,11 +26,12 @@ void gl::postfx::apply(opengl_texture &src, framebuffer *dst) apply({&src}, dst); } -void gl::postfx::apply(std::vector src, framebuffer *dst) +void gl::postfx::apply(std::vector src, framebuffer *dst, bool clear) { if (dst) { - dst->clear(GL_COLOR_BUFFER_BIT); + if (clear) + dst->clear(GL_COLOR_BUFFER_BIT); dst->bind(); } else diff --git a/gl/postfx.h b/gl/postfx.h index 9ea4b719..3211e649 100644 --- a/gl/postfx.h +++ b/gl/postfx.h @@ -20,6 +20,6 @@ namespace gl void attach(); void apply(opengl_texture &src, framebuffer *dst); - void apply(std::vector src, framebuffer *dst); + void apply(std::vector src, framebuffer *dst, bool clear = true); }; } diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 9eaf615e..35391ab6 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -214,6 +214,13 @@ bool opengl33_renderer::Init(GLFWwindow *Window) m_pfx_ssao_blur = std::make_unique("ssao_blur"); m_pfx_ssao_apply = std::make_unique("ssao_apply"); + if (Global.gfx_postfx_gtao_enabled) + { + m_pfx_gtao = std::make_unique("gtao"); + m_pfx_gtao_blur = std::make_unique("gtao_blur"); + m_pfx_gtao_apply = std::make_unique("gtao_apply"); + } + // Generate hemisphere kernel (z > 0 = toward surface normal) std::uniform_real_distribution rnd(0.0f, 1.0f); std::default_random_engine gen(42); // NOSONAR fixed seed on purpose, SSAO kernel has to be identical every run @@ -503,7 +510,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 || Global.gfx_postfx_ssao_enabled) + if (Global.gfx_postfx_chromaticaberration_enabled || Global.gfx_postfx_motionblur_enabled || Global.gfx_postfx_ssao_enabled || Global.gfx_postfx_gtao_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); @@ -566,6 +573,24 @@ bool opengl33_renderer::init_viewport(viewport_config &vp) if (!vp.ssao_blur_fb->is_complete()) { ErrorLog("ssao blur framebuffer setup failed"); return false; } } + if (Global.gfx_postfx_gtao_enabled) + { + int gtao_w = std::max(1, vp.width / 2); + int gtao_h = std::max(1, vp.height / 2); + + vp.gtao_tex = std::make_unique(); + vp.gtao_tex->alloc_rendertarget(GL_R8, GL_RED, gtao_w, gtao_h, 1, 1, GL_CLAMP_TO_EDGE); + vp.gtao_fb = std::make_unique(); + vp.gtao_fb->attach(*vp.gtao_tex, GL_COLOR_ATTACHMENT0); + if (!vp.gtao_fb->is_complete()) { ErrorLog("gtao framebuffer setup failed"); return false; } + + vp.gtao_blur_tex = std::make_unique(); + vp.gtao_blur_tex->alloc_rendertarget(GL_R8, GL_RED, gtao_w, gtao_h, 1, 1, GL_CLAMP_TO_EDGE); + vp.gtao_blur_fb = std::make_unique(); + vp.gtao_blur_fb->attach(*vp.gtao_blur_tex, GL_COLOR_ATTACHMENT0); + if (!vp.gtao_blur_fb->is_complete()) { ErrorLog("gtao blur framebuffer setup failed"); return false; } + } + if (!vp.main2_fb->is_complete()) return false; } @@ -1042,12 +1067,8 @@ if (!Global.gfx_skippipeline) 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_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT1); vp.msaa_fb->blit_to(vp.main_fb.get(), vp.width, vp.height, GL_DEPTH_BUFFER_BIT, GL_DEPTH_ATTACHMENT); - - model_ubs.param[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter); - 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) + else if (Global.gfx_postfx_ssao_enabled || Global.gfx_postfx_gtao_enabled) { // resolve color+depth so SSAO can sample the depth texture vp.main_fb->clear(GL_COLOR_BUFFER_BIT); @@ -1061,6 +1082,42 @@ if (!Global.gfx_skippipeline) vp.msaa_fb->blit_to(vp.main2_fb.get(), vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); } + if (Global.gfx_postfx_gtao_enabled) + { + int gtao_w = std::max(1, vp.width / 2); + int gtao_h = std::max(1, vp.height / 2); + bool const blend { glIsEnabled(GL_BLEND) == GL_TRUE }; + + glDisable(GL_BLEND); + model_ubs.param[0] = glm::vec4(Global.gfx_postfx_gtao_radius, Global.gfx_postfx_gtao_intensity, static_cast(Global.gfx_postfx_gtao_directions), static_cast(Global.gfx_postfx_gtao_steps)); + model_ubs.param[1] = glm::vec4((GLAD_GL_ARB_clip_control || GLAD_GL_EXT_clip_control) ? 1.f : 0.f, 1000.f, 0.f, 0.f); + model_ubo->update(model_ubs); + glViewport(0, 0, gtao_w, gtao_h); + m_pfx_gtao->apply({ vp.main_texd.get() }, vp.gtao_fb.get()); + m_pfx_gtao_blur->apply({ vp.gtao_tex.get(), vp.main_texd.get() }, vp.gtao_blur_fb.get()); + + glViewport(0, 0, vp.width, vp.height); + vp.main_fb->setup_drawing(1); + glEnable(GL_BLEND); + glBlendFunc(GL_ZERO, GL_SRC_COLOR); + m_pfx_gtao_apply->apply({ vp.gtao_blur_tex.get() }, vp.main_fb.get(), false); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if (!blend) + glDisable(GL_BLEND); + } + + if (Global.gfx_postfx_motionblur_enabled) + { + model_ubs.param[0].x = m_framerate / (1.0 / Global.gfx_postfx_motionblur_shutter); + 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_gtao_enabled && !Global.gfx_postfx_ssao_enabled) + { + vp.main2_fb->clear(GL_COLOR_BUFFER_BIT); + vp.main_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) { diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index 64574420..c8aa3b4f 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -251,6 +251,11 @@ class opengl33_renderer : public gfx_renderer { std::unique_ptr ssao_fb; std::unique_ptr ssao_blur_fb; + std::unique_ptr gtao_tex; + std::unique_ptr gtao_blur_tex; + std::unique_ptr gtao_fb; + std::unique_ptr gtao_blur_fb; + // LDR backbuffer for offscreen rendering std::unique_ptr backbuffer_fb; std::unique_ptr backbuffer_tex; @@ -513,6 +518,10 @@ class opengl33_renderer : public gfx_renderer { std::unique_ptr m_ssao_noise_tex; glm::vec3 m_ssao_kernel[32]; + std::unique_ptr m_pfx_gtao; + std::unique_ptr m_pfx_gtao_blur; + std::unique_ptr m_pfx_gtao_apply; + std::unique_ptr m_shadow_shader; std::unique_ptr m_alpha_shadow_shader; diff --git a/shaders/postfx_gtao.frag b/shaders/postfx_gtao.frag new file mode 100644 index 00000000..f006c5d9 --- /dev/null +++ b/shaders/postfx_gtao.frag @@ -0,0 +1,102 @@ +in vec2 f_coords; + +layout(location = 0) out vec4 out_color; + +#texture (depth_tex, 0, R) +uniform sampler2D depth_tex; + +#include + +const float PI = 3.14159265; +const float HALF_PI = 1.57079633; + +vec3 view_position(ivec2 texel) +{ + vec2 size = vec2(textureSize(depth_tex, 0)); + texel = clamp(texel, ivec2(0), ivec2(size) - 1); + float depth = max(texelFetch(depth_tex, texel, 0).r, 1e-6); + float ndcz = (param[1].x > 0.5 ? depth : depth * 2.0 - 1.0); + float z = -projection[3][2] / (ndcz + projection[2][2]); + vec2 ndc = (vec2(texel) + 0.5) / size * 2.0 - 1.0; + return vec3((-ndc.x * z - projection[2][0] * z) / projection[0][0], + (-ndc.y * z - projection[2][1] * z) / projection[1][1], + z); +} + +void main() +{ + vec2 size = vec2(textureSize(depth_tex, 0)); + ivec2 center = ivec2(f_coords * size); + if (texelFetch(depth_tex, center, 0).r <= 0.0) { + out_color = vec4(1.0); + return; + } + + vec3 P = view_position(center); + vec3 V = normalize(-P); + vec3 right = view_position(center + ivec2(1, 0)) - P; + vec3 left = P - view_position(center - ivec2(1, 0)); + vec3 up = view_position(center + ivec2(0, 1)) - P; + vec3 down = P - view_position(center - ivec2(0, 1)); + vec3 N = normalize(cross(abs(right.z) < abs(left.z) ? right : left, abs(up.z) < abs(down.z) ? up : down)); + if (dot(N, V) < 0.0) + N = -N; + + float radius = param[0].x; + float pixels = radius * projection[1][1] * 0.5 * size.y / -P.z; + if (pixels < 2.0) { + out_color = vec4(1.0); + return; + } + pixels = min(pixels, size.y * 0.25); + + int directions = max(int(param[0].z), 1); + int steps = max(int(param[0].w), 1); + float falloffrange = 0.6 * radius; + float falloffmul = -1.0 / falloffrange; + float falloffadd = (radius - falloffrange) / falloffrange + 1.0; + float noise = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))); + float jitter = fract(noise * 1.61803399); + + float visibility = 0.0; + for (int d = 0; d < directions; ++d) + { + float phi = (float(d) + noise) * PI / float(directions); + vec2 omega = vec2(cos(phi), sin(phi)); + vec3 direction = vec3(omega, 0.0); + vec3 ortho = direction - dot(direction, V) * V; + vec3 axis = normalize(cross(ortho, V)); + vec3 projected = N - axis * dot(N, axis); + float projectedlength = length(projected); + float cosn = clamp(dot(projected, V) / max(projectedlength, 1e-4), 0.0, 1.0); + float n = sign(dot(ortho, projected)) * acos(cosn); + float lowcos0 = cos(n + HALF_PI); + float lowcos1 = cos(n - HALF_PI); + float horizoncos0 = lowcos0; + float horizoncos1 = lowcos1; + for (int s = 0; s < steps; ++s) + { + float t = (float(s) + jitter) / float(steps); + t = t * t; + vec2 offset = omega * max(t * pixels, 1.5); + vec3 delta0 = view_position(center + ivec2(round(offset))) - P; + vec3 delta1 = view_position(center - ivec2(round(offset))) - P; + float length0 = length(delta0); + float length1 = length(delta1); + float weight0 = clamp(length(vec3(delta0.xy, delta0.z * (delta0.z > 0.0 ? 5.0 : 1.0))) * falloffmul + falloffadd, 0.0, 1.0); + float weight1 = clamp(length(vec3(delta1.xy, delta1.z * (delta1.z > 0.0 ? 5.0 : 1.0))) * falloffmul + falloffadd, 0.0, 1.0); + horizoncos0 = max(horizoncos0, mix(lowcos0, dot(delta0, V) / max(length0, 1e-4), weight0)); + horizoncos1 = max(horizoncos1, mix(lowcos1, dot(delta1, V) / max(length1, 1e-4), weight1)); + } + float h0 = -acos(clamp(horizoncos1, -1.0, 1.0)); + float h1 = acos(clamp(horizoncos0, -1.0, 1.0)); + h0 = n + clamp(h0 - n, -HALF_PI, HALF_PI); + h1 = n + clamp(h1 - n, -HALF_PI, HALF_PI); + float arc0 = (cosn + 2.0 * h0 * sin(n) - cos(2.0 * h0 - n)) * 0.25; + float arc1 = (cosn + 2.0 * h1 * sin(n) - cos(2.0 * h1 - n)) * 0.25; + visibility += projectedlength * (arc0 + arc1); + } + visibility = clamp(visibility / float(directions), 0.0, 1.0); + float fade = smoothstep(param[1].y * 0.75, param[1].y, -P.z); + out_color = vec4(mix(visibility, 1.0, fade)); +} diff --git a/shaders/postfx_gtao_apply.frag b/shaders/postfx_gtao_apply.frag new file mode 100644 index 00000000..c8fa37a9 --- /dev/null +++ b/shaders/postfx_gtao_apply.frag @@ -0,0 +1,13 @@ +in vec2 f_coords; + +layout(location = 0) out vec4 out_color; + +#texture (ao_tex, 0, R) +uniform sampler2D ao_tex; + +#include + +void main() +{ + out_color = vec4(vec3(pow(clamp(texture(ao_tex, f_coords).r, 0.0, 1.0), param[0].y)), 1.0); +} diff --git a/shaders/postfx_gtao_blur.frag b/shaders/postfx_gtao_blur.frag new file mode 100644 index 00000000..ba3b65c7 --- /dev/null +++ b/shaders/postfx_gtao_blur.frag @@ -0,0 +1,45 @@ +in vec2 f_coords; + +layout(location = 0) out vec4 out_color; + +#texture (ao_tex, 0, R) +uniform sampler2D ao_tex; + +#texture (depth_tex, 1, R) +uniform sampler2D depth_tex; + +#include + +float view_distance(vec2 uv) +{ + ivec2 size = textureSize(depth_tex, 0); + float depth = texelFetch(depth_tex, clamp(ivec2(uv * vec2(size)), ivec2(0), size - 1), 0).r; + if (depth <= 0.0) + return 1e6; + float ndcz = (param[1].x > 0.5 ? depth : depth * 2.0 - 1.0); + return projection[3][2] / (ndcz + projection[2][2]); +} + +void main() +{ + vec2 texel = 1.0 / vec2(textureSize(ao_tex, 0)); + float center = view_distance(f_coords); + if (center >= 1e6) { + out_color = vec4(1.0); + return; + } + float sum = 0.0; + float weights = 0.0; + for (int y = -2; y <= 2; ++y) + { + for (int x = -2; x <= 2; ++x) + { + vec2 uv = f_coords + vec2(float(x), float(y)) * texel; + float difference = (view_distance(uv) - center) / (center * 0.02 + 0.01); + float weight = exp(-float(x * x + y * y) / 4.5 - difference * difference); + sum += texture(ao_tex, uv).r * weight; + weights += weight; + } + } + out_color = vec4(sum / max(weights, 1e-4)); +} diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index a18234af..7eef3408 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1442,6 +1442,31 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token Parser.getTokens(1); Parser >> gfx_postfx_chromaticaberration_enabled; } + else if (Token == "gfx.postfx.gtao.enabled") + { + Parser.getTokens(1); + Parser >> gfx_postfx_gtao_enabled; + } + else if (Token == "gfx.postfx.gtao.radius") + { + Parser.getTokens(1); + Parser >> gfx_postfx_gtao_radius; + } + else if (Token == "gfx.postfx.gtao.intensity") + { + Parser.getTokens(1); + Parser >> gfx_postfx_gtao_intensity; + } + else if (Token == "gfx.postfx.gtao.directions") + { + Parser.getTokens(1); + Parser >> gfx_postfx_gtao_directions; + } + else if (Token == "gfx.postfx.gtao.steps") + { + Parser.getTokens(1); + Parser >> gfx_postfx_gtao_steps; + } else if (Token == "gfx.format.color") { Parser.getTokens(1); @@ -1753,6 +1778,11 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.postfx.motionblur.shutter", gfx_postfx_motionblur_shutter ); // TODO: export gfx_postfx_motionblur_format export_as_text( Output, "gfx.postfx.chromaticaberration.enabled", gfx_postfx_chromaticaberration_enabled ); + export_as_text( Output, "gfx.postfx.gtao.enabled", gfx_postfx_gtao_enabled ); + export_as_text( Output, "gfx.postfx.gtao.radius", gfx_postfx_gtao_radius ); + export_as_text( Output, "gfx.postfx.gtao.intensity", gfx_postfx_gtao_intensity ); + export_as_text( Output, "gfx.postfx.gtao.directions", gfx_postfx_gtao_directions ); + export_as_text( Output, "gfx.postfx.gtao.steps", gfx_postfx_gtao_steps ); // TODO: export gfx_format_color // TODO: export gfx_format_depth export_as_text( Output, "gfx.skiprendering", gfx_skiprendering ); diff --git a/utilities/Globals.h b/utilities/Globals.h index caf8af21..de4c702b 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -293,6 +293,11 @@ struct global_settings { GLenum gfx_format_color = GL_RGB16F; GLenum gfx_format_depth = GL_DEPTH_COMPONENT32F; bool gfx_postfx_chromaticaberration_enabled = true; + bool gfx_postfx_gtao_enabled = true; + float gfx_postfx_gtao_radius = 0.35f; + float gfx_postfx_gtao_intensity = 2.0f; + int gfx_postfx_gtao_directions = 2; + int gfx_postfx_gtao_steps = 8; bool gfx_skippipeline = false; bool gfx_extraeffects = true; bool gfx_shadergamma = false;