//
// Basic HDR functions
// Only 'ToneMapHDR()' is used - the rest is experimentation
// RvG, 11-3-09
//

//
// Helpers
//
float3 NightDesaturate(float3 color)
// Given a color, desaturate low-light color to simulate night-time
// and eyes not being able to distinguish color anymore
{
  // Desaturate low-light pixels (per-pixel, so lit areas are unaffected)
  // Calculate black & white value, looking at brightness of red/green/blue individually
  float bw=0.28*color.r+0.59*color.g+0.13*color.b;
  // Fully desaturated color to interpolate to - add a bit of a blue shift
  // that seems to give a 'night' feeling
  float3 bwColor=float3(bw*0.8,bw*0.8,bw*1.1);
  // Desaturation amount
  float desat=exp(-10.0*bw);
  //  Always leave a bit of color
  desat=min(desat,0.99);
  color=lerp(color,bwColor,desat);
  
  return color;
}

//
// Tonemappers
//
float4 ToneMapHDR_Linear(float4 color,float exposure)
// Linear (more like LDR)
{
  return exposure*color;
}

float4 ToneMapHDR_Ogre(float4 color,float exposure)
// Linear (more like LDR)
// From 'Photographic Tone Reproduction For Digital Images' and Ogre
{
  /*const float MIDDLE_GREY = 0.72;
  const float FUDGE = 0.001;
  const float L_WHITE = 0.2;*/
  
  /*
  const float MIDDLE_GREY = 1.5;
  const float FUDGE = 0.001;
  const float L_WHITE = 0.5;*/
  
  // Nice
  /*
  const float MIDDLE_GREY = 0.5;
  const float FUDGE = 0.001;
  const float L_WHITE = 0.4;
  */
  
  const float MIDDLE_GREY = 0.72;
  const float FUDGE = 0.001;
  const float L_WHITE = 0.5;

  // Initial luminence scaling (equation 2)
  color.rgb *= MIDDLE_GREY / (FUDGE + (1.0/exposure));

  // Control white out (equation 4 nom)
  color.rgb *= (1.0 + color.rgb / L_WHITE);

  // Final mapping (equation 4 denom)
  color.rgb /= (1.0 + color.rgb);

  return color;
}

float4 ToneMapHDR0(float4 color,float exposure)
// Easy trick
{
  color/=color+1.0;
  
  // Gamma correction (pow(2.0))
  const float gamma=2.2;
  color=pow(color,gamma);
  return color;
}

float4 ToneMapHDR1(float4 color,float exposure)
{
  // Currently unchanged (LDR)
  // Future: map HDR color to LDR color
  return color;
}

float4 ToneMapHDR2(float4 color,float exposure)
// Simple mapping from 0..inf -> 0..1
// Darkens image but bright colors remain visible
{
  float4 c;
  
  c.r=color.r/(color.r+1.0);
  c.g=color.g/(color.g+1.0);
  c.b=color.b/(color.b+1.0);
  c.a=color.a;
  return c;
}

float4 ToneMapHDR3(float4 color,float exposure)
// Simple mapping from 0..inf -> 0..1
// Darkens image but bright colors remain visible
{
  float4 c;

  c=color*0.5;
  c.a=1;
  return c;
}

float4 ToneMapHDR4(float4 color,float exposure)
// Exponential diminish
{
  return 1.0-exp(-exposure*color);
}

float4 ToneMapHDR5(float4 color,float exposure)
// From HDRRenderingInOpenGL.pdf
{
  float Y = dot(vec4(0.30, 0.59, 0.11, 0.0), color);
  float YD = exposure * (exposure/brightMax + 1.0) / (exposure + 1.0);
  color *= YD;
  return color;
}

float4 ToneMapHDR6(float4 color,float exposure)
// Exponential (more contrast than L=L/(L+1)
// Used in Racer v0.8.8 for example
{
  float3 c;
  //c=1.1-1.2*exp(-exposure*color);
  c=1.0-exp(-2.0*exposure*color.rgb); //do we want alpha to be 1 for sure?
  // Gamma correction (pow(2.0))
  const float gamma=2.2;
  c=pow(c,gamma);
  //c*=c;
  return float4(c,1);
  
  //return color;
  //return 1.1-1.2*exp(-exposure*color);
  //return 1.0-exp(-exposure*color);
  //return 1.05-1.1*exp(-exposure*color);
  //return 1.2-1.3*exp(-exposure*color);
  //return 1.0-color;
}

float4 ToneMapHDR7(float4 color,float exposure)
// Exponential (more contrast than L=L/(L+1)
// Used in Racer v0.8.17+ for lux light unit
{
  float3 c;
  
  c=1.0-exp(-exposure*color.rgb); // do we want alpha to be 1 for sure?
  //c=1.0-0.01*exp(-exposure*color.rgb); // do we want alpha to be 1 for sure?

  // Gamma correction (pow(2.0))
  const float gamma=2.0;
  c=pow(c,gamma);
  //c*=c;
  return float4(c,1);
}

