【目标】

UE4的Tonemapper

【思路】

1 UE3中关键字

  • USE_TONEMAPPERTYPE
  • \Shaders\UberPostProcessBlendPixelShader.usf
  • TonemapAndGammaCorrect



2 UE3的算法

 

half3 TonemapAndGammaCorrect(half3 LinearColor)
{
half A = (half)ImageAdjustments2.x;
half B = (half)ImageAdjustments2.y;

half3 GammaColor;

#if USE_TONEMAPPERTYPE == 0
// no tonemapper
{
GammaColor = pow(LinearColor, 1.0f / 2.2f);
}
#elif USE_TONEMAPPERTYPE == 1
// filmic approximation (s-curve, contrast enhances, small toe, changes saturation and hue)
// simple but effective tonemapper
// outputs in 0..1 range (saturate())
// clamps negative colors to 0 (abs())
{
GammaColor = LinearColor / abs(LinearColor + A) * B;
}

#elif USE_TONEMAPPERTYPE == 2
// neutral soft white clamp (experimental, not optimized yet)
// outputs in 0..1 range (saturate())
// clamps negataive colors to 0 (abs())
{
// ToeFactor 0 = non means linear .. 1 = full (filmic look)
half ToeFactor = (half)ImageAdjustments3.x;
half LinearSteepness = (half)ImageAdjustments2.w;
// value below we have linear adjustments to the sigmoid curve that we don't to above the value
half FunctionSplitPos = ImageAdjustments2.z;

half3 GammaColorOldTonemapperTonemapperStartingFromLinear = LinearColor / abs(LinearColor + A) * B;

// efficient 3 component implementation of: LinearColor >= FunctionSplitPos
half3 SplitMask = saturate((LinearColor - FunctionSplitPos) * 10000.0f);

const half3 GammaColorNotTonemapped = pow(LinearColor * LinearSteepness, 1.0f / 2.2f);

half3 FlatGammaColor = lerp(GammaColorNotTonemapped, GammaColorOldTonemapperTonemapperStartingFromLinear, SplitMask);

GammaColor = lerp(FlatGammaColor, GammaColorOldTonemapperTonemapperStartingFromLinear, ToeFactor);
}
#endif

// in all cases it's good to clamp into the 0..1 range (e.g for LUT color grading)
GammaColor = saturate(GammaColor);

return GammaColor;
}


变量定义

// xy=Grain Offset to have animated noise, z=unused, w=ImageGrainScale
// Note: ImageAdjustments1.xy is a random float2 in the range 0..1
float4 ImageAdjustments1;
// TonemapperFunction(x) = x / (x + A) * B
// x=A, y=B, z=split pos, w=LinearSteepness
float4 ImageAdjustments2;
// x=tonemapper toe factor (0=linear..1=full toe)
float4 ImageAdjustments3;




3 UE4相关

ACES Gets in the Games

Bookmark and Share

Mon, 04/10/2017 - 12:01 -- Sarah Priestnall

A royalty-free image from Epic Games' Unreal Engine.The Motion Picture Academy’s Academy Color Encoding System is now widely used across the movie industry but perhaps surprisingly, it’s also gaining traction in the gaming world. To learn more, Digital Cinema Report recently spoke with Brian Karis at Cary, North Carolina-based Epic Games. The company is known for its industry-leading Unreal Engine, a ground breaking game engine that can be used by other game developers who do not want to develop their own engine.

Digital Cinema Report: Tell us what your role at Epic Games is?

Brian Karis: I'm a senior graphics programmer on the rendering team here at Epic. I work on many different aspects of the renderer.

DCR: What made you decide to incorporate ACES?

Brian KarisBK: A couple of years ago I tackled changing our tone mapper from a proprietary ad hoc curve with some artist parameters to a new curve that better simulated film response and could be configured to fit a number of different film responses. During that effort I learned a ton about color science and physical film. I also came upon ACES and decided it should be the foundation for our color handling. Right now, we do all rendering in Rec. 709 space but do the grading in ACEScg and then use an Output Transform. I ended up creating a parametric curve that combines the Reference Rendering Transform (RRT) and Output Device Transform (ODT) for 100 nit monitors that allows the artist to get different film stock looks than the ACES standard look but our defaults match ACES. So currently the default tone mapper and color grading in Unreal Engine 4 (UE4) is ACES.

DCR: What advantages do you see in using ACES within a gaming engine?

