141 lines
5 KiB
GLSL
141 lines
5 KiB
GLSL
/**
|
|
* An implementation of GTAO (Ground Truth Ambient Occlusion)
|
|
* Based on 'Practical Real-Time Strategies for Accurate Indirect Occlusion' by
|
|
* Jorge Jimenez et al.
|
|
* https://www.activision.com/cdn/research/Practical_Real_Time_Strategies_for_Accurate_Indirect_Occlusion_NEW%20VERSION_COLOR.pdf
|
|
* https://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pdf
|
|
* Most of the shader is based on Algorithm 1 of the paper.
|
|
*/
|
|
|
|
#version 330 core
|
|
|
|
out float fragColor;
|
|
|
|
in vec2 texCoord;
|
|
|
|
uniform sampler2D gbuffer0_tex;
|
|
uniform sampler2D depth_tex;
|
|
|
|
uniform float world_radius;
|
|
|
|
uniform vec4 fg_Viewport;
|
|
uniform vec2 fg_PixelSize;
|
|
uniform mat4 fg_ProjectionMatrix;
|
|
|
|
const float PI = 3.141592653;
|
|
const float PI_HALF = PI * 0.5;
|
|
const float SLICE_COUNT = 3.0;
|
|
const float DIRECTION_SAMPLE_COUNT = 4.0;
|
|
|
|
vec3 decodeNormal(vec2 f);
|
|
vec3 positionFromDepth(vec2 pos, float depth);
|
|
|
|
void main()
|
|
{
|
|
float depth = textureLod(depth_tex, texCoord, 0.0).r;
|
|
// Ignore the background
|
|
if (depth == 0.0) {
|
|
fragColor = 0.0;
|
|
discard;
|
|
}
|
|
// Slightly push the depth towards the camera to avoid imprecision artifacts
|
|
depth = clamp(depth * 1.00001, 0.0, 1.0);
|
|
|
|
// View space normal
|
|
vec3 normal = decodeNormal(texture(gbuffer0_tex, texCoord).rg);
|
|
// Fragment position in view space
|
|
vec3 pos = positionFromDepth(texCoord, depth);
|
|
// View vector in view space
|
|
vec3 v = normalize(-pos);
|
|
|
|
float noiseDirection = 0.0625 * float(
|
|
((int(gl_FragCoord.x) + int(gl_FragCoord.y) & 3) << 2) +
|
|
(int(gl_FragCoord.x) & 3));
|
|
|
|
float noiseOffset = 0.25 * float(int(gl_FragCoord.x) + int(gl_FragCoord.y) & 3);
|
|
|
|
// Transform the world space hemisphere radius to screen space pixels with
|
|
// the following formula:
|
|
// radius * 1 / [ tan(fovy / 2) * z_distance ] * (screen_size.y / 2)
|
|
// In our case, the (1,1) element of the projection matrix contains
|
|
// 1 / tan(fovy / 2), so we can use that directly.
|
|
// z_distance is the distance from the camera to the fragment, which is
|
|
// just the positive z component of the view space fragment position.
|
|
float radiusPixels = world_radius * (fg_ProjectionMatrix[1][1] / abs(pos.z))
|
|
* fg_Viewport.w * 0.5;
|
|
|
|
float visibility = 0.0;
|
|
|
|
for (float i = 0.0; i < SLICE_COUNT; ++i) {
|
|
float phi = ((i + noiseDirection) / SLICE_COUNT) * PI;
|
|
float cosPhi = cos(phi);
|
|
float sinPhi = sin(phi);
|
|
vec2 omega = vec2(cosPhi, sinPhi);
|
|
|
|
vec3 dir = vec3(omega, 0.0);
|
|
vec3 orthoDirection = dir - dot(dir, v) * v;
|
|
vec3 axis = normalize(cross(dir, v));
|
|
vec3 projNormal = normal - axis * dot(normal, axis);
|
|
float projNormalLength = max(1e-5, length(projNormal));
|
|
|
|
float sgnN = sign(dot(orthoDirection, projNormal));
|
|
float cosN = clamp(dot(projNormal, v) / projNormalLength, 0.0, 1.0);
|
|
float n = sgnN * acos(cosN);
|
|
|
|
float hcos1 = -1.0, hcos2 = -1.0;
|
|
|
|
for (float j = 0.0; j < DIRECTION_SAMPLE_COUNT; ++j) {
|
|
float s = (j + noiseOffset) / DIRECTION_SAMPLE_COUNT;
|
|
s += 1.2 / radiusPixels;
|
|
vec2 sOffset = s * radiusPixels * omega;
|
|
sOffset = round(sOffset) * fg_PixelSize;
|
|
|
|
vec2 sTexCoord1 = texCoord - sOffset;
|
|
float sDepth1 = textureLod(depth_tex, sTexCoord1, 0.0).r;
|
|
if (sDepth1 == 0.0) {
|
|
// Skip background
|
|
continue;
|
|
}
|
|
vec3 sPos1 = positionFromDepth(sTexCoord1, sDepth1);
|
|
|
|
vec2 sTexCoord2 = texCoord + sOffset;
|
|
float sDepth2 = textureLod(depth_tex, sTexCoord2, 0.0).r;
|
|
if (sDepth2 == 0.0) {
|
|
// Skip background
|
|
continue;
|
|
}
|
|
vec3 sPos2 = positionFromDepth(sTexCoord2, sDepth2);
|
|
|
|
vec3 sHorizon1 = sPos1 - pos;
|
|
vec3 sHorizon2 = sPos2 - pos;
|
|
float sHorizonLength1 = length(sHorizon1);
|
|
float sHorizonLength2 = length(sHorizon2);
|
|
|
|
float shcos1 = dot(sHorizon1 / sHorizonLength1, v);
|
|
float shcos2 = dot(sHorizon2 / sHorizonLength2, v);
|
|
|
|
// Section 4.3: Bounding the sampling area
|
|
// Attenuate samples that are further away
|
|
float weight1 = clamp((1.0 - sHorizonLength1 / world_radius) * 2.0, 0.0, 1.0);
|
|
float weight2 = clamp((1.0 - sHorizonLength2 / world_radius) * 2.0, 0.0, 1.0);
|
|
shcos1 = mix(-1.0, shcos1, weight1);
|
|
shcos2 = mix(-1.0, shcos2, weight2);
|
|
|
|
hcos1 = max(hcos1, shcos1);
|
|
hcos2 = max(hcos2, shcos2);
|
|
}
|
|
|
|
float h1 = n + max(-acos(hcos1) - n, -PI_HALF);
|
|
float h2 = n + min( acos(hcos2) - n, PI_HALF);
|
|
|
|
float sinN = sin(n);
|
|
float h1_2 = 2.0 * h1;
|
|
float h2_2 = 2.0 * h2;
|
|
float vd = 0.25 * ((cosN + h1_2 * sinN - cos(h1_2 - n)) +
|
|
(cosN + h2_2 * sinN - cos(h2_2 - n)));
|
|
visibility += projNormalLength * vd;
|
|
}
|
|
visibility /= float(SLICE_COUNT);
|
|
|
|
fragColor = clamp(visibility, 0.0, 1.0);
|
|
}
|