float4 ToneMapHDR8(float4 color,float exposure)
// Exponential (more contrast than L=L/(L+1)
// Desaturates in low-light to mimic night
// Used in Racer v0.8.17+ for lux light unit
{
  float3 c;
  
  // Desaturate low-light pixels (per-pixel, so lit areas are unaffected)
  // Calculate black & white value, looking at brightness of red/green/blue individually
  float bw=0.28*color.r+0.59*color.g+0.13*color.b;
  // Fully desaturated color to interpolate to - add a bit of a blue shift
  // that seems to give a 'night' feeling
  float3 bwColor=float3(bw*0.8,bw*0.8,bw*1.1);
  // Desaturation amount
  float desat=exp(-0.9*bw);
  //  Always leave a bit of color
  desat=min(desat,0.9);
  color.rgb=lerp(color.rgb,bwColor,desat);

  // HDR->LDR tonemap
  c=1.0-exp(-exposure*color.rgb);
  //c=color/(color+1);
  //c=exposure*color/(exposure*color+1);

  // Gamma correction?
  //const float gamma=2.2;
  //c=pow(c,gamma);
  //c = max(0, c-0.004);
  //c = (c*(6.2*c+0.5))/(c*(6.2*c+1.7)+0.06);
  return float4(c,1);
}

float4 ToneMapHDR_Uncharted_v0821(float4 color,float exposure)
// Filmic tonemapping from Uncharted 2
// Desaturates in low-light to mimic night
// Used in Racer v0.8.21
{
  float3 c;
  
  // Desaturate low-light pixels (per-pixel, so lit areas are unaffected)
  // Calculate black & white value, looking at brightness of red/green/blue individually
  float bw=0.28*color.r+0.59*color.b+0.13*color.b;
  // Fully desaturated color to interpolate to - add a bit of a blue shift
  // that seems to give a 'night' feeling
  float3 bwColor=float3(bw*0.8,bw*0.8,bw*1.1);
  // Desaturation amount
  float desat=exp(-5.0*bw);
  //  Always leave a bit of color
  desat=min(desat,0.9);
  color.rgb=lerp(color.rgb,bwColor,desat);

  // HDR->LDR tonemap
  //float3 x=max(0,color.rgb-float3(0.004,0.004,0.004));
  c=1.0-exp(-exposure*color.rgb);

  // Gamma correction?
  //const float gamma=2.2;
  //c=pow(c,gamma);
  return float4(c,1);
}

//
// atan() mapping by Marno Hopmans
//
float4 ToneMapHDR_atan(float4 color,float exposure)
// Filmic tonemapping
// Desaturates in low-light to mimic night
{
  float3 c;

  // Night time gives blue shift
  //color.rgb=NightDesaturate(color.rgb);

  // Filmic tonemapping
  c=color;
  //c.rgb-=exposure;
  float3 x=max(float3(0,0,0),c.rgb-float3(0.004,0.004,0.004));
  x=c.rgb;
  
  // Marno function
  const float pi=3.14159265;
  float middleCol=exposure;
  c=(atan(0.5*(x-middleCol))/pi)+0.5;
  //c=x;
  
  // Gamma correction?
  const float gamma=1.0/2.2;
  //c=pow(c,gamma);
  //c = max(0, c-0.004);
  //c = (c*(6.2*c+0.5))/(c*(6.2*c+1.7)+0.06);
  return float4(c,1);
}

//
// Ye olde Reinhard
//
float4 ToneMapHDR_Reinhard(float4 color,float exposure)
// Filmic tonemapping
{
  float3 c;

  // Night time gives blue shift
  //color.rgb=NightDesaturate(color.rgb);

  // Filmic tonemapping
  c=color;
  c.rgb*=exposure;
  
  // Reinhard tonemapping
  c=c/(1+c);
  
  // Gamma correction so that it comes out linearly to your eye
  const float gamma=1.0/2.2;
  c=pow(c,gamma);
  return float4(c,1);
}

