1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Implemented a very simple SSAO in GLES3.

This commit is contained in:
jon1solution
2025-08-08 14:37:39 -07:00
parent ab6c6eece8
commit 31ee691fbf
9 changed files with 271 additions and 7 deletions

View File

@@ -10,6 +10,11 @@ USE_LUMINANCE_MULTIPLIER = false
USE_BCS = false
USE_COLOR_CORRECTION = false
USE_1D_LUT = false
USE_SSAO_ABYSS = false
USE_SSAO_LOW = false
USE_SSAO_MED = false
USE_SSAO_HIGH = false
USE_SSAO_MEGA = false
#[vertex]
layout(location = 0) in vec2 vertex_attrib;
@@ -86,6 +91,29 @@ vec3 apply_color_correction(vec3 color) {
#endif // USE_1D_LUT
#endif // USE_COLOR_CORRECTION
#if defined(USE_SSAO_ABYSS) || defined(USE_SSAO_LOW) || defined(USE_SSAO_MED) || defined(USE_SSAO_HIGH) || defined(USE_SSAO_MEGA)
#define USE_SOME_SSAO
uniform float ssao_intensity;
uniform float ssao_radius_frac;
uniform vec2 ssao_prn_UV;
#ifdef USE_MULTIVIEW
// VR will have 2 depth buffers.
uniform sampler2DArray depth_buffer_array; // texunit:3
#else
uniform sampler2D depth_buffer; // texunit:3
#endif
#if defined(USE_SSAO_ABYSS)
// Use the tiny 2-sample version.
#include "../s4ao_micro_inc.glsl"
#elif defined(USE_SSAO_HIGH) || defined(USE_SSAO_MEGA)
// Use the rings version for the higher qualities.
#include "../s4ao_mega_inc.glsl"
#else
// Use the more generic NxN grid version.
#include "../s4ao_inc.glsl"
#endif
#endif
in vec2 uv_interp;
layout(location = 0) out vec4 frag_color;
@@ -131,6 +159,11 @@ void main() {
color.rgb = srgb_to_linear(color.rgb);
#if defined(USE_SOME_SSAO)
// Putting SSAO after the conversion to linear color, though it might be better before the glow.
color.rgb *= s4ao(uv_interp); // The USE_SSAO_X controls the number of samples.
#endif
color.rgb = apply_tonemapping(color.rgb, white);
#ifdef USE_BCS

View File

@@ -0,0 +1,63 @@
// S4AO (Stupid Simple Screen Space Ambient Occlusion) - Jonathan Dummer (O1S)
// The sample_width should be even, else the midpoint is at UV.
// Takes sample_width^2 samples in a grid, with the corners notched.
#if defined(USE_SSAO_LOW)
const int sample_width = 2;
#elif defined(USE_SSAO_HIGH)
const int sample_width = 6;
#else
const int sample_width = 4;
#endif
const int notch_01 = int(sample_width > 3); // Set to 1 to skip the corner samples, 0 to include them.
const float sample_mid = (float(sample_width) - 1.0) * 0.50001; // Can't be exactly 0.5 in case sample_width is odd.
#if defined(USE_SSAO_LOW)
const float inv_half_width = 1.0 / sample_mid; // The 2x2 sampling looks wider as all samples are at radius.
#else
const float inv_half_width = 1.7 / sample_mid; // Bake in the 1.7 scale for the random rotation.
#endif
const float average_samples = 1.0 / float(sample_width * sample_width - 4 * notch_01); // 1 / number_of_samples
const float ssao_falloff_frac = 0.25;
// Perform the SSAO.
float s4ao(vec2 UV) {
#ifdef USE_MULTIVIEW
float depth = texture(depth_buffer_array, vec3(UV, view)).r;
#else
float depth = texture(depth_buffer, UV).r;
#endif
float radius = max(1e-4f, depth * ssao_radius_frac);
float inv_falloff = 1.0f / max(1e-4f, depth * ssao_falloff_frac);
// Random 2D rotation per pixel (+/-45 deg, with 0 having a lower probability).
// The random cosine vector is vec2( 0.5, -0.5 to +0.5 ) and *1.7 makes the average length ~ 1.
vec2 rcos = (inv_half_width * radius) * vec2(0.5f, fract(dot(UV, ssao_prn_UV)) - 0.5f);
vec2 rsin = rcos.yx * vec2(-1, 1); // Perpendicular to the random cosine vector.
// Grab the samples and determine the occlusion.
float occlusion = 0.0f;
vec2 base_duv = -sample_mid * rsin;
for (int j = sample_width; --j >= 0;) {
#if defined(USE_SSAO_LOW)
// Low quality uses 2x2 samples, no notching.
vec2 duv = -sample_mid * rcos + base_duv;
for (int i = sample_width; --i >= 0;) {
#else
// Will uses 4x4 or 6x6 samples, with the corners notched out.
int o = /*notch_01 &*/ int((j <= 0) || (j >= (sample_width - 1))); // Notch corners of the grid.
vec2 duv = (float(o) - sample_mid) * rcos + base_duv;
for (int i = sample_width - o - o; --i >= 0;) {
#endif
#ifdef USE_MULTIVIEW
float dz = texture(depth_buffer_array, vec3(UV + duv, view)).r - depth;
#else
float dz = texture(depth_buffer, UV + duv).r - depth;
#endif
float validity = smoothstep(1.0f, 0.0f, dz * inv_falloff);
occlusion += normalize(vec3(duv, dz)).z * validity; // How 'directly overhead' is it?
duv += rcos; // March along the rcos direction with i.
}
base_duv += rsin; // March along the rsin direction with j.
}
// Adjust the occlusion for intensity, and # samples.
occlusion *= ssao_intensity * average_samples;
occlusion = clamp(1.0f - occlusion, 0.0f, 1.0f);
return occlusion * occlusion;
}

View File

@@ -0,0 +1,50 @@
// S4AO (Stupid Simple Screen Space Ambient Occlusion) - Jonathan Dummer (O1S)
// The mega version uses N concentric rings of samples.
#if defined(USE_SSAO_MEGA)
const int rings = 4; // Start with the outer ring.
const int samps[] = int[](24, 18, 12, 6); // ( 9, 6, 3 ) is a minimum, but I want better.
#else
const int rings = 3; // Start with the outer ring.
const int samps[] = int[](15, 10, 5, 1); // ( 9, 6, 3 ) is a minimum, but I want better.
#endif
const float average_samples = 1.0 / float(samps[0] + samps[1] * int(rings > 1) + samps[2] * int(rings > 2) + samps[3] * int(rings > 3));
const float ssao_falloff_frac = 0.25;
// Perform the SSAO.
float s4ao(vec2 UV) {
#ifdef USE_MULTIVIEW
float depth = texture(depth_buffer_array, vec3(UV, view)).r;
#else
float depth = texture(depth_buffer, UV).r;
#endif
float inv_falloff = 1.0f / max(1e-4f, depth * ssao_falloff_frac);
// Random 2D rotation per pixel (0..1 -> parabola approximating a 180 deg arc)
float r01 = fract(dot(UV, ssao_prn_UV));
vec2 rcos = vec2(r01 - 0.5f, 2.0f * (r01 - r01 * r01)) * (2.0f * depth * ssao_radius_frac); // 180 degrees.
vec2 rsin = rcos.yx * vec2(-1, 1); // Perpendicular to the random cosine vector.
// Grab the samples and determine the occlusion.
float occlusion = 0.0f;
float ring_shrink = 0.75f; // Shrink every ring.
for (int r = 0; r < rings; ++r) {
float dt = (6.283185307f) / float(samps[r]);
float t = float(r & 1) * 0.5f * dt;
for (int s = 0; s < samps[r]; ++s) {
vec2 duv = cos(t) * rcos + sin(t) * rsin;
#ifdef USE_MULTIVIEW
float dz = texture(depth_buffer_array, vec3(UV + duv, view)).r - depth;
#else
float dz = texture(depth_buffer, UV + duv).r - depth;
#endif
// How 'directly overhead' is it? Factor in the falloff depth.
occlusion += normalize(vec3(duv, dz)).z * smoothstep(1.0f, 0.0f, dz * inv_falloff);
t += dt;
}
// The next ring will be smaller.
rcos *= ring_shrink;
rsin *= ring_shrink;
}
// Adjust the occlusion for intensity, and # samples.
occlusion *= ssao_intensity * average_samples;
occlusion = 1.0f - clamp(occlusion, 0.0f, 1.0f);
return occlusion * occlusion;
}

View File

@@ -0,0 +1,32 @@
// S4AO (Stupid Simple Screen Space Ambient Occlusion) - Jonathan Dummer (O1S)
// This micro version uses only 3 depth samples, the midpoint and a randomly-rotated, balanced pair.
const mediump float ssao_falloff_frac = 0.25;
// Perform the SSAO.
float s4ao(vec2 UV) {
#ifdef USE_MULTIVIEW
mediump float depth = texture(depth_buffer_array, vec3(UV, view)).r;
#else
mediump float depth = texture(depth_buffer, UV).r;
#endif
mediump float inv_falloff = 1.0f / max(1e-4f, depth * ssao_falloff_frac);
// Random 2D rotation per pixel (0..1 -> parabola approximating a 180 deg arc)
mediump float r01 = fract(dot(UV, ssao_prn_UV));
mediump vec2 duv = vec2(r01 - 0.5f, 2.0f * (r01 - r01 * r01)) * (2.0f * depth * ssao_radius_frac); // 180 degrees.
// Grab the samples and determine the occlusion.
mediump float occlusion = 0.0f;
for (int s = 0; s < 2; ++s) {
#ifdef USE_MULTIVIEW
mediump float dz = texture(depth_buffer_array, vec3(UV + duv, view)).r - depth;
#else
mediump float dz = texture(depth_buffer, UV + duv).r - depth;
#endif
// How 'directly overhead' is it? Factor in the falloff depth.
occlusion += normalize(vec3(duv, dz)).z * mix(1.0f, 0.0f, dz * inv_falloff);
// Mirror the next sample.
duv = -duv;
}
// Adjust the occlusion for intensity, and # samples.
occlusion = 1.0f - clamp(occlusion * 0.5f * ssao_intensity, 0.0f, 1.0f);
return occlusion * occlusion;
}