BK: What advantages there are is a large question but I'll touch on a few things. In my opinion the biggest impact is from doing tone mapping in ACEScg space. Regardless of the curve, that alone makes colors look so much better and more realistic. The specific curve of ACES matches film characteristics but honestly it is just a choice of look amongst many options. Having the RRT and ODT separated is a really good idea and one I wish I had embraced earlier. If I had, I could have had my parametric curve work for HDR displays as well. That now needs a bit of a rethink. Matching a standard that we can load up in Nuke or other packages has proven useful so far and I expect will be ever more useful in the future as UE4 becomes more used by film and VFX companies.

For HDR displays we use the ACES ODTs and disable those controls. I'd like to make that work in the future. We are also interested in adding support for rendering in ACEScg space. I recently engaged with the larger ACES community in helping write a retrospective and suggestions for improvements for future versions.


4 youtube介绍

https://www.youtube.com/watch?v=A-wectYNfRQ

Filmic Tonemapper | Feature Highlight | Unreal Engine

 

 
 
 

 
 


5 UE4 Shader流向

TonemapCommon.usf FilmSlope ->FLUTBlenderPS.FilmSlope 


FilmToneMap函数

/*
============================================
// 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.88;
Toe = 0.55;
Shoulder = 0.26;
BlackClip = 0;
WhiteClip = 0.04;
===========================================
*/

float FilmSlope;
float FilmToe;
float FilmShoulder;
float FilmBlackClip;
float FilmWhiteClip;

half3 FilmToneMap( 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 AP1_2_sRGB = mul( XYZ_2_sRGB_MAT, mul( D60_2_D65_CAT, AP1_2_XYZ_MAT ) );
#if !ES2_PROFILE //disabling this part for mobile, Adreno devices can't handle it (UE-40689)
float3 ACESColor = mul( sRGB_2_AP0, float3(LinearColor) );

// --- 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 saturation = rgb_2_saturation( ACESColor );
float hue = rgb_2_hue( ACESColor );
float centeredHue = center_hue( hue, RRT_RED_HUE );
float hueWeight = Square( smoothstep( 0, 1, 1 - abs( 2 * centeredHue / RRT_RED_WIDTH ) ) );
ACESColor.r += hueWeight * saturation * (RRT_RED_PIVOT - ACESColor.r) * (1. - RRT_RED_SCALE);

// Use ACEScg primaries as working space
float3 WorkingColor = mul( AP0_2_AP1_MAT, ACESColor );
#else
// Use ACEScg primaries as working space
float3 WorkingColor = mul( sRGB_2_AP1, float3(LinearColor) );
#endif

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 );

//ToneColor = mul( AP1_2_sRGB, ToneColor );

//return saturate( ToneColor );
//return max( 0, ToneColor );
}


6  \UnrealEngine4\Engine\Shaders\ACES.usf 定义一些要用到的常量

https://github.com/ampas/aces-dev/tree/v1.0


\UnrealEngine4\Engine\Shaders\PostProcessTonemap.usf

\UnrealEngine4\Engine\Shaders\TonemapCommon.usf

7 计算流程

  • PostProcessCombineLUTs.usf MainPS



https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/

 





【步骤】

1 修改 \Engine\Shaders\UberPostProcessBlendPixelShader.usf


elif USE_TONEMAPPERTYPE == 3
{
//float a = 2.51f;
    //float b = 0.03f;
    //float c = 2.43f;
    //float d = 0.59f;
    //float e = 0.14f;
    //return saturate((x*(a*x+b))/(x*(c*x+d)+e));
    GammaColor = saturate((LinearColor*(2.51f*LinearColor+0.03f))/(LinearColor*(2.43f*LinearColor+0.59f)+0.14f));
}



2 修改\Development\Src\Engine\Classes\UberPostProcessEffect.uc 添加类型

/** Allows to specify the tone mapper function which maps HDR colors into the LDR color range. */
var(Tonemapper) enum ETonemapperType
{
    Tonemapper_Off<DisplayName=Off>, 
    Tonemapper_ACES<DisplayName=ACES>, 
    Tonemapper_Customizable<DisplayName=Customizable>, 
    Tonemapper_Filmic<DisplayName=Filmic>, 
} TonemapperType;


3 FUberPostProcessSceneProxy.Render 

#define VARIATION1(A)        VARIATION2(A,0)            VARIATION2(A,1)            VARIATION2(A,2)            VARIATION2(A,3)



4 UberPostProcessEffect.cpp 

#define VARIATION1(A)        VARIATION2(A,0)            VARIATION2(A,1)            VARIATION2(A,2)        VARIATION2(A,3)


运行 ACES效果


对比film效果
 
没有试过UE4的效果


