<@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> // // ambient_occlusion.frag // fragment shader // // Created by Niraj Venkat on 7/15/15. // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // <@include DeferredBufferWrite.slh@> <@include gpu/Transform.slh@> <$declareStandardTransform()$> // Based on NVidia HBAO implementation in D3D11 // http://www.nvidia.co.uk/object/siggraph-2008-HBAO.html in vec2 varTexcoord; uniform sampler2D depthTexture; uniform sampler2D normalTexture; uniform float g_scale; uniform float g_bias; uniform float g_sample_rad; uniform float g_intensity; uniform float bufferWidth; uniform float bufferHeight; const float PI = 3.14159265; const vec2 FocalLen = vec2(1.0, 1.0); const vec2 LinMAD = vec2(0.1-10.0, 0.1+10.0) / (2.0*0.1*10.0); const vec2 AORes = vec2(1024.0, 768.0); const vec2 InvAORes = vec2(1.0/1024.0, 1.0/768.0); const vec2 NoiseScale = vec2(1024.0, 768.0) / 4.0; const float AOStrength = 1.9; const float R = 0.3; const float R2 = 0.3*0.3; const float NegInvR2 = - 1.0 / (0.3*0.3); // can't use tan to initialize a const value const float TanBias = 0.57735027; // tan(30.0 * PI / 180.0); const float MaxRadiusPixels = 50.0; const int NumDirections = 6; const int NumSamples = 4; out vec4 outFragColor; float ViewSpaceZFromDepth(float d){ // [0,1] -> [-1,1] clip space d = d * 2.0 - 1.0; // Get view space Z return -1.0 / (LinMAD.x * d + LinMAD.y); } vec3 UVToViewSpace(vec2 uv, float z){ //uv = UVToViewA * uv + UVToViewB; return vec3(uv * z, z); } vec3 GetViewPos(vec2 uv){ float z = ViewSpaceZFromDepth(texture(depthTexture, uv).r); return UVToViewSpace(uv, z); } vec3 GetViewPosPoint(ivec2 uv){ vec2 coord = vec2(gl_FragCoord.xy) + uv; //float z = texelFetch(texture0, coord, 0).r; float z = texture(depthTexture, uv).r; return UVToViewSpace(uv, z); } float TanToSin(float x){ return x * inversesqrt(x*x + 1.0); } float InvLength(vec2 V){ return inversesqrt(dot(V,V)); } float Tangent(vec3 V){ return V.z * InvLength(V.xy); } float BiasedTangent(vec3 V){ return V.z * InvLength(V.xy) + TanBias; } float Tangent(vec3 P, vec3 S){ return -(P.z - S.z) * InvLength(S.xy - P.xy); } float Length2(vec3 V){ return dot(V,V); } vec3 MinDiff(vec3 P, vec3 Pr, vec3 Pl){ vec3 V1 = Pr - P; vec3 V2 = P - Pl; return (Length2(V1) < Length2(V2)) ? V1 : V2; } vec2 SnapUVOffset(vec2 uv){ return round(uv * AORes) * InvAORes; } float Falloff(float d2){ return d2 * NegInvR2 + 1.0f; } float HorizonOcclusion( vec2 deltaUV, vec3 P, vec3 dPdu, vec3 dPdv, float randstep, float numSamples){ float ao = 0; // Offset the first coord with some noise vec2 uv = varTexcoord + SnapUVOffset(randstep*deltaUV); deltaUV = SnapUVOffset( deltaUV ); // Calculate the tangent vector vec3 T = deltaUV.x * dPdu + deltaUV.y * dPdv; // Get the angle of the tangent vector from the viewspace axis float tanH = BiasedTangent(T); float sinH = TanToSin(tanH); float tanS; float d2; vec3 S; // Sample to find the maximum angle for(float s = 1; s <= numSamples; ++s){ uv += deltaUV; S = GetViewPos(uv); tanS = Tangent(P, S); d2 = Length2(S - P); // Is the sample within the radius and the angle greater? if(d2 < R2 && tanS > tanH) { float sinS = TanToSin(tanS); // Apply falloff based on the distance ao += Falloff(d2) * (sinS - sinH); tanH = tanS; sinH = sinS; } } return ao; } vec2 RotateDirections(vec2 Dir, vec2 CosSin){ return vec2(Dir.x*CosSin.x - Dir.y*CosSin.y, Dir.x*CosSin.y + Dir.y*CosSin.x); } void ComputeSteps(inout vec2 stepSizeUv, inout float numSteps, float rayRadiusPix, float rand){ // Avoid oversampling if numSteps is greater than the kernel radius in pixels numSteps = min(NumSamples, rayRadiusPix); // Divide by Ns+1 so that the farthest samples are not fully attenuated float stepSizePix = rayRadiusPix / (numSteps + 1); // Clamp numSteps if it is greater than the max kernel footprint float maxNumSteps = MaxRadiusPixels / stepSizePix; if (maxNumSteps < numSteps) { // Use dithering to avoid AO discontinuities numSteps = floor(maxNumSteps + rand); numSteps = max(numSteps, 1); stepSizePix = MaxRadiusPixels / numSteps; } // Step size in uv space stepSizeUv = stepSizePix * InvAORes; } float getRandom(vec2 uv){ return fract(sin(dot(uv.xy ,vec2(12.9898,78.233))) * 43758.5453); } void main(void){ float numDirections = NumDirections; vec3 P, Pr, Pl, Pt, Pb; P = GetViewPos(varTexcoord); // Sample neighboring pixels Pr = GetViewPos(varTexcoord + vec2( InvAORes.x, 0)); Pl = GetViewPos(varTexcoord + vec2(-InvAORes.x, 0)); Pt = GetViewPos(varTexcoord + vec2( 0, InvAORes.y)); Pb = GetViewPos(varTexcoord + vec2( 0,-InvAORes.y)); // Calculate tangent basis vectors using the minimum difference vec3 dPdu = MinDiff(P, Pr, Pl); vec3 dPdv = MinDiff(P, Pt, Pb) * (AORes.y * InvAORes.x); // Get the random samples from the noise function vec3 random = vec3(getRandom(varTexcoord.xy), getRandom(varTexcoord.yx), getRandom(varTexcoord.xx)); // Calculate the projected size of the hemisphere vec2 rayRadiusUV = 0.5 * R * FocalLen / -P.z; float rayRadiusPix = rayRadiusUV.x * AORes.x; float ao = 1.0; // Make sure the radius of the evaluated hemisphere is more than a pixel if(rayRadiusPix > 1.0){ ao = 0.0; float numSteps; vec2 stepSizeUV; // Compute the number of steps ComputeSteps(stepSizeUV, numSteps, rayRadiusPix, random.z); float alpha = 2.0 * PI / numDirections; // Calculate the horizon occlusion of each direction for(float d = 0; d < numDirections; ++d){ float theta = alpha * d; // Apply noise to the direction vec2 dir = RotateDirections(vec2(cos(theta), sin(theta)), random.xy); vec2 deltaUV = dir * stepSizeUV; // Sample the pixels along the direction ao += HorizonOcclusion( deltaUV, P, dPdu, dPdv, random.z, numSteps); } // Average the results and produce the final AO ao = 1.0 - ao / numDirections * AOStrength; } outFragColor = vec4(vec3(ao), 1.0); }