UE4 ACES Tone mapping port URP.


For more information, please see my notion page.

The Unity version used for training is 2021.1.18, and it was written based on the URP 11 version.
用于训练的Unity版本为2021.1.18,基于URP 11版本编写。

We will try to port the ACES tone mapping used in UE4 to the Unity engine URP. Through this process, you will be able to flexibly add various tone mapping logic in the future. You must read it carefully.

我们将尝试将 UE4 中使用的 ACES 色调映射移植到 Unity 引擎 URP。 通过这个过程,你以后就可以灵活的添加各种色调映射逻辑了。 你必须仔细阅读。

We will add it in the same format as above. 我们将以与上述相同的格式添加它。

Adding tone mapping mode.

Add Enum type. 添加枚举类型。

Tonemapping.cs

public enum TonemappingMode
    {
        None,
        Neutral, // Neutral tonemapper
        ACES,    // ACES Filmic reference tonemapper (custom approximation)
        Custom,
        /////////////////UE4_ACES_BEGIN///////////////
        UE4_ACES,
       /////////////////UE4_ACES_END/////////////////
    }

Added UE4 ACES. 添加了 UE4 ACES。

One more mode has been added. 添加了另一种模式。

Now let’s add some options. 现在让我们添加一些选项。

Let’s add a variable to the Tone Mapping class. 让我们向色调映射类添加一个变量。

Tonemapping.cs

public sealed class Tonemapping : VolumeComponent, IPostProcessComponent
    {
        [Tooltip("Select a tonemapping algorithm to use for the color grading process.")]
        public TonemappingModeParameter mode = new TonemappingModeParameter(TonemappingMode.None);
        
        /////////////////UE4_ACES_BEGIN/////////////////
        //create floatParameter
        /// This is only used when <see cref="Tonemapper.UE4_ACES"/> is active.
        [Tooltip("Film_Slope")]
        public ClampedFloatParameter slope = new ClampedFloatParameter(0.88f, 0f, 1f);
        /// This is only used when <see cref="Tonemapper.UE4_ACES"/> is active.
        [Tooltip("Film_Toe")]
        public ClampedFloatParameter toe = new ClampedFloatParameter(0.55f, 0.0f, 1.0f);
        /// This is only used when <see cref="Tonemapper.UE4_ACES"/> is active.
        [Tooltip("Film_Shoulder")]
        public ClampedFloatParameter shoulder = new ClampedFloatParameter(0.26f, 0.0f, 1.0f);
        /// This is only used when <see cref="Tonemapper.UE4_ACES"/> is active.
        [Tooltip("Film_BlackClip")]
        public ClampedFloatParameter blackClip = new ClampedFloatParameter(0.0f, 0.0f, 1.0f);
        /// This is only used when <see cref="Tonemapper.UE4_ACES"/> is active.
        [Tooltip("Film_WhiteClip")]
        public ClampedFloatParameter whiteClip = new ClampedFloatParameter(0.04f, 0.0f, 1.0f);
        /////////////////UE4_ACES_END/////////////////        

        public bool IsActive() => mode.value != TonemappingMode.None;

        public bool IsTileCompatible() => true;
    }

Now, you need to pass the value obtained from the added variable to the editor.

现在,您需要将从添加的变量中获得的值传递给编辑器。

First, let’s add five SerializedDataParameters to the editor.

首先,让我们向编辑器添加五个 SerializedDataParameters。

TonemappingEditor.cs

/////////////////UE4_ACES_BEGIN/////////////////
SerializedDataParameter m_Slope;
SerializedDataParameter m_Toe;
SerializedDataParameter m_Shoulder;
SerializedDataParameter m_BlackClip;
SerializedDataParameter m_WhiteClip;
/////////////////UE4_ACES_END/////////////////

TonemappingEditor.cs

public override void OnEnable()
{
    var o = new PropertyFetcher<Tonemapping>(serializedObject);

    m_Mode = Unpack(o.Find(x => x.mode));
    /////////////////UE4_ACES_BEGIN/////////////////
    m_Slope = Unpack(o.Find(x => x.slope));
    m_Toe = Unpack(o.Find(x => x.toe));
    m_Shoulder = Unpack(o.Find(x => x.shoulder));
    m_BlackClip = Unpack(o.Find(x => x.blackClip));
    m_WhiteClip = Unpack(o.Find(x => x.whiteClip));
    /////////////////UE4_ACES_END///////////////////

}