5 UE4算法

 
6 拷贝ACES.usf
\Engine\Shaders\UberPostProcessBlendPixelShader.usf
#include "ACES.usf"
...


\Engine\Shaders\UberPostProcessBlendPixelShader.usf

/*
============================================
// 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.88;
Toe = 0.55;
Shoulder = 0.26;
BlackClip = 0;
WhiteClip = 0.04;
===========================================
*/

const static float FilmSlope = 0.88;
const static float FilmToe = 0.55;
const static float FilmShoulder = 0.26;
const static float FilmBlackClip = 0;
const static float FilmWhiteClip = 0.04;

half3 FilmToneMap( 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 AP1_2_sRGB = mul( XYZ_2_sRGB_MAT, mul( D60_2_D65_CAT, AP1_2_XYZ_MAT ) );
float3 ACESColor = mul( sRGB_2_AP0, float3(LinearColor) );

// --- 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 saturation = rgb_2_saturation( ACESColor );
float hue = rgb_2_hue( ACESColor );
float centeredHue = center_hue( hue, RRT_RED_HUE );
float hueWeight = Square( smoothstep( 0, 1, 1 - abs( 2 * centeredHue / RRT_RED_WIDTH ) ) );
ACESColor.r += hueWeight * saturation * (RRT_RED_PIVOT - ACESColor.r) * (1. - RRT_RED_SCALE);

// Use ACEScg primaries as working space
float3 WorkingColor = mul( AP0_2_AP1_MAT, ACESColor );


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 );

//ToneColor = mul( AP1_2_sRGB, ToneColor );

//return saturate( ToneColor );
//return max( 0, ToneColor );
}
...
GammaColor = FilmToneMap(LinearColor);




【运行】

 
7 几个配置都加进去
#elif USE_TONEMAPPERTYPE == 3
{
// Simple ACES
//float a = 2.51f;
    //float b = 0.03f;
    //float c = 2.43f;
    //float d = 0.59f;
    //float e = 0.14f;
    //return saturate((x*(a*x+b))/(x*(c*x+d)+e));
    GammaColor = saturate(( LinearColor * (2.51f * LinearColor + 0.03f )) / ( LinearColor * ( 2.43f * LinearColor + 0.59f ) + 0.14f ));  
}
#elif USE_TONEMAPPERTYPE == 4
{
// Uncharted settings
  FilmSlope = 0.63;
FilmToe = 0.55;
FilmShoulder = 0.47;
FilmBlackClip= 0;
FilmWhiteClip = 0.01;

GammaColor = FilmToneMap(LinearColor);
}
#elif USE_TONEMAPPERTYPE == 5
{
// HP settings
FilmSlope = 0.65;
FilmToe = 0.63;
FilmShoulder = 0.45;
FilmBlackClip = 0;
FilmWhiteClip = 0;

GammaColor = FilmToneMap(LinearColor);
}
#elif USE_TONEMAPPERTYPE == 6
{
// Legacy settings
FilmSlope = 0.98;
FilmToe = 0.3;
FilmShoulder = 0.22;
FilmBlackClip = 0;
FilmWhiteClip = 0.025;

GammaColor = FilmToneMap(LinearColor);
}
#elif USE_TONEMAPPERTYPE == 7
{
// ACES settings
FilmSlope = 0.88;
FilmToe = 0.55;
FilmShoulder = 0.26;
FilmBlackClip = 0;
FilmWhiteClip = 0.04;
FilmGammaColor = FilmToneMap(LinearColor);
}
#endif



 

FUberPostProcessSceneProxy.Render 

#define VARIATION1(A)        VARIATION2(A,0)            VARIATION2(A,1)            VARIATION2(A,2)    VARIATION2(A,3) VARIATION2(A,4) VARIATION2(A,5) VARIATION2(A,6) VARIATION2(A,7)



UberPostProcessEffect.cpp 

#define VARIATION1(A)        VARIATION2(A,0)            VARIATION2(A,1)            VARIATION2(A,2)        VARIATION2(A,3) VARIATION2(A,4) VARIATION2(A,5) VARIATION2(A,6) VARIATION2(A,7)


语法报错嵌套太深 感觉超过48层就报错了


只能采用传另外一个参数

ImageAdjustments3只用的第一个Float


9 FUberPostProcessSceneProxy 中添加属性

    UINT TonemapperACESType;


