Files
UnrealEngineUWP/Engine/Shaders/Private/DeviceEncodingOnlyCommon.ush
rod bogart dfdb63661d Remove Legacy tonemapper
#jira UE-106206
#rb brian.karis,michal.valient

[CL 15275511 by rod bogart in ue5-main branch]
2021-02-01 17:58:35 -04:00

357 lines
11 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
DeviceEncodingOnlyCommon.usf: PostProcessing tone mapping common
=============================================================================*/
#include "ACES.ush"
#include "GammaCorrectionCommon.ush"
// usually 1/2.2, the .y is used for inverse gamma when "gamma only" mode is not used
half3 InverseGamma;
// Scale factor for converting pixel values to nits.
// This value is required for PQ (ST2084) conversions, because PQ linear values are in nits.
// The purpose is to make good use of PQ lut entries. A scale factor of 100 conveniently places
// about half of the PQ lut indexing below 1.0, with the other half for input values over 1.0.
// Also, 100nits is the expected monitor brightness for a 1.0 pixel value without a tone curve.
static const float LinearToNitsScale = 100.0;
static const float LinearToNitsScaleInverse = 1.0 / 100.0;
half3 DeviceEncodingOnlyAndGammaCorrect(half3 LinearColor)
{
// Clamp input to be positive
// This displays negative colors as black
LinearColor = max(LinearColor, 0);
half3 GammaColor = pow(LinearColor, InverseGamma.x);
// in all cases it's good to clamp into the 0..1 range (e.g for LUT color grading)
GammaColor = saturate(GammaColor);
return GammaColor;
}
/*
============================================
// Uncharted settings
Slope = 0.63;
Toe = 0.55;
Shoulder = 0.47;
BlackClip= 0;
WhiteClip = 0.01;
// HP settings
Slope = 0.65;
Toe = 0.63;
Shoulder = 0.45;
BlackClip = 0;
WhiteClip = 0;
// Legacy settings
Slope = 0.98;
Toe = 0.3;
Shoulder = 0.22;
BlackClip = 0;
WhiteClip = 0.025;
// ACES settings
Slope = 0.91;
Toe = 0.53;
Shoulder = 0.23;
BlackClip = 0;
WhiteClip = 0.035;
===========================================
*/
float FilmSlope = 0.91;
float FilmToe = 0.53;
float FilmShoulder = 0.23;
float FilmBlackClip = 0;
float FilmWhiteClip = 0.035;
half3 FilmDeviceEncodingOnly( half3 LinearColor )
{
const float3x3 sRGB_2_AP0 = mul( XYZ_2_AP0_MAT, mul( D65_2_D60_CAT, sRGB_2_XYZ_MAT ) );
const float3x3 sRGB_2_AP1 = mul( XYZ_2_AP1_MAT, mul( D65_2_D60_CAT, sRGB_2_XYZ_MAT ) );
const float3x3 AP0_2_sRGB = mul( XYZ_2_sRGB_MAT, mul( D60_2_D65_CAT, AP0_2_XYZ_MAT ) );
const float3x3 AP1_2_sRGB = mul( XYZ_2_sRGB_MAT, mul( D60_2_D65_CAT, AP1_2_XYZ_MAT ) );
const float3x3 AP0_2_AP1 = mul( XYZ_2_AP1_MAT, AP0_2_XYZ_MAT );
const float3x3 AP1_2_AP0 = mul( XYZ_2_AP0_MAT, AP1_2_XYZ_MAT );
float3 ColorAP1 = LinearColor;
//float3 ColorAP1 = mul( sRGB_2_AP1, float3(LinearColor) );
#if 0
{
float3 oces = Inverse_ODT_sRGB_D65( LinearColor );
float3 aces = Inverse_RRT( oces );
ColorAP1 = mul( AP0_2_AP1, aces );
}
#endif
#if 0
float3 ColorSRGB = mul( AP1_2_sRGB, ColorAP1 );
ColorSRGB = max( 0, ColorSRGB );
ColorAP1 = mul( sRGB_2_AP1, ColorSRGB );
#endif
float3 ColorAP0 = mul( AP1_2_AP0, ColorAP1 );
#if 0
{
float3 aces = ColorAP0;
float3 oces = RRT( aces );
LinearColor = ODT_sRGB_D65( oces );
}
return mul( sRGB_2_AP1, LinearColor );
#endif
#if 1
// "Glow" module constants
const float RRT_GLOW_GAIN = 0.05;
const float RRT_GLOW_MID = 0.08;
float saturation = rgb_2_saturation( ColorAP0 );
float ycIn = rgb_2_yc( ColorAP0 );
float s = sigmoid_shaper( (saturation - 0.4) / 0.2);
float addedGlow = 1 + glow_fwd( ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID);
ColorAP0 *= addedGlow;
#endif
#if 1
// --- Red modifier --- //
const float RRT_RED_SCALE = 0.82;
const float RRT_RED_PIVOT = 0.03;
const float RRT_RED_HUE = 0;
const float RRT_RED_WIDTH = 135;
float hue = rgb_2_hue( ColorAP0 );
float centeredHue = center_hue( hue, RRT_RED_HUE );
float hueWeight = Square( smoothstep( 0, 1, 1 - abs( 2 * centeredHue / RRT_RED_WIDTH ) ) );
ColorAP0.r += hueWeight * saturation * (RRT_RED_PIVOT - ColorAP0.r) * (1. - RRT_RED_SCALE);
#endif
// Use ACEScg primaries as working space
float3 WorkingColor = mul( AP0_2_AP1_MAT, ColorAP0 );
WorkingColor = max( 0, WorkingColor );
// Pre desaturate
WorkingColor = lerp( dot( WorkingColor, AP1_RGB2Y ), WorkingColor, 0.96 );
const half ToeScale = 1 + FilmBlackClip - FilmToe;
const half ShoulderScale = 1 + FilmWhiteClip - FilmShoulder;
const float InMatch = 0.18;
const float OutMatch = 0.18;
float ToeMatch;
if( FilmToe > 0.8 )
{
// 0.18 will be on straight segment
ToeMatch = ( 1 - FilmToe - OutMatch ) / FilmSlope + log10( InMatch );
}
else
{
// 0.18 will be on toe segment
// Solve for ToeMatch such that input of InMatch gives output of OutMatch.
const float bt = ( OutMatch + FilmBlackClip ) / ToeScale - 1;
ToeMatch = log10( InMatch ) - 0.5 * log( (1+bt)/(1-bt) ) * (ToeScale / FilmSlope);
}
float StraightMatch = ( 1 - FilmToe ) / FilmSlope - ToeMatch;
float ShoulderMatch = FilmShoulder / FilmSlope - StraightMatch;
half3 LogColor = log10( WorkingColor );
half3 StraightColor = FilmSlope * ( LogColor + StraightMatch );
half3 ToeColor = ( -FilmBlackClip ) + (2 * ToeScale) / ( 1 + exp( (-2 * FilmSlope / ToeScale) * ( LogColor - ToeMatch ) ) );
half3 ShoulderColor = ( 1 + FilmWhiteClip ) - (2 * ShoulderScale) / ( 1 + exp( ( 2 * FilmSlope / ShoulderScale) * ( LogColor - ShoulderMatch ) ) );
ToeColor = LogColor < ToeMatch ? ToeColor : StraightColor;
ShoulderColor = LogColor > ShoulderMatch ? ShoulderColor : StraightColor;
half3 t = saturate( ( LogColor - ToeMatch ) / ( ShoulderMatch - ToeMatch ) );
t = ShoulderMatch < ToeMatch ? 1 - t : t;
t = (3-2*t)*t*t;
half3 ToneColor = lerp( ToeColor, ShoulderColor, t );
// Post desaturate
ToneColor = lerp( dot( float3(ToneColor), AP1_RGB2Y ), ToneColor, 0.93 );
// Returning positive AP1 values
return max( 0, ToneColor );
}
half3 FilmDeviceEncodingOnlyInverse( half3 ToneColor )
{
const float3x3 sRGB_2_AP1 = mul( XYZ_2_AP1_MAT, mul( D65_2_D60_CAT, sRGB_2_XYZ_MAT ) );
const float3x3 AP1_2_sRGB = mul( XYZ_2_sRGB_MAT, mul( D60_2_D65_CAT, AP1_2_XYZ_MAT ) );
// Use ACEScg primaries as working space
half3 WorkingColor = mul( sRGB_2_AP1, saturate( ToneColor ) );
WorkingColor = max( 0, WorkingColor );
// Post desaturate
WorkingColor = lerp( dot( WorkingColor, AP1_RGB2Y ), WorkingColor, 1.0 / 0.93 );
half3 ToeColor = 0.374816 * pow( 0.9 / min( WorkingColor, 0.8 ) - 1, -0.588729 );
half3 ShoulderColor = 0.227986 * pow( 1.56 / ( 1.04 - WorkingColor ) - 1, 1.02046 );
half3 t = saturate( ( WorkingColor - 0.35 ) / ( 0.45 - 0.35 ) );
t = (3-2*t)*t*t;
half3 LinearColor = lerp( ToeColor, ShoulderColor, t );
// Pre desaturate
LinearColor = lerp( dot( LinearColor, AP1_RGB2Y ), LinearColor, 1.0 / 0.96 );
LinearColor = mul( AP1_2_sRGB, LinearColor );
// Returning positive sRGB values
return max( 0, LinearColor );
}
//
// ACES sRGB D65 Output Transform - Forward and Inverse
// Input is scene-referred linear values in the sRGB gamut
// Output is output-referred linear values in the sRGB gamut
//
float3 ACESOutputTransformsRGBD65( float3 SceneReferredLinearsRGBColor )
{
const float3x3 sRGB_2_AP0 = mul( XYZ_2_AP0_MAT, mul( D65_2_D60_CAT, sRGB_2_XYZ_MAT ) );
float3 aces = mul( sRGB_2_AP0, SceneReferredLinearsRGBColor * 1.5 );
float3 oces = RRT( aces );
float3 OutputReferredLinearsRGBColor = ODT_sRGB_D65( oces );
return OutputReferredLinearsRGBColor;
}
float3 InverseACESOutputTransformsRGBD65( float3 OutputReferredLinearsRGBColor )
{
const float3x3 AP0_2_sRGB = mul( XYZ_2_sRGB_MAT, mul( D60_2_D65_CAT, AP0_2_XYZ_MAT ) );
float3 oces = Inverse_ODT_sRGB_D65( OutputReferredLinearsRGBColor );
float3 aces = Inverse_RRT( oces );
float3 SceneReferredLinearsRGBColor = mul( AP0_2_sRGB, aces ) * 0.6666;
return SceneReferredLinearsRGBColor;
}
//
// ACES D65 1000 nit Output Transform - Forward
// Input is scene-referred linear values in the sRGB gamut
// Output is output-referred linear values in the AP1 gamut
//
float3 ACESOutputTransforms1000( float3 SceneReferredLinearsRGBColor )
{
const float3x3 sRGB_2_AP0 = mul( XYZ_2_AP0_MAT, mul( D65_2_D60_CAT, sRGB_2_XYZ_MAT ) );
float3 aces = mul( sRGB_2_AP0, SceneReferredLinearsRGBColor * 1.5 );
float3 oces = RRT( aces );
float3 OutputReferredLinearAP1Color = ODT_1000nits( oces );
return OutputReferredLinearAP1Color;
}
//
// ACES D65 2000 nit Output Transform - Forward
// Input is scene-referred linear values in the sRGB gamut
// Output is output-referred linear values in the AP1 gamut
//
float3 ACESOutputTransforms2000( float3 SceneReferredLinearsRGBColor )
{
const float3x3 sRGB_2_AP0 = mul( XYZ_2_AP0_MAT, mul( D65_2_D60_CAT, sRGB_2_XYZ_MAT ) );
float3 aces = mul( sRGB_2_AP0, SceneReferredLinearsRGBColor * 1.5 );
float3 oces = RRT( aces );
float3 OutputReferredLinearAP1Color = ODT_2000nits( oces );
return OutputReferredLinearAP1Color;
}
static const float3x3 GamutMappingIdentityMatrix = { 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 };
//
// Gamut conversion matrices
//
float3x3 OuputGamutMappingMatrix( uint OutputGamut )
{
// Gamut mapping matrices used later
const float3x3 AP1_2_sRGB = mul( XYZ_2_sRGB_MAT, mul( D60_2_D65_CAT, AP1_2_XYZ_MAT ) );
const float3x3 AP1_2_DCI_D65 = mul( XYZ_2_P3D65_MAT, mul( D60_2_D65_CAT, AP1_2_XYZ_MAT ) );
const float3x3 AP1_2_Rec2020 = mul( XYZ_2_Rec2020_MAT, mul( D60_2_D65_CAT, AP1_2_XYZ_MAT ) );
// Set gamut mapping matrix
// 0 = sRGB - D65
// 1 = P3 - D65
// 2 = Rec.2020 - D65
// 3 = ACES AP0 - D60
// 4 = ACES AP1 - D60
if( OutputGamut == 1 )
return AP1_2_DCI_D65;
else if( OutputGamut == 2 )
return AP1_2_Rec2020;
else if( OutputGamut == 3 )
return AP1_2_AP0_MAT;
else if( OutputGamut == 4 )
return GamutMappingIdentityMatrix;
else
return AP1_2_sRGB;
}
float3x3 OuputInverseGamutMappingMatrix( uint OutputGamut )
{
// Gamut mapping matrices used later
const float3x3 sRGB_2_AP1 = mul( XYZ_2_AP1_MAT, mul( D65_2_D60_CAT, sRGB_2_XYZ_MAT ) );
const float3x3 DCI_D65_2_AP1 = mul( XYZ_2_AP1_MAT, mul( D65_2_D60_CAT, P3D65_2_XYZ_MAT ) );
const float3x3 Rec2020_2_AP1 = mul( XYZ_2_AP1_MAT, mul( D65_2_D60_CAT, Rec2020_2_XYZ_MAT ) );
// Set gamut mapping matrix
// 0 = sRGB - D65
// 1 = P3 (DCI) - D65
// 2 = Rec.2020 - D65
// 3 = ACES AP0 - D60
float3x3 GamutMappingMatrix = sRGB_2_AP1;
if( OutputGamut == 1 )
GamutMappingMatrix = DCI_D65_2_AP1;
else if( OutputGamut == 2 )
GamutMappingMatrix = Rec2020_2_AP1;
else if( OutputGamut == 3 )
GamutMappingMatrix = AP0_2_AP1_MAT;
return GamutMappingMatrix;
}
float3 ST2084ToScRGB(float3 Color, uint OutputDevice)
{
// Nvidia HDR encoding - Remove PQ, convert to linear scRGB
const float3x3 AP1_2_sRGB = mul(XYZ_2_sRGB_MAT, AP1_2_XYZ_MAT);
const float WhitePoint = 80.f;
// 1000.f nit display
float MaxODTNits = 1000.0f;
float MinODTNits = 0.0001f;
if (OutputDevice == 4 || OutputDevice == 6)
{
// 2000 nit display
MaxODTNits = 2000.0f;
MinODTNits = 0.005f;
}
float3 OutColor = ST2084ToLinear(Color);
OutColor = clamp(OutColor, MinODTNits, MaxODTNits);
OutColor.x = Y_2_linCV(OutColor.x, MaxODTNits, MinODTNits);
OutColor.y = Y_2_linCV(OutColor.y, MaxODTNits, MinODTNits);
OutColor.z = Y_2_linCV(OutColor.z, MaxODTNits, MinODTNits);
float scRGBScale = MaxODTNits / WhitePoint;
OutColor = mul(AP1_2_sRGB, OutColor) * scRGBScale;
return OutColor;
}