better SSAO

This commit is contained in:
RF47
2026-05-17 17:28:50 -03:00
parent 74dc760ab0
commit 7159bb43cd
2 changed files with 157 additions and 64 deletions

View File

@@ -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);
}

View File

@@ -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);
}