FUberPostProcessSceneProxy.FUberPostProcessSceneProxy 

    FUberPostProcessSceneProxy(const UUberPostProcessEffect* InEffect,const FPostProcessSettings* WorldSettings, UINT InColorGradingCVar, 
        UINT InTonemapperType,UINT InTonemapperACESType, UBOOL bInMotionBlur, UBOOL bInImageGrain,const FCameraInfo& CI)
        :    FDOFAndBloomPostProcessSceneProxy(InEffect, WorldSettings,CI)
        ,    MotionBlurSoftEdgeKernelSize(InEffect->MotionBlurSoftEdgeKernelSize)
        ,    TonemapperToeFactor(InEffect->TonemapperToeFactor)
        ,    TonemapperRange(InEffect->TonemapperRange)
        ,    ColorGradingCVar(InColorGradingCVar)
        ,    TonemapperType(InTonemapperType)
        ,    TonemapperACESType(InTonemapperACESType)



10 \ue3\Development\Src\Engine\Classes\UberPostProcessEffect.uc

var(Tonemapper) enum EACESType
{
    ACES_One<DisplayName=ACES>, 
    ACES_Simple<DisplayName=Simple>, 
    ACES_Uncharted<DisplayName=Uncharted>, 
    ACES_HP<DisplayName=HP>, 
    ACES_Legacy<DisplayName=Legacy>, 
} ACESType;


11 UUberPostProcessEffect.CreateSceneProxy

    return new FUberPostProcessSceneProxy(this, WorldSettings, GColorGrading, LocalTonemapperType,ACESType, bLocalMotionBlur, bEnableImageGrain,CI);


12 Shader赋值FUberPostProcessSceneProxy.RenderVariationFullRes.{.SetPixelShaderValue 

            SetPixelShaderValue(
                BlendPixelShader->GetPixelShader(), 
                BlendPixelShader->ImageAdjustments3Parameter,
                FVector4(Clamp(TonemapperToeFactor, 0.0f, 1.0f), TonemapperACESType, 0, 0));


13 \Engine\Shaders\UberPostProcessBlendPixelShader.usf

#elif USE_TONEMAPPERTYPE == 3
{

if (ACESType == 1)
{
// Simple ACES
//float a = 2.51f;
    //float b = 0.03f;
    //float c = 2.43f;
    //float d = 0.59f;
    //float e = 0.14f;
    //return saturate((x*(a*x+b))/(x*(c*x+d)+e));
    GammaColor = saturate(( LinearColor * (2.51f * LinearColor + 0.03f )) / ( LinearColor * ( 2.43f * LinearColor + 0.59f ) + 0.14f ));  
    }
    else
    {
    if (ACESType == 0)
    {
    // ACES settings
FilmSlope = 0.88;
FilmToe = 0.55;
FilmShoulder = 0.26;
FilmBlackClip = 0;
FilmWhiteClip = 0.04;
  }
  else if (ACESType == 2)
  {
  // Uncharted settings
FilmSlope = 0.63;
FilmToe = 0.55;
FilmShoulder = 0.47;
FilmBlackClip= 0;
FilmWhiteClip = 0.01;
  }
  else if (ACESType == 3)
  {
// HP settings
FilmSlope = 0.65;
FilmToe = 0.63;
FilmShoulder = 0.45;
FilmBlackClip = 0;
FilmWhiteClip = 0;
  }
  else if (ACESType == 4)
  {
  // Legacy settings
FilmSlope = 0.98;
FilmToe = 0.3;
FilmShoulder = 0.22;
FilmBlackClip = 0;
FilmWhiteClip = 0.025;
  }
  
  GammaColor = FilmToneMap(LinearColor);
    }
}
#endif



14 添加指令

UUberPostProcessEffect.CreateSceneProxy.

        CVar = GConsoleManager->FindConsoleVariable(TEXT("ACESType")); 
        Value = CVar->GetInt();
        if (Value >= ACES_One && Value < ACES_MAX)
        {
            LocalACESType = Value;
        }


CreateConsoleVariables 

    GConsoleManager->RegisterConsoleVariable(TEXT("ACESType"),
        -1,
        TEXT("Allows to override which ACESType function (during the post processing stage to transform HDR to LDR colors) is used:\n")
        TEXT("-1: use what is specified elsewhere (default)\n")
        TEXT(" 0: ACES settings \n")
        TEXT(" 1: Simple ACES \n")
        TEXT(" 2: Uncharted settings\n")
        TEXT(" 3: HP settings"),
        TEXT(" 4: Legacy settings"),
        ECVF_Cheat);


指令

TonemapperType 3
ACESType 0-4


" 0: ACES settings"

" 1: Simple ACES"

" 2: Uncharted settings"

" 3: HP settings"

" 4: Legacy settings"



15 



16






posted on 2018-04-19 09:21  维尔福  阅读(1721)  评论(0编辑  收藏  举报