//
// Jim Heyl and Richard Burgess-Dawson
// Optimized curve
//
float4 ToneMapHDR_Jim(float4 color,float exposure)
// Filmic tonemapping
// Desaturates in low-light to mimic night
{
  float3 c;

  // Night time gives blue shift
  //color.rgb=NightDesaturate(color.rgb);

/*
  // Filmic tonemapping
  c=color;
  c.rgb*=exposure;
  //float3 x=max(float3(0,0,0),c.rgb-float3(0.004,0.004,0.004));
  float d=0; //exposure;
  float3 x=max(float3(0,0,0),c.rgb-float3(d,d,d));
  //float3 x=c.rgb;
  //c.rgb-=1.9;
  //c=(x*(float3(6.2,6.2,6.2)*x+float3(0.5,0.5,0.5) ))/(x*(float3(6.2,6.2,6.2)*x+float3(1.7,1.7,1.7) )
  //       + float3(0.06,0.06,0.06) );
*/
 
  float3 texColor=color*exposure;
  
  //float3 x = max(0,texColor-0.004);
  //float3 retColor = (x*(6.2*x+.5))/(x*(6.2*x+1.7)+0.06);
  
  c=color*exposure;
  float d=0.004;
  float3 x=max(float3(0,0,0),c.rgb-float3(d,d,d));
  // Next formula includes gamma correct ( pow(c,1/2.2) )
  c=(x*(float3(6.2,6.2,6.2)*x+float3(0.5,0.5,0.5) ))/(x*(float3(6.2,6.2,6.2)*x+float3(1.7,1.7,1.7) )
         + float3(0.06,0.06,0.06) );

  //~c=retColor;
  
  //const float whitePoint=exposure; //11.2;
  //c=Film(x); ///Film(whitePoint);

  // Gamma correction so that it comes out linearly to your eye
  const float gamma=1.0/2.2;
  //c=pow(c,gamma);
  //c = max(0, c-0.004);
  //c = (c*(6.2*c+0.5))/(c*(6.2*c+1.7)+0.06);
  return float4(c,1);
}

//
// Uncharted 2
//
float3 UC2ToneMap(float3 x)
{
  // A = Shoulder Strength
  // B = Linear Strength
  // C = Linear Angle
  // D = Toe Strength
  // E = Toe Numerator
  // F = Toe Denominator
  // From the PowerPoint presentation
  const float A=0.22,B=0.30,C=0.10,D=0.20,E=0.01,F=0.30;  // From the PowerPoint presentation
  // From http://filmicgames.com/archives/75
  //const float A=0.15,B=0.50,C=0.10,D=0.20,E=0.02,F=0.30;
  
  return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F)) - E/F;
}

float4 ToneMapHDR_Uncharted2(float4 color,float exposure)
// Filmic tonemapping
// Desaturates in low-light to mimic night
{
  float3 c;
  const float W=11.2;
  
  // Night time gives blue shift
  color.rgb=NightDesaturate(color.rgb);

  // Exposure control
  c=color*exposure;
  
  float ExposureBias = 2.0f;
  float3 curr = UC2ToneMap(ExposureBias*c);
  float3 whiteScale = 1.0f/UC2ToneMap(W);
  c = curr*whiteScale;
  
  // Gamma correction so that it comes out linearly to your eye
  c=pow(c,gammaFactor*1.0/2.2);
  
  return float4(c,1);
}

//
// Modified Reinhard for localized stuff
// From: http://imdoingitwrong.wordpress.com/2010/08/19/why-reinhard-desaturates-my-blacks-3/
//
float RHT_ToneMap(float x)
{
  // A = Shoulder Strength
  // B = Linear Strength
  // C = Linear Angle
  // D = Toe Strength
  // E = Toe Numerator
  // F = Toe Denominator
  // From the PowerPoint presentation
  //const float A=0.22,B=0.30,C=0.10,D=0.20,E=0.01,F=0.30;  // From the PowerPoint presentation
  // From http://filmicgames.com/archives/75
  const float A=0.22,B=0.30,C=0.10,D=0.20,E=0.01,F=0.30;
  
  return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F)) - E/F;
}
float4 ToneMapHDR_ReinhardTom(float4 color,float exposure)
// Reinhard with a twist to avoid taking the entire dynamic range
// and compressing it all, giving a flat image.
// Tom Madams adjusted Reinhard a little, to use a single luminance.
{
  float3 c;

  // Night time gives blue shift
  //color.rgb=NightDesaturate(color.rgb);

  c=color*exposure;

  // Reinhard tonemapping using a single luminance to avoid color shifts
  // Calc luminance of pixel
  float L=0.2126*c.r + 0.7152 * c.g + 0.0722 * c.b;
  float nL=RHT_ToneMap(L);
  float scale=nL/L;
  c*=scale;
  
  // Gamma correction so that it comes out linearly to your eye
  const float gamma=1.0/2.2;
  c=pow(c,gamma);
  
  return float4(c,1);
}

float4 ToneMapHDR(float4 color,float exposure)
// This function is actually being called - it will pass the arguments
// through to the actual implementation
{
  //return ToneMapHDR_Uncharted_v0821(color,exposure);
  //return ToneMapHDR_Reinhard(color,exposure);     // A bit flat
  //return ToneMapHDR_ReinhardTom(color,exposure);     // ?
  //return ToneMapHDR_Jim(color,exposure);          // Quite nice but less control
  return ToneMapHDR_Uncharted2(color,exposure);     // Very ok, may need a bit of extra gamma
  //return ToneMapHDR_atan(color,exposure);
}