<@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; // the distance to the near clip plane uniform float near; // scale factor for depth: (far - near) / far uniform float depthScale; // offset for depth texture coordinates uniform vec2 depthTexCoordOffset; // scale for depth texture coordinates uniform vec2 depthTexCoordScale; // the resolution of the occlusion buffer // and its inverse uniform vec2 renderTargetRes; uniform vec2 renderTargetResInv; const float PI = 3.14159265; const float AOStrength = 1.9; // TODO: R (radius) should be exposed as a uniform parameter const float R = 0.01; const float R2 = 0.01*0.01; const float NegInvR2 = - 1.0 / (0.01*0.01); // 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; /** * Gets the normal in view space from a normal texture. * uv: the uv texture coordinates to look up in the texture at. */ vec3 GetViewNormalFromTexture(vec2 uv) { // convert [0,1] -> [-1,1], note: since we're normalizing // we don't need to do v*2 - 1.0, we can just do a v-0.5 return normalize(texture(normalTexture, uv).xyz - 0.5); } /** * Gets the linearized depth in view space. * d: the depth value [0-1], usually from a depth texture to convert. */ float ViewSpaceZFromDepth(float d){ return near / (d * depthScale - 1.0); } /** * Converts a uv coordinate and depth value into a 3D view space coordinate. * uv: the uv coordinates to convert * z: the view space depth of the uv coordinate. */ vec3 UVToViewSpace(vec2 uv, float z){ return vec3((depthTexCoordOffset + varTexcoord * depthTexCoordScale) * z, z); } /** * Converts a uv coordinate into a 3D view space coordinate. * The depth of the uv coord is determined from the depth texture. * uv: the uv coordinates to convert */ vec3 GetViewPos(vec2 uv) { float z = ViewSpaceZFromDepth(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 * renderTargetRes) * renderTargetResInv; } 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 * renderTargetResInv; } float getRandom(vec2 uv) { return fract(sin(dot(uv.xy ,vec2(12.9898,78.233))) * 43758.5453); } void main(void) { mat4 projMatrix = getTransformCamera()._projection; float numDirections = NumDirections; vec3 P, Pr, Pl, Pt, Pb; P = GetViewPos(varTexcoord); // Sample neighboring pixels Pr = GetViewPos(varTexcoord + vec2( renderTargetResInv.x, 0)); Pl = GetViewPos(varTexcoord + vec2(-renderTargetResInv.x, 0)); Pt = GetViewPos(varTexcoord + vec2( 0, renderTargetResInv.y)); Pb = GetViewPos(varTexcoord + vec2( 0,-renderTargetResInv.y)); // Calculate tangent basis vectors using the minimum difference vec3 dPdu = MinDiff(P, Pr, Pl); vec3 dPdv = MinDiff(P, Pt, Pb) * (renderTargetRes.y * renderTargetResInv.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 float w = P.z * projMatrix[2][3] + projMatrix[3][3]; vec2 rayRadiusUV = (0.5 * R * vec2(projMatrix[0][0], projMatrix[1][1]) / w); // [-1,1] -> [0,1] uv float rayRadiusPix = rayRadiusUV.x * renderTargetRes.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); }