Added Reset button UI.添加了重置按钮 UI。

TonemappingEditor.cs

In the OnInspectorGUI() function, add a Reset button using GUILayout.Button() API.

OnInspectorGUI() 函数中,使用 GUILayout.Button() API 添加重置按钮。

public override void OnInspectorGUI()
{
    PropertyField(m_Mode);
    /////////////////UE4_ACES_BEGIN/////////////////
    if ( m_Mode.value.intValue == (int)TonemappingMode.UE4_ACES)
    {
        UnityEngine.GUILayout.BeginVertical("box");
        UnityEngine.GUILayout.BeginHorizontal();

        PropertyField(m_Slope);
        if (UnityEngine.GUILayout.Button("Reset"))
        {
            m_Slope.value.floatValue = 0.88f;
        }
        UnityEngine.GUILayout.EndHorizontal();

        UnityEngine.GUILayout.BeginHorizontal();
        PropertyField(m_Toe);
        if (UnityEngine.GUILayout.Button("Reset"))
        {
            m_Toe.value.floatValue = 0.55f;
        }
        UnityEngine.GUILayout.EndHorizontal();

        UnityEngine.GUILayout.BeginHorizontal();
        PropertyField(m_Shoulder);
        if (UnityEngine.GUILayout.Button("Reset"))
        {
            m_Shoulder.value.floatValue = 0.26f;
        }
        UnityEngine.GUILayout.EndHorizontal();

        UnityEngine.GUILayout.BeginHorizontal();
        PropertyField(m_BlackClip);
        if (UnityEngine.GUILayout.Button("Reset"))
        {
            m_BlackClip.value.floatValue = 0.0f;
        }
        UnityEngine.GUILayout.EndHorizontal();

        UnityEngine.GUILayout.BeginHorizontal();
        PropertyField(m_WhiteClip);
        if (UnityEngine.GUILayout.Button("Reset"))
        {
            m_WhiteClip.value.floatValue = 0.04f;
        }
        UnityEngine.GUILayout.EndHorizontal();
        UnityEngine.GUILayout.EndVertical();

    }
    /////////////////UE4_ACES_END////////////////////

    // Display a warning if the user is trying to use a tonemap while rendering in LDR
    var asset = UniversalRenderPipeline.asset;
    if (asset != null && !asset.supportsHDR)
    {
        EditorGUILayout.HelpBox("Tonemapping should only be used when working in HDR.", MessageType.Warning);
        return;
    }
}

You can also import the UnityEngine namespace using Using . 您还可以使用 Using 导入 UnityEngine 命名空间。

If you click the Reset button, each serialized property will be fetched back to the initial value as the default value. 如果单击“重置”按钮,每个序列化的属性将被提取回初始值作为默认值。

good. The function does not work, but the UI is well equipped as intended.

好的。 该功能不起作用,但用户界面按预期配备齐全。

Properties and shader integration. 属性和着色器集成。

Let’s add the shader code written by referring to the UE4 engine’s ACES structure.

我们来添加参考UE4引擎的ACES结构编写的shader代码。

Packages/com.unity.render-pipelines.universal@11.0.0/ShaderLibrary/UE4_ACES.hlsl

Add it to the above path in the format of UE4_ACES.hlsl.

以 UE4_ACES.hlsl 格式添加到上述路径中。

#ifndef UNITY_UE4ACES_INCLUDED
#define UNITY_UE4ACES_INCLUDED

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ACES.hlsl"

float FilmSlope;// = 0.91;
float FilmToe;// = 0.53;
float FilmShoulder;// = 0.23;
float FilmBlackClip;// = 0;
float FilmWhiteClip;// = 0.035;

float3 UE4ACES(float3 aces)
{
	// "Glow" module constants
	const float RRT_GLOW_GAIN = 0.05;
	const float RRT_GLOW_MID = 0.08;

	float saturation = rgb_2_saturation(aces);
	float ycIn = rgb_2_yc(aces);
	float s = sigmoid_shaper((saturation - 0.4) / 0.2);
	float addedGlow = 1.0 + glow_fwd(ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID);
	aces *= addedGlow;

	const float RRT_RED_SCALE = 0.82;
	const float RRT_RED_PIVOT = 0.03;
	const float RRT_RED_HUE = 0.0;
	const float RRT_RED_WIDTH = 135.0;

	// --- Red modifier --- //
	float hue = rgb_2_hue(aces);
	float centeredHue = center_hue(hue, RRT_RED_HUE);
	float hueWeight;
	{
		hueWeight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH));
		hueWeight *= hueWeight;
	}
	//float hueWeight = Square( smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH)) );

	aces.r += hueWeight * saturation * (RRT_RED_PIVOT - aces.r) * (1.0 - RRT_RED_SCALE);

	// Use ACEScg primaries as working space
	float3 acescg = max(0.0, ACES_to_ACEScg(aces));

	// Pre desaturate
	acescg = lerp(dot(acescg, AP1_RGB2Y).xxx, acescg, 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(acescg);
	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 linearCV = lerp(ToeColor, ShoulderColor, t);

	// Post desaturate
	linearCV = lerp(dot(float3(linearCV), AP1_RGB2Y), linearCV, 0.93);

	// Returning positive AP1 values
	//return max(0, linearCV);

	// Convert to display primary encoding
	// Rendering space RGB to XYZ
	float3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);

	// Apply CAT from ACES white point to assumed observer adapted white point
	XYZ = mul(D60_2_D65_CAT, XYZ);

	// CIE XYZ to display primaries
	linearCV = mul(XYZ_2_REC709_MAT, XYZ);

	linearCV = saturate(linearCV); //Protection to make negative return out.

	return linearCV;

}
/////////////////SWS_UE4_ACES_END/////////////////

#endif

ACES code for unity3D from UE4

When implementing, it is often checked in the scene view, but it seems that saturation works internally in the scene view. 实现时,经常在场景视图中检查,但似乎饱和度在场景视图内部起作用。 linearCV = saturate(linearCV); If not added, a negative value by Black Clip will be returned in the game view. linearCV = saturate(linearCV); 如果未添加,则在游戏视图中将返回 Black Clip 的负值。

UE4 ACES tone mapping uses ODT_Rec709_100nits_dim by default.

UE4 ACES 色调映射默认使用 ODT_Rec709_100nits_dim

If we look only at the ODT_Rec709_100nits_dim( ) function of ACES used in Unity. 如果我们只看Unity中使用的ACES的ODT_Rec709_100nits_dim()函数。

//Packages/com.unity.render-pipelines.core@11.0.0/ShaderLibrary/ACES.hlsl
// CIE XYZ to display primaries
linearCV = mul(XYZ_2_REC709_MAT, XYZ);

// Handle out-of-gamut values
// Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
linearCV = saturate(linearCV);

ODT_Rec709_100nits_dim

The ACES code briefing will be omitted for now. I need a very long and lengthy explanation, so next time…
ACES 代码简介暂时将被省略。 我需要一个很长很长的解释,所以下次……

However, if you are interested, please see the link below.

但是,如果您有兴趣,请参阅下面的链接。

Personally, to fully understand this ACES, I will have to look at various topics related to it for almost half a year.
就个人而言,要完全理解这个ACES,我将不得不花近半年时间查看与之相关的各种主题。

Added Shader Keyword.添加了着色器关键字。

Packages/com.unity.render-pipelines.universal@11.0.0/Runtime/UniversalRenderPipelineCore.cs
public static class ShaderKeywordStrings
{
    ...
    public static readonly string TonemapACES = "_TONEMAP_ACES";
    public static readonly string TonemapNeutral = "_TONEMAP_NEUTRAL";
    /////////////////UE4_ACES_END/////////////////
    public static readonly string TonemapUE4ACES = "_TONEMAP_UE4ACES";
    /////////////////UE4_ACES_END/////////////////
...
}

public static readonly string TonemapUE4ACES = “_TONEMAP_UE4ACES”; 를 추가합니다.

Packages/com.unity.render-pipelines.universal@11.0.0/Runtime/Passes/PostProcessPass.cs

Packages/com.unity.render-pipelines.universal@11.0.0/Runtime/Passes/PostProcessPass.cs
void SetupColorGrading(CommandBuffer cmd, ref RenderingData renderingData, Material material)
{
    ref var postProcessingData = ref renderingData.postProcessingData;
    bool hdr = postProcessingData.gradingMode == ColorGradingMode.HighDynamicRange;
    int lutHeight = postProcessingData.lutSize;
    int lutWidth = lutHeight * lutHeight;

    // Source material setup
    float postExposureLinear = Mathf.Pow(2f, m_ColorAdjustments.postExposure.value);
    cmd.SetGlobalTexture(ShaderConstants._InternalLut, m_InternalLut.Identifier());
    material.SetVector(ShaderConstants._Lut_Params, new Vector4(1f / lutWidth, 1f / lutHeight, lutHeight - 1f, postExposureLinear));
    material.SetTexture(ShaderConstants._UserLut, m_ColorLookup.texture.value);
    material.SetVector(ShaderConstants._UserLut_Params, !m_ColorLookup.IsActive()
        ? Vector4.zero
        : new Vector4(1f / m_ColorLookup.texture.value.width,
            1f / m_ColorLookup.texture.value.height,
            m_ColorLookup.texture.value.height - 1f,
            m_ColorLookup.contribution.value)
    );

    if (hdr)
    {
        material.EnableKeyword(ShaderKeywordStrings.HDRGrading);
    }
    else
    {
        switch (m_Tonemapping.mode.value)
        {
            case TonemappingMode.Neutral: material.EnableKeyword(ShaderKeywordStrings.TonemapNeutral); break;
            case TonemappingMode.ACES: material.EnableKeyword(ShaderKeywordStrings.TonemapACES); break;
            /////////////////UE4_ACES_BEGIN/////////////////
            case TonemappingMode.UE4_ACES: material.EnableKeyword(ShaderKeywordStrings.TonemapUE4ACES); break;
            default: break; // None
        }
        //////LDR Bind UI
        material.SetFloat("FilmSlope", (float)m_Tonemapping.slope);
        material.SetFloat("FilmToe", (float)m_Tonemapping.toe);
        material.SetFloat("FilmShoulder", (float)m_Tonemapping.shoulder);
        material.SetFloat("FilmBlackClip", (float)m_Tonemapping.blackClip);
        material.SetFloat("FilmWhiteClip", (float)m_Tonemapping.whiteClip);
    }
}

case TonemappingMode.UE4_ACES: material.EnableKeyword(ShaderKeywordStrings.TonemapUE4ACES); break;

and set the arguments to send to the shader. 并设置要发送到着色器的参数。

material.SetFloat("FilmSlope", (float)m_Tonemapping.slope);
 material.SetFloat("FilmToe", (float)m_Tonemapping.toe);
 material.SetFloat("FilmShoulder", (float)m_Tonemapping.shoulder);
 material.SetFloat("FilmBlackClip", (float)m_Tonemapping.blackClip);
 material.SetFloat("FilmWhiteClip", (float)m_Tonemapping.whiteClip);

Tone mapping applied. 应用了色调映射。

`Packages/src/com.unity.render-pipelines.universal@7.2.1/Shaders/PostProcessing/Common.hlsl`

#ifndef UNIVERSAL_POSTPROCESSING_COMMON_INCLUDED
#define UNIVERSAL_POSTPROCESSING_COMMON_INCLUDED

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/Utils/Fullscreen.hlsl"
/////////////////UE4_ACES_BEGIN///////////////
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UE4_ACES.hlsl"
/////////////////UE4_ACES_END/////////////////

// ----------------------------------------------------------------------------------
// Render fullscreen mesh by using a matrix set directly by the pipeline instead of
// relying on the matrix set by the C++ engine to avoid issues with XR

Added multi-compilation keyword to UberPost.

为 UberPost 添加了多编译关键字。

Assets/BundleRes/Shader/PostEffect/UberPost.shader

Shader "Hidden/Universal Render Pipeline/UberPost"
{
Properties
{
    [HideInInspector] _StencilRef("_StencilRef", Int) = 0
}
HLSLINCLUDE
    
#pragma multi_compile_local _ _DISTORTION
#pragma multi_compile_local _ _CHROMATIC_ABERRATION
#pragma multi_compile_local _ _BLOOM_LQ _BLOOM_HQ _BLOOM_LQ_DIRT _BLOOM_HQ_DIRT
#pragma multi_compile_local _ _HDR_GRADING _TONEMAP_ACES _TONEMAP_NEUTRAL _TONEMAP_UE4ACES /////////////////UE4_ACES_BEGIN/////////////////
#pragma multi_compile_local _ _FILM_GRAIN
#pragma multi_compile_local _ _DITHERING
#pragma multi_compile_local _ _LINEAR_TO_SRGB_CONVERSION

Add operation to lut builder. 向 lut builder 添加操作。

Assets/BundleRes/Shader/PostEffect/LutBuilderHdr.shader

// Note: when the ACES tonemapper is selected the grading steps will be done using ACES spaces
float3 ColorGrade(float3 colorLutSpace)
{
    // Switch back to linear
    float3 colorLinear = LogCToLinear(colorLutSpace);

    // White balance in LMS space
    float3 colorLMS = LinearToLMS(colorLinear);
    colorLMS *= _ColorBalance.xyz;
    colorLinear = LMSToLinear(colorLMS);

    // Do contrast in log after white balance
    /////////////////UE4_ACES_BEGIN///////////////
    #if _TONEMAP_ACES || _TONEMAP_UE4ACES
    float3 colorLog = ACES_to_ACEScc(unity_to_ACES(colorLinear));
    /////////////////UE4_ACES_END///////////////
    #else
    float3 colorLog = LinearToLogC(colorLinear);
    #endif

    colorLog = (colorLog - ACEScc_MIDGRAY) * _HueSatCon.z + ACEScc_MIDGRAY;

    /////////////////UE4_ACES_BEGIN///////////////
    #if _TONEMAP_ACES || _TONEMAP_UE4ACES
    colorLinear = ACES_to_ACEScg(ACEScc_to_ACES(colorLog));
    /////////////////UE4_ACES_END///////////////
    #else
    colorLinear = LogCToLinear(colorLog);
    #endif

    // Color filter is just an unclipped multiplier
    colorLinear *= _ColorFilter.xyz;

    // Do NOT feed negative values to the following color ops
    colorLinear = max(0.0, colorLinear);
...
)

Add the _TONEMAP_UE4ACES preprocessing keyword to ColorGrade() .

_TONEMAP_UE4ACES 预处理关键字添加到 ColorGrade()

float3 Tonemap(float3 colorLinear)
{
    #if _TONEMAP_NEUTRAL
    {
        colorLinear = NeutralTonemap(colorLinear);
    }
    #elif _TONEMAP_ACES
    {
        // Note: input is actually ACEScg (AP1 w/ linear encoding)
        float3 aces = ACEScg_to_ACES(colorLinear);
        colorLinear = AcesTonemap(aces);
    }
    /////////////////UE4_ACES_BEGIN/////////////////
    #elif _TONEMAP_UE4ACES
    {
        float3 aces = ACEScg_to_ACES(colorLinear);
        colorLinear = UE4ACES(aces);
    }
		/////////////////UE4_ACES_END/////////////////
    #endif

    return colorLinear;
}

Packages/com.unity.render-pipelines.universal@11.0.0/Runtime/Passes/ColorGradingLutPass.cs

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
    var cmd = CommandBufferPool.Get();
    using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.ColorGradingLUT)))
    {
        ...

        // Secondary curves
        material.SetTexture(ShaderConstants._CurveHueVsHue, curves.hueVsHue.value.GetTexture());
        material.SetTexture(ShaderConstants._CurveHueVsSat, curves.hueVsSat.value.GetTexture());
        material.SetTexture(ShaderConstants._CurveLumVsSat, curves.lumVsSat.value.GetTexture());
        material.SetTexture(ShaderConstants._CurveSatVsSat, curves.satVsSat.value.GetTexture());
        
        /////////////////UE4_ACES_BEGIN/////////////////
        //HDR Bind UI
        material.SetFloat("FilmSlope", (float)tonemapping.slope);
        material.SetFloat("FilmToe", (float)tonemapping.toe);
        material.SetFloat("FilmShoulder", (float)tonemapping.shoulder);
        material.SetFloat("FilmBlackClip", (float)tonemapping.blackClip);
        material.SetFloat("FilmWhiteClip", (float)tonemapping.whiteClip);
        /////////////////UE4_ACES_END/////////////////

        // Tonemapping (baked into the lut for HDR)
        if (hdr)
        {
            material.shaderKeywords = null;

            switch (tonemapping.mode.value)
            {
                case TonemappingMode.Neutral: material.EnableKeyword(ShaderKeywordStrings.TonemapNeutral); break;
                case TonemappingMode.ACES: material.EnableKeyword(ShaderKeywordStrings.TonemapACES); break;
                /////////////////UE4_ACES_BEGIN/////////////////
                case TonemappingMode.UE4_ACES: material.EnableKeyword(ShaderKeywordStrings.TonemapUE4ACES); break;
                /////////////////UE4_ACES_END/////////////////
                default: break; // None
            }
        }
...
}

Add UE4 Aces argument to ColorGradingLutPass.cs and add keywords.

将 UE4 Aces 参数添加到 ColorGradingLutPass.cs 并添加关键字。

Wanna be to access git project?

Try to become my Patreon.

댓글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중