//-----------------------------------------------------------------------------
// Screen Space Ambient Occlusion for TGEA
// Copyright (C) Ubiq Visuals Inc.
// http://www.ubiqvisuals.com/
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// Structures                                                                  
//-----------------------------------------------------------------------------
struct PS_INPUT
{
   float4 Position : POSITION0;
   float4 Texcoord : TEXCOORD0;
};

struct PS_OUTPUT
{
   float4 Color : COLOR0;
};


//-----------------------------------------------------------------------------
// mirror
// Returns the reflection of vector V, in plane P
// - P must be normalized
//-----------------------------------------------------------------------------
float3 mirror( float3 V, float3 P )
{
   return V - 2 * P * dot(P,V);
}


//-----------------------------------------------------------------------------
// Main
//-----------------------------------------------------------------------------
PS_OUTPUT main(PS_INPUT Input,
         uniform sampler2D DepthMap : register(S0),
         uniform sampler2D RandNormMap : register(S1),
         uniform float2 ScreenSize : register(C0),
         uniform float2 RandNormTexSize : register(C1),
         uniform float4 SSAOParams : register(C2),
         uniform float2 ClipPlanes : register(C3) )
{
   PS_OUTPUT Output;
   
   //Lookup the scene depth at this pixel
   half PixelDepth = tex2D( DepthMap, Input.Texcoord.xy ).r;
  
   //Convert range back to meters
   //from (0 - 1) to (0 - far clipping plane)
   half PixelDepthMetres = PixelDepth * ClipPlanes.y;

   //Scaling the sampling area with distance (though physically incorrect)
   //generally looks better. The algorithm "pops out" geometric detail at
   //any distance and doesn't appear strange when very far or very close
   half3 SamplingScale = SSAOParams.zzw
      * (1.0f + PixelDepthMetres / 9.0f ) // generally increase sampling area with distance
      * saturate(PixelDepthMetres / 6.0f); // decrease sampling area when distance less than 6 meters

   //This will be used to scale the difference in depth between this pixel and sampled pixels
   float DepthRangeScale = ClipPlanes.y / SamplingScale.z * 0.85f;
   
   //Convert meters to screen space units
   SamplingScale.xy *= 1.0f / PixelDepthMetres;
   SamplingScale.z  *= 2.0f / ClipPlanes.y;

   float DepthTolerance = 64.0f / SamplingScale.z;   
   
   //Define 8 "base" vectors (equally spaced around a sphere, linearly increasing in length)
   //These will be mirrored by a per-pixel random normal and used as lookup
   //coordinates into the depth map for sampling surrounding depths
   const half deltaLength = 1.0f / 46.0f;
   half n = 0;
   const half3 BaseVectors[8] =
   {
      (n += deltaLength) * normalize( half3( 1, 1, 1) ),
      (n += deltaLength) * normalize( half3(-1,-1,-1) ),
      (n += deltaLength) * normalize( half3(-1,-1, 1) ),
      (n += deltaLength) * normalize( half3(-1, 1,-1) ),
      (n += deltaLength) * normalize( half3(-1, 1 ,1) ),
      (n += deltaLength) * normalize( half3( 1,-1,-1) ),
      (n += deltaLength) * normalize( half3( 1,-1, 1) ),
      (n += deltaLength) * normalize( half3( 1, 1,-1) ),
   };

   //Lookup our per-pixel random normal for mirroring
   half3 RandNormal = tex2D(RandNormMap, Input.Texcoord.zw).rgb;
   RandNormal = (2.0 * RandNormal - 1.0);   //convert range from (0 to 1) to (-1 to 1)
   
   //Now we do the actual sampling...
   half3 SampleOffset;      //the offset of the current sample from this pixel
   half4 SampledDepths[2];  //storage for our sampled depths 
   const float DefaultOcclusion = 0.55f;
   half4 Occlusion = 0.0f;   //the final measure of occlusion at this pixel
   
   //We re-use each SampleOffset by scaling it
   float SampleOffsetScale = 0.5f;

   //By storing our samples in xyzw like this we get "cheap" math below
   for(int i=0; i<2; i++)
   {
      //8 samples
      SampleOffset = mirror(BaseVectors[i*4+0], RandNormal) * SamplingScale;
      SampledDepths[0].x = tex2D( DepthMap, Input.Texcoord.xy + SampleOffset.xy ).r + SampleOffset.z;
      SampleOffset *= SampleOffsetScale;
      SampledDepths[1].x = tex2D( DepthMap, Input.Texcoord.xy + SampleOffset.xy ).r + SampleOffset.z;
      
      SampleOffset = mirror(BaseVectors[i*4+1], RandNormal) * SamplingScale;
      SampledDepths[0].y = tex2D( DepthMap, Input.Texcoord.xy + SampleOffset.xy ).r + SampleOffset.z;
      SampleOffset *= SampleOffsetScale;
      SampledDepths[1].y = tex2D( DepthMap, Input.Texcoord.xy + SampleOffset.xy ).r + SampleOffset.z;
      
      SampleOffset = mirror(BaseVectors[i*4+2], RandNormal) * SamplingScale;
      SampledDepths[0].z = tex2D( DepthMap, Input.Texcoord.xy + SampleOffset.xy ).r + SampleOffset.z;
      SampleOffset *= SampleOffsetScale;
      SampledDepths[1].z = tex2D( DepthMap, Input.Texcoord.xy + SampleOffset.xy ).r + SampleOffset.z;
      
      SampleOffset = mirror(BaseVectors[i*4+3], RandNormal) * SamplingScale;
      SampledDepths[0].w = tex2D( DepthMap, Input.Texcoord.xy + SampleOffset.xy ).r + SampleOffset.z;
      SampleOffset *= SampleOffsetScale;
      SampledDepths[1].w = tex2D( DepthMap, Input.Texcoord.xy + SampleOffset.xy ).r + SampleOffset.z;

      //tally them up
      for(int s = 0; s < 2; s++)
      {
         //here's the "cheap" math - we're doing 4 samples at once
         half4 Distance = PixelDepth - SampledDepths[s];   //the distance between our depth and the sampled depths
         float4 DistanceScaled = Distance * DepthRangeScale;
         float4 DisregardFactor = saturate(DistanceScaled);
         Occlusion += lerp(saturate((-Distance)*DepthTolerance), DefaultOcclusion, DisregardFactor);
      }
   }

   //initialize our output to avoid compiler warnings
   Output.Color = float4(1,1,1,1);
   
   //we use the dot product here as a sneaky way to finish the average
   //it simultaneously sums the 4 terms (each of which is the sum of 4 terms itself) and divides
   Output.Color = dot( Occlusion, 1/16.0f * 2.0f );
   
   //Now we'll do some final "post-processing" on it
   Output.Color = saturate(lerp( 0.9f, Output.Color - SSAOParams.y, SSAOParams.x ));   //apply brighness(y) and contrast(x)
   return Output;
}