// // 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.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(-3.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 //const float gamma=1.0/2.2; c=pow(c,gamma*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); }