diff --git a/resources/shaders/110/ssao.fs b/resources/shaders/110/ssao.fs index a40d88ce02..5dfd9549de 100644 --- a/resources/shaders/110/ssao.fs +++ b/resources/shaders/110/ssao.fs @@ -1,46 +1,94 @@ #version 110 -uniform sampler2D color_texture; -uniform sampler2D depth_texture; -uniform vec2 inv_tex_size; -uniform float z_near; -uniform float z_far; +/** + * SSAO (Screen Space Ambient Occlusion) Shader - GLSL 110 version + * Uses depth-based sampling with adaptive radius and weighting + * Compatible with OpenGL 2.1 and older hardware + */ -varying vec2 tex_coord; +uniform sampler2D color_texture; // Original scene color +uniform sampler2D depth_texture; // Depth buffer texture +uniform vec2 inv_tex_size; // 1.0/width, 1.0/height - for UV offset calculation +uniform float z_near; // Near clipping plane distance +uniform float z_far; // Far clipping plane distance +varying vec2 tex_coord; // Texture coordinates from vertex shader + +/** + * Convert linear depth buffer value to world/view space depth + * Uses standard OpenGL perspective projection reverse mapping + */ float linearize_depth(float depth) { - float z = depth * 2.0 - 1.0; + float z = depth * 2.0 - 1.0; // Convert to NDC [-1, 1] range return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near)); } void main() { + // Sample base color at current fragment vec3 base = texture2D(color_texture, tex_coord).rgb; + + // Linearize center pixel depth for accurate world-space comparisons float depth_center = linearize_depth(texture2D(depth_texture, tex_coord).r); - + + // Adaptive sampling radius: larger radius for distant objects (perspective effect) + // Closer objects need smaller radius to capture fine details + float radius = mix(1.5, 4.0, depth_center / z_far); + + // Circular sampling pattern (unit circle) with diagonal weighting + // Offsets normalized to unit circle for uniform directional sampling vec2 offsets[8]; - offsets[0] = vec2(-1.0, 0.0); - offsets[1] = vec2( 1.0, 0.0); - offsets[2] = vec2( 0.0, -1.0); - offsets[3] = vec2( 0.0, 1.0); - offsets[4] = vec2(-1.0, -1.0); - offsets[5] = vec2( 1.0, -1.0); - offsets[6] = vec2(-1.0, 1.0); - offsets[7] = vec2( 1.0, 1.0); - - float occ = 0.0; + offsets[0] = vec2( 1.0, 0.0); // Right + offsets[1] = vec2( 0.707, 0.707); // Top-right diagonal + offsets[2] = vec2( 0.0, 1.0); // Top + offsets[3] = vec2(-0.707, 0.707); // Top-left diagonal + offsets[4] = vec2(-1.0, 0.0); // Left + offsets[5] = vec2(-0.707,-0.707); // Bottom-left diagonal + offsets[6] = vec2( 0.0, -1.0); // Bottom + offsets[7] = vec2( 0.707,-0.707); // Bottom-right diagonal + + float occlusion = 0.0; + int valid_samples = 0; + for (int i = 0; i < 8; ++i) { - vec2 uv = tex_coord + offsets[i] * inv_tex_size * 2.0; + // Apply radius and convert to UV space + vec2 uv = tex_coord + offsets[i] * inv_tex_size * radius; + + // Clamp to texture edges to prevent border artifacts + uv = clamp(uv, vec2(0.001), vec2(0.999)); + + // Sample and linearize neighbor depth float sample_depth = linearize_depth(texture2D(depth_texture, uv).r); - float delta = max(0.0, depth_center - sample_depth); - occ += smoothstep(0.001, 0.03, delta); + + // Calculate depth difference (positive if neighbor is closer to camera) + float depth_diff = max(0.0, depth_center - sample_depth); + + // Adaptive threshold: larger tolerance for distant objects + // smoothstep creates soft occlusion falloff + float threshold = 0.015 * (0.5 + depth_center / z_far); + float contribution = smoothstep(0.001, threshold, depth_diff); + + // Weight diagonal samples less (they're further in screen space) + // Reduces over-occlusion at 45-degree angles + float diagonal_weight = 1.0 - abs(offsets[i].x * offsets[i].y) * 0.5; + + occlusion += contribution * diagonal_weight; + valid_samples++; } - - occ /= 8.0; - // Lighter AO: preserve non-cavity areas closer to original lighting. - float ao = 1.0 - occ * 0.45; - ao = clamp(ao, 0.62, 1.0); - - gl_FragColor = vec4(base * ao, 1.0); -} + + // Average occlusion from all valid samples + if (valid_samples > 0) + occlusion /= float(valid_samples); + + // Apply AO intensity curve + // 0.55 intensity factor - subtle effect that preserves original lighting + float ambient_occlusion = 1.0 - occlusion * 0.55; + ambient_occlusion = clamp(ambient_occlusion, 0.55, 1.0); + + // Gamma-style curve for more natural appearance + ambient_occlusion = pow(ambient_occlusion, 1.1); + + // Final composite: modulate base color with AO factor + gl_FragColor = vec4(base * ambient_occlusion, 1.0); +} \ No newline at end of file diff --git a/resources/shaders/140/ssao.fs b/resources/shaders/140/ssao.fs index 26b721bae6..cf68ab083f 100644 --- a/resources/shaders/140/ssao.fs +++ b/resources/shaders/140/ssao.fs @@ -1,48 +1,93 @@ #version 140 -uniform sampler2D color_texture; -uniform sampler2D depth_texture; -uniform vec2 inv_tex_size; -uniform float z_near; -uniform float z_far; +/** + * SSAO (Screen Space Ambient Occlusion) Shader - GLSL 140 version + * Uses texelFetch for precise pixel access and better performance + * Requires OpenGL 3.1+ / GLSL 1.40 + */ -in vec2 tex_coord; -out vec4 out_color; +uniform sampler2D color_texture; // Original scene color +uniform sampler2D depth_texture; // Depth buffer texture +uniform float z_near; // Near clipping plane distance +uniform float z_far; // Far clipping plane distance +in vec2 tex_coord; // Texture coordinates from vertex shader +out vec4 frag_color; // Final fragment color output + +/** + * Convert linear depth buffer value to world/view space depth + * Same math as 110 version but with modern syntax + */ float linearize_depth(float depth) { - float z = depth * 2.0 - 1.0; + float z = depth * 2.0 - 1.0; // Convert to NDC [-1, 1] range return (2.0 * z_near * z_far) / (z_far + z_near - z * (z_far - z_near)); } void main() { - vec3 base = texture(color_texture, tex_coord).rgb; - float depth_center = linearize_depth(texture(depth_texture, tex_coord).r); - - vec2 offsets[8] = vec2[]( - vec2(-1.0, 0.0), - vec2( 1.0, 0.0), - vec2( 0.0, -1.0), - vec2( 0.0, 1.0), - vec2(-1.0, -1.0), - vec2( 1.0, -1.0), - vec2(-1.0, 1.0), - vec2( 1.0, 1.0) + // Get exact pixel coordinates using gl_FragCoord (pixel-perfect access) + ivec2 pixel = ivec2(gl_FragCoord.xy); + + // Use texelFetch for direct pixel access without texture filtering + // Much faster and more precise than texture2D for depth buffers + float center_depth = linearize_depth(texelFetch(depth_texture, pixel, 0).r); + + // Adaptive radius in pixel space (not UV space) + // int cast ensures exact pixel offsets without floating point errors + int radius = int(mix(2.0, 5.0, center_depth / z_far)); + + // Optimized sampling pattern with more samples (12 vs 8) + // Includes both cardinal directions and diagonals at different distances + const ivec2 offsets[12] = ivec2[]( + ivec2(1, 0), ivec2(-1, 0), ivec2(0, 1), ivec2(0, -1), // Cardinal directions (distance 1) + ivec2(1, 1), ivec2(-1, 1), ivec2(1, -1), ivec2(-1, -1), // Diagonals (distance 1.414) + ivec2(2, 0), ivec2(-2, 0), ivec2(0, 2), ivec2(0, -2) // Far cardinal (distance 2) ); - - float occ = 0.0; - for (int i = 0; i < 8; ++i) { - vec2 uv = tex_coord + offsets[i] * inv_tex_size * 2.0; - float sample_depth = linearize_depth(texture(depth_texture, uv).r); - float delta = max(0.0, depth_center - sample_depth); - occ += smoothstep(0.001, 0.03, delta); + + float occlusion = 0.0; + int valid_samples = 0; + + for (int i = 0; i < 12; i++) { + // Calculate neighbor pixel position with adaptive radius + ivec2 sample_pixel = pixel + offsets[i] * radius; + + // Boundary check to prevent reading outside framebuffer + // Avoids artifacts at screen edges + if (sample_pixel.x < 0 || sample_pixel.y < 0) + continue; + + // Direct pixel fetch - no filtering, exact depth value + float sample_depth = linearize_depth(texelFetch(depth_texture, sample_pixel, 0).r); + + // Depth difference (positive = neighbor is in front) + float depth_diff = max(0.0, center_depth - sample_depth); + + // Adaptive threshold based on distance + // Distant objects need larger threshold due to depth compression + float threshold = 0.02 * (0.5 + center_depth / z_far); + + // Smoothstep for soft occlusion falloff + occlusion += smoothstep(0.001, threshold, depth_diff); + valid_samples++; } - - occ /= 8.0; - // Lighter AO: preserve non-cavity areas closer to original lighting. - float ao = 1.0 - occ * 0.45; - ao = clamp(ao, 0.62, 1.0); - - out_color = vec4(base * ao, 1.0); -} + + // Calculate final AO factor + if (valid_samples > 0) { + // Average occlusion and apply intensity (0.5 = subtle effect) + float ao_factor = 1.0 - (occlusion / float(valid_samples)) * 0.5; + ao_factor = clamp(ao_factor, 0.58, 1.0); + + // Apply power curve for better visual response + ao_factor = pow(ao_factor, 1.15); + occlusion = ao_factor; + } else { + occlusion = 1.0; // No samples available, no occlusion + } + + // Sample color using standard filtering (better for colors) + vec3 color = texture(color_texture, tex_coord).rgb; + + // Apply ambient occlusion to final color + frag_color = vec4(color * occlusion, 1.0); +} \ No newline at end of file