// Copyright Epic Games, Inc. All Rights Reserved. #pragma once half3 LinearTo709Branchless(half3 lin) { return min(lin * 4.5, pow(max(lin, 0.018), 0.45) * 1.099 - 0.099); } half3 Rec709ToLinear(half3 Color) { // abs() added to silence compiler warning about computing pow() of negative number (both sides of the select are evaluated). return select(Color > 0.081, pow((abs(Color) + 0.099) / 1.099, 1.0 / 0.45), Color / 4.5); } half3 LinearToSrgbBranchless(half3 lin) { return min(lin * 12.92, pow(max(lin, 0.00313067), 1.0/2.4) * 1.055 - 0.055); // Possible that mobile GPUs might have native pow() function? //return min(lin * 12.92, exp2(log2(max(lin, 0.00313067)) * (1.0/2.4) + log2(1.055)) - 0.055); } half LinearToSrgbBranchingChannel(half lin) { if(lin < 0.00313067) return lin * 12.92; return pow(lin, (1.0/2.4)) * 1.055 - 0.055; } half3 LinearToSrgbBranching(half3 lin) { return half3( LinearToSrgbBranchingChannel(lin.r), LinearToSrgbBranchingChannel(lin.g), LinearToSrgbBranchingChannel(lin.b)); } half3 LinearToSrgb(half3 lin) { #if FEATURE_LEVEL > FEATURE_LEVEL_ES3_1 // Branching is faster than branchless on AMD on PC. return LinearToSrgbBranching(lin); #else // Adreno devices(Nexus5) with Android 4.4.2 do not handle branching version well, so always use branchless on Mobile return LinearToSrgbBranchless(lin); #endif } half3 sRGBToLinear( half3 Color ) { // abs() added to silence compiler warning about computing pow() of negative number (both sides of the select are evaluated). return select(Color > 0.04045, pow( abs(Color) * (1.0 / 1.055) + 0.0521327, 2.4 ), Color * (1.0 / 12.92)); } /** * @param GammaCurveRatio The curve ratio compared to a 2.2 standard gamma, e.g. 2.2 / DisplayGamma. So normally the value is 1. */ half3 ApplyGammaCorrection(half3 LinearColor, half GammaCurveRatio) { // Apply "gamma" curve adjustment. half3 CorrectedColor = pow(LinearColor, GammaCurveRatio); #if MAC // Note, MacOSX native output is raw gamma 2.2 not sRGB! CorrectedColor = pow(CorrectedColor, 1.0/2.2); #else #if USE_709 // Didn't profile yet if the branching version would be faster (different linear segment). CorrectedColor = LinearTo709Branchless(CorrectedColor); #else CorrectedColor = LinearToSrgb(CorrectedColor); #endif #endif return CorrectedColor; } // // Generic log lin transforms // float3 LogToLin( float3 LogColor ) { const float LinearRange = 14; const float LinearGrey = 0.18; const float ExposureGrey = 444; // Using stripped down, 'pure log', formula. Parameterized by grey points and dynamic range covered. float3 LinearColor = exp2( ( LogColor - ExposureGrey / 1023.0 ) * LinearRange ) * LinearGrey; //float3 LinearColor = 2 * ( pow(10.0, ((LogColor - 0.616596 - 0.03) / 0.432699)) - 0.037584 ); // SLog //float3 LinearColor = ( pow( 10, ( 1023 * LogColor - 685 ) / 300) - .0108 ) / (1 - .0108); // Cineon //LinearColor = max( 0, LinearColor ); return LinearColor; } float3 LinToLog( float3 LinearColor ) { const float LinearRange = 14; const float LinearGrey = 0.18; const float ExposureGrey = 444; // Using stripped down, 'pure log', formula. Parameterized by grey points and dynamic range covered. float3 LogColor = log2(LinearColor) / LinearRange - log2(LinearGrey) / LinearRange + ExposureGrey / 1023.0; // scalar: 3log2 3mad //float3 LogColor = (log2(LinearColor) - log2(LinearGrey)) / LinearRange + ExposureGrey / 1023.0; //float3 LogColor = log2( LinearColor / LinearGrey ) / LinearRange + ExposureGrey / 1023.0; //float3 LogColor = (0.432699 * log10(0.5 * LinearColor + 0.037584) + 0.616596) + 0.03; // SLog //float3 LogColor = ( 300 * log10( LinearColor * (1 - .0108) + .0108 ) + 685 ) / 1023; // Cineon LogColor = saturate( LogColor ); return LogColor; } // // Pseudo-ACES 100 nit transforms // float aces100nitFitInverseFloat(float x) { x = max(0.f, min(0.99f, x)); //float c = ((-6.32456e-8*pow((-21510484096000.0*x*x + 26714646293200.0*x + 27735750507.0), 0.5) -0.146704*x + 0.0083284)) * rcp( x - 1.01654); float c = ( -0.632456 * sqrt( -0.21510484096 *x*x + 0.267146462932 * x + 0.00027735750507 ) - 0.146704 * x + 0.0083284 ) / ( x - 1.01654 ); // Clamp to half float range return max(0.f, min(65504.f, c)); } float3 aces100nitFitInverse(float3 FilmColor) { float3 inverse; inverse.r = aces100nitFitInverseFloat(FilmColor.r); inverse.g = aces100nitFitInverseFloat(FilmColor.g); inverse.b = aces100nitFitInverseFloat(FilmColor.b); return inverse; } // // Dolby PQ transforms // float3 ST2084ToLinear(float3 pq) { const float m1 = 0.1593017578125; // = 2610. / 4096. * .25; const float m2 = 78.84375; // = 2523. / 4096. * 128; const float c1 = 0.8359375; // = 2392. / 4096. * 32 - 2413./4096.*32 + 1; const float c2 = 18.8515625; // = 2413. / 4096. * 32; const float c3 = 18.6875; // = 2392. / 4096. * 32; const float C = 10000.; float3 Np = pow( pq, 1./m2 ); float3 L = Np - c1; L = max(0., L); L = L / (c2 - c3 * Np); L = pow( L, 1./m1 ); float3 P = L * C; return P; } float3 LinearToST2084(float3 lin) { const float m1 = 0.1593017578125; // = 2610. / 4096. * .25; const float m2 = 78.84375; // = 2523. / 4096. * 128; const float c1 = 0.8359375; // = 2392. / 4096. * 32 - 2413./4096.*32 + 1; const float c2 = 18.8515625; // = 2413. / 4096. * 32; const float c3 = 18.6875; // = 2392. / 4096. * 32; const float C = 10000.; float3 L = lin/C; float3 Lm = pow(L, m1); float3 N1 = ( c1 + c2 * Lm ); float3 N2 = ( 1.0 + c3 * Lm ); float3 N = N1 * rcp(N2); float3 P = pow( N, m2 ); return P; } // // Sony S-Log3 // float3 SLog3ToLinear(float3 Value) { return select((Value >= 171.2102946929f / 1023.0f), (pow(10.0f, (Value * 1023.0f - 420.f) / 261.5f)) * 0.19f - 0.01f, (Value * 1023.0f - 95.0f) * 0.01125000f / (171.2102946929f - 95.0f)); }