TECHARTNOMAD | TECHARTFLOWIO.COM

TECH.ART.FLOW.IO

[번역]Unity-URP 자체 톤매핑(UE4 포팅) 및 기타 포스트 프로세싱 함수 수정하기

jplee 2024. 2. 15. 23:04

역자의 말.

종종 위쳇 마이크로 블로그에 글을 올리는 중국인 엔지니어 포스팅을 보다가... 원래의 내 글을 참조 해서 구현하는 기사를 봤습니다. 개인적으로는 굳이 저렇게? 라고 생각이 들지만 렌더피처 형식으로 구현하고자 할 경우 이 기사를 참조 하는 것도 좋을 듯 해서 번역 해 봤습니다.

원본 글.

 

修改Unity-URP自带的Tonemapping(移植UE4)以及其它后处理功能记录

事先声明一下作者是非码农出身的在读研三狗,所以对代码的理解可能存在误解,还请见谅。 视频教程b站链接如下,可以去git直接下载脚本: 【教程】修改Unity-URP自带的Tonemapping(移植UE4)以

zhuanlan.zhihu.com

 

중국인 저자가 참고 한 원래 내 포스팅.

 

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…

leegoonz.blog


필자는 이번 연구에 있어서 컴공 전공을 한 코더가 아니기 때문에 코드에 대한 오해가 있을 수 있으니 용서해 주시기 바랍니다.

최근 렌더링 한

동영상 튜토리얼 b사이트 링크는 아래에 있으며, git으로 이동하여 스크립트를 직접 다운로드할 수 있습니다:

 

【教程】修改Unity-URP自带的Tonemapping(移植UE4)以及其它后处理功能_哔哩哔哩_bilibili

一直觉得Unity自带的Tonemapping效果太少而且不好用,特别是自带的ACES效果一般般,也不像HDRP一样有很多可调的参数,于是一直想把UE4中的Tonemapping效果移植到Unity中,这次在查阅各种大佬的资料下

www.bilibili.com

선견지명:
톤 매핑은 하이 다이나믹 HDR 색상을 로우 다이나믹 LDR 색 영역에 매핑하는 일반적인 후처리 효과로, 모든 장치에서 사진이 제대로 표시되도록 하고 사진의 밝고 어두운 디테일을 높여 일반적으로 영화에서 더 흔하게 발생하는 과다 노출을 방지합니다. 초보자는 톤 매핑, 감마 보정 및 기타 개념을 구분하지 못할 수 있으므로 다음 블로거의 글을 참조할 수 있으며 여기서는 반복하지 않겠습니다.

 

【图形学基础拾遗】系统性理解颜色空间、HDR与Tonemapping

0x00 前言本文是笔者在解决一些关于色调映射(Tonemapping)的坑之后完成的复盘,旨在帮助有遇到同样问题的同学或是小白系统性地梳理相关知识。本文主要涉及的内容有: 相关概念复习Gamma空间

zhuanlan.zhihu.com

UE4의 ACES 매핑 체계 포팅 (초기 버전)
실험용 버전은 Unity2021.3.24f1-URP이며, 저는 항상 Unity에는 톤 매핑 효과가 너무 적고 사용하기에 좋지 않다고 느꼈고, 특히 조정 가능한 파라미터가 많은 HDRP와 달리 일반적으로 ACES 효과가 함께 제공되므로 항상 UE4에 이식 된 톤 매핑 효과에서 UE4를 원했기 때문에 정보를 확인하기 시작했고 누군가 먼저 해법을 만든것을 발견했으며 각 다른 톤 매핑 체계의 홈 페이지에서 비디오를 볼 수 있습니다 (초기 버전). 그래서 UE4에서 Unity로 톤 매핑 효과를 포팅하려고 시도하고 있었기 때문에 정보를 확인하기 시작했고, 누군가 이미 비슷한 처리를 한 것을 발견했으며, 홈페이지의 비디오에서 다른 톤 매핑 체계의 차이점을 볼 수 있습니다:

 

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…

leegoonz.blog

이 방법은 소스 코드 수정이 많이 필요할 뿐만 아니라 레퍼런스도 수정해야 하고, 프로젝트를 업데이트할 때마다 다시 처리해야 하는 번거로움이 있어서 자체적인 후처리로 문제를 해결하고자 합니다. 첫 번째 아이디어는 화면의 픽셀을 톤 매핑하는 별도의 렌더 피처를 추가한 다음 셰이더에 자신만의 수정된 톤 매핑 함수를 추가하여 위에서 언급한 방법을 통해 픽셀을 매핑하는 것입니다.

유니티와 함께 제공되는 ACES_Tonemapping 메서드를 얻으려면 패키지에서 Color.hlsl 파일을 검색한 다음 열어 내부에서 AcesTonemap 메서드를 찾을 수 있으며, 여기에 입력된 메서드는 float3 aces이므로 이 메서드를 사용할 때는 셰이더의 선형 공간 색상을 먼저 에이스 색 공간으로 변환한 다음 셰이더에 자체 수정 톤 매핑 함수를 추가해야 한다는 점에 유의하세요. 셰이더의 선형 공간 색상을 에이스 색 공간으로 변환한 다음 에이스 색 공간에 매핑해야 합니다. 여기서는 새 셰이더를 생성하고 위에서 언급한 방법을 참조하여 수정할 수 있도록 Unity와 함께 제공되는 매핑 함수를 복사한 다음 이후 섹션에서 호출하는 데 사용합니다.

Unity와 함께 제공되는 ACES 매핑
포스트 프로세싱 셰이더의 핵심 파트
ACES.hlsl과 함께 제공되는 변환 메서드

Shader "JJ/UE4_Tonemapping"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    	
        }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
			#include "Packages/com.unity.shadergraph/ShaderGraphLibrary/ShaderVariablesFunctions.hlsl"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            	float3 posWorld : TEXCOORD1;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _CameraOpaqueTexture; //相机不透明物体的渲染完成的采样
            sampler2D _CameraDepthTexture;

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

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
            	o.posWorld = TransformObjectToWorld(v.vertex);
                // UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

			static const float e = 2.71828;
            
            
            float3 AcesTonemap_UE4(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/////////////////
			///
			///
			float3 AcesTonemap_Unity(float3 aces)
			{
				#if TONEMAPPING_USE_FULL_ACES

				    float3 oces = RRT(aces);
				    float3 odt = ODT_RGBmonitor_100nits_dim(oces);
				    return odt;

				#else

				    // --- Glow module --- //
				    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;

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

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

				    // --- ACES to RGB rendering space --- //
				    float3 acescg = max(0.0, ACES_to_ACEScg(aces));

				    // --- Global desaturation --- //
				    //acescg = mul(RRT_SAT_MAT, acescg);
				    acescg = lerp(dot(acescg, AP1_RGB2Y).xxx, acescg, RRT_SAT_FACTOR.xxx);

				    // Luminance fitting of *RRT.a1.0.3 + ODT.Academy.RGBmonitor_100nits_dim.a1.0.3*.
				    // https://github.com/colour-science/colour-unity/blob/master/Assets/Colour/Notebooks/CIECAM02_Unity.ipynb
				    // RMSE: 0.0012846272106
				#if defined(SHADER_API_SWITCH) // Fix floating point overflow on extremely large values.
				    const float a = 2.785085 * 0.01;
				    const float b = 0.107772 * 0.01;
				    const float c = 2.936045 * 0.01;
				    const float d = 0.887122 * 0.01;
				    const float e = 0.806889 * 0.01;
				    float3 x = acescg;
				    float3 rgbPost = ((a * x + b)) / ((c * x + d) + e/(x + FLT_MIN));
				#else
				    const float a = 2.785085;
				    const float b = 0.107772;
				    const float c = 2.936045;
				    const float d = 0.887122;
				    const float e = 0.806889;
				    float3 x = acescg;
				    float3 rgbPost = (x * (a * x + b)) / (x * (c * x + d) + e);
				#endif

				    // Scale luminance to linear code value
				    // float3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);

				    // Apply gamma adjustment to compensate for dim surround
				    float3 linearCV = darkSurround_to_dimSurround(rgbPost);

				    // Apply desaturation to compensate for luminance difference
				    //linearCV = mul(ODT_SAT_MAT, color);
				    linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);

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

				    return linearCV;

				#endif
			}
            

            half4 frag (v2f i) : SV_Target
            {
                half4 col = tex2D(_MainTex, i.uv);
            	// col = pow(col,2.2);
            	float3 clo_aces = unity_to_ACES(col.xyz);  //转到aces空间
            	
                half3 ACE_Col = AcesTonemap_UE4(clo_aces);   //在aces空间进行映射
				half3 finalCol = ACE_Col;
				col = float4(finalCol,1);
            	

                return col;
            }
            ENDHLSL
        }
    }
}

이 셰이더에는 노출할 수 있는 여러 파라미터가 있으며, 포스트 프로세싱에서 직접 조정할 수 있으며, 여기서는 볼륨 컨트롤 패널에서 노출하고 있으며, URP 포스트 프로세싱 튜토리얼을 찾아서 직접 만들 수 있습니다. 커스텀 톤 매핑을 켠 상태와 켜지 않은 상태, 그리고 언티 커스텀 톤 매핑 효과는 다음과 같으며, UE4 매핑 커브 대비가 더 제어 가능하며 유니티의 자체 톤 매핑 체계 대비만큼 크지는 않지만 채도가 약간 더 높다는 것을 알 수 있습니다.

커스텀 UE4 톤매핑 포스트 프로세싱 활성화
커스텀 UE4톤매핑 포스트 프로세싱이 켜지지 않았습니다.
유니티의 자체 톤 매핑 포스트 프로세싱 켜기

포스트 프로세싱 효과의 실행 순서 문제
이 단계에서 효과가 있지만 치명적인 문제가 있습니다. 즉, 블룸 효과가 영향을받습니다. 톤 매핑 후에는 HDR 색상이 LDR로 축소되므로 원래의 라이트 오버플로 부분이 더 이상 유효하지 않기 때문입니다. 유니티와 함께 제공되는 포스트 프로세싱에는 실행 순서가 고정되어 있으며 URP의 소스 코드를 확인하면 블룸 효과가 고정되어 있음을 알 수 있습니다. 는 톤 매핑 이전이므로 이러한 문제가 없습니다.

소스 코드 파일의 위치입니다:

Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/UberPost.shader

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

소스 코드에서 톤 매핑은 ColorGradiant 메서드로 구현됩니다.
UberPost에서 시스템의 기본 후처리 순서는 블룸-비네트-컬러그라디언트입니다.

내 포스트 프로세싱이 시스템 포스트 프로세싱 전에 실행되도록 작성되기 전에 프레임 디버거를 확인하면 시스템 기본 포스트 프로세싱이 이 패스 전체에서 직접 실행되고 내부 포스트 프로세싱 순서가 고정되어 있으며, 우리는 우리 자신의 포스트 프로세싱 순서를 포스트 프로세싱 전후에 전체 시스템 포스트 프로세싱 패스에서만 정의할 수 있음을 알 수 있습니다. 따라서 커스텀 톤 매핑이 시스템 포스트 프로세싱 전에 실행되면 블룸 효과가 방해받는다는 문제가 있습니다.

이 매핑 효과를 시스템 포스트 프로세싱 후에 넣고 싶지만, 이 커스텀 포스트 프로세싱이 아무런 효과가 없는 상태에서 정보를 수집하기 위해 렌더 포스트 프로세싱 효과가 궁극적으로 백버퍼에 직접 그려지는 것을 발견했습니다. 그래서 우리는 우리 자신의 렌더 기능을 추가 기본 포스트 프로세싱 전에 시스템에만있을 수 있습니다. 따라서 시스템 기본 포스트 프로세싱 전에만 자체 렌더 피처를 추가할 수 있으므로 사용자 정의 포스트 프로세싱과 URP 자체 포스트 프로세싱의 실행 순서를 제어할 수 없다는 문제가 발생합니다. (물론 제가 아직 충분한 정보를 수집하지 못했을 수도 있고, 커스텀 패스를 백버퍼로 읽어 백버퍼에 출력하는 것이 가능할 수도 있겠죠?).

시스템 포스트 프로세싱 전에 사용자 지정 톤 매핑 포스트 프로세싱이 수행됩니다.
시스템 후처리 후 실행, 패스를 실행해도 화면 출력은 변경되지 않았습니다.

물론 어리석은 해결책도 있습니다. 즉, 커스텀 톤 매핑 앞에 또는 커스텀 톤 매핑 셰이더에 직접 배치 된 커스텀 블룸 효과를 작성하므로 블룸과 톤 매핑 사이의 문제를 해결할 수는 있지만 해결할 수 없고 다른 시스템에서 후 처리가 함께 제공됩니다.
블룸과 톤매핑 사이의 문제는 해결할 수 있지만 다른 시스템 포스트 프로세서와의 순서 문제를 해결할 수 없고, 새로운 렌더 기능을 열 때마다 추가 소모가 늘어나므로 이번에는 다루지 않겠습니다. (물론 Unity와 같은 포스트 프로세싱 시스템을 커스터마이징하고 다양한 매크로를 사용하여 처리하는 방법을 배울 수도 있지만 워크로드가 매우 무거워집니다).

UE4 포팅을 위한 ACES 매핑 방식(최적화 버전)
이전 질문에 대한 답변으로, 추가 패스를 추가하지 않고 자체 포스트 프로세싱과 원본 포스트 프로세싱 간의 실행 순서를 커스터마이징하려면 결국 유니티 소스 코드를 수정하는 UberPost를 수정할 수밖에 없는데, 다행히 시스템의 포스트 프로세싱 소스 코드를 수정하는 방법에 대한 기사를 발견했습니다. 이 기사는 매우 전문적이고 상세하며 주의 깊게 읽으면 후처리에 대한 대부분의 의문을 해결할 수 있습니다.

 

URP自定义后处理

本文介绍如何使用URP来添加自定义的后处理效果。如果对URP整体概念比较陌生的话,可以翻阅 从零开始的SRP 这里使用了一个故障效果作为例子进行演示 添加入口 首先要做的第一步便是让URP识别

zhuanlan.zhihu.com

이제 위에서 설명한 내용을 바탕으로 포스트 프로세싱 순서를 커스터마이징할 수 있습니다. 먼저 Create-Rendering-URP postprocessingData를 마우스 오른쪽 버튼으로 클릭하여 새 사용자 지정 포스트 프로세싱 파일을 만든 다음 인스펙터 모드를 디버그로 조정하고(일반에서는 프로퍼티가 표시되지 않음), UberPost.shader의 복사본을 만들고 이름을 변경하고, 복사한 UberPost를 UberPost.shader를 복사하고 이름을 변경한 다음 복사한 UberPost를 "Uber Post PS"로 드래그 앤 드롭합니다. 아래 그림에서 볼 수 있듯이.

사용된 UberPost 파일 수정

그런 다음 다음과 같이 파이프라인 렌더링 설정 파일의 "PostProcessData"에 이 PostProcessData 파일을 끌어다 놓습니다.

파이프라인의 후처리 설정 변경하기

이제 엔진은 방금 복사한 UberPost를 후처리에 사용하므로 복사한 셰이더의 출력을 흰색으로 변경하여 테스트할 수 있으며, 화면이 흰색이면 교체에 성공했다는 의미입니다.

사용자 지정 UberPost 출력 변경
수정 성공

다음 단계는 톤 매핑에 대한 부분을 수정하는 것입니다. 이 부분은 앞서 언급 한 셰이더 메서드의 다음 부분에서 실행되며이 메서드는 참조 Common.hlsl 파일에 있으며이 메서드를 수정하는 방법에는 두 가지가 있습니다. 첫 번째는 소스 코드의 복사본을 복사 한 다음 처리 할 이름과 셰이더의 참조를 변경하는 것이고 다른 하나는 개별 방법을 사용자 정의 셰이더에 복사하거나 새 hlsl을 생성하는 것입니다. 다른 하나는 개별 메서드를 커스텀 셰이더에 복사하거나 직접 새 hlsl을 만드는 것입니다. 다른 셰이더에서 재사용하려면 방법 1이 가장 좋지만 여기서는 시간을 절약하기 위해 방법 2를 사용하겠습니다.

톤 매핑은 다음과 같은 방법으로 수행됩니다.

새 hlsl 파일을 만든 다음 공통 파일에서 이 메서드를 새 hlsl 파일에 복사하고 이름을 바꿉니다. 그런 다음 이 메서드에서 톤 매핑 메서드를 찾아 셰이더에 복사하고 이름을 바꿉니다. 그런 다음 UE4에서 톤 매핑 메서드를 복사하고 패스 파라미터를 변경합니다.

수정된 사용자 지정 후처리 hlsl 파일

이전 복사본에서 사용자 정의 포스트 프로세싱 셰이더에 매크로와 참조를 추가하고 참조 경로는 직접 정의한 hlsl 파일의 위치로 지정합니다. 그런 다음 셰이더에서 자체 커스텀 메서드를 호출할 수 있습니다. 전체 hlsl 파일 코드는 다음과 같습니다.

셰이더에 매크로 및 hlsl 레퍼런스 추가하기
방금 사용한 사용자 정의 메서드로 메서드를 수정합니다.

#ifndef JJ_POSTPROCESSING_INCLUDED
#define JJ_POSTPROCESSING_INCLUDED

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Filtering.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/Common.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Debug/DebuggingFullscreen.hlsl"

float _FilmSlope;// = 0.91;
float _FilmToe;// = 0.53;
float _FilmShoulder;// = 0.23;
float _FilmBlackClip;// = 0;
float _FilmWhiteClip;// = 0.035;


//UE4的色调映射方法
	float3 AcesTonemap_UE4(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/////////////////


//自定义的Tonemapping后处理方法....................................................................
half3 JJ_ApplyTonemap(half3 input)
{
#if _TONEMAP_ACES
    float3 aces = unity_to_ACES(input);
    input = AcesTonemap(aces);
#elif _TONEMAP_NEUTRAL
    input = NeutralTonemap(input);
#elif  _TONEMAP_ACES_UE4
    float3 aces = unity_to_ACES(input);
    input = AcesTonemap_UE4(aces);   //增加修改
#endif

    return saturate(input);
}

//自定义的后处理方法....................................................................
half3 JJ_ApplyColorGrading(half3 input, float postExposure, TEXTURE2D_PARAM(lutTex, lutSampler), float3 lutParams, TEXTURE2D_PARAM(userLutTex, userLutSampler), float3 userLutParams, float userLutContrib)
{
    // Artist request to fine tune exposure in post without affecting bloom, dof etc
    input *= postExposure;

    // HDR Grading:
    //   - Apply internal LogC LUT
    //   - (optional) Clamp result & apply user LUT
    #if _HDR_GRADING
    {
        float3 inputLutSpace = saturate(LinearToLogC(input)); // LUT space is in LogC
        input = ApplyLut2D(TEXTURE2D_ARGS(lutTex, lutSampler), inputLutSpace, lutParams);

        UNITY_BRANCH
        if (userLutContrib > 0.0)
        {
            input = saturate(input);
            input.rgb = GetLinearToSRGB(input.rgb); // In LDR do the lookup in sRGB for the user LUT
            half3 outLut = ApplyLut2D(TEXTURE2D_ARGS(userLutTex, userLutSampler), input, userLutParams);
            input = lerp(input, outLut, userLutContrib);
            input.rgb = GetSRGBToLinear(input.rgb);
        }
    }

    // LDR Grading:
    //   - Apply tonemapping (result is clamped)
    //   - (optional) Apply user LUT
    //   - Apply internal linear LUT
    #else
    {
        input = JJ_ApplyTonemap(input);

        UNITY_BRANCH
        if (userLutContrib > 0.0)
        {
            input.rgb = GetLinearToSRGB(input.rgb); // In LDR do the lookup in sRGB for the user LUT
            half3 outLut = ApplyLut2D(TEXTURE2D_ARGS(userLutTex, userLutSampler), input, userLutParams);
            input = lerp(input, outLut, userLutContrib);
            input.rgb = GetSRGBToLinear(input.rgb);
        }

        input = ApplyLut2D(TEXTURE2D_ARGS(lutTex, lutSampler), input, lutParams);
    }
    #endif

    return input;
}
#endif // JJ_POSTPROCESSING_INCLUDED

이제 셰이더가 변경되었으므로 다음으로 할 일은 일반 후 처리 방법에 따라 직접 후 처리의 매개 변수를 전달하여 줄을 변경하고 여기에서 이전 후 처리 스크립트를 변경하고 코드를 직접 살펴 보는 것입니다.

첫 번째는 렌더 기능을 추가하는 것입니다. 여기서는 이미지 처리를 수행하지 않고 매개 변수와 매크로 키워드를 전달하기 만하면됩니다.

using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class UETonemappingPassFeature : ScriptableRendererFeature
{
    UETonemappingPass m_ScriptablePass;
    public override void Create()
    {
        // m_ScriptablePass = new CustomRenderPass(settings.material);
        m_ScriptablePass = new UETonemappingPass(RenderPassEvent.BeforeRenderingPostProcessing);
    }
    
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        // m_ScriptablePass.source = renderer.cameraColorTarget;
        renderer.EnqueuePass(m_ScriptablePass);
    }
    
    class UETonemappingPass : ScriptableRenderPass
    {
        // 用于Shader中使用的Uniform变量
        static class ShaderIDs
        {
            internal static readonly int FilmSlope = Shader.PropertyToID("_FilmSlope");
            internal static readonly int FilmToe = Shader.PropertyToID("_FilmToe");
            internal static readonly int FilmShoulder = Shader.PropertyToID("_FilmShoulder");
            internal static readonly int FilmBlackClip = Shader.PropertyToID("_FilmBlackClip");
            internal static readonly int FilmWhiteClip = Shader.PropertyToID("_FilmWhiteClip");
        }
        
        // 用于FrameDebugger或其他Profiler中显示的名字
        private const string m_ProfilerTag = "UETonemapping Block";
        // 后处理配置类
        private UETonemappingSettings volumeStack;
        
        public UETonemappingPass(RenderPassEvent evt)
        {
            renderPassEvent = evt;
        }
        
        //pass的内容
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            // 获取后处理配置
            var volumeStack = VolumeManager.instance.stack;  //获取volumn堆栈
            var UE4_TonemappingData = volumeStack.GetComponent<UETonemappingSettings>();

            CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);
            // 当效果激活时才执行逻辑
            bool active = UE4_TonemappingData.IsActive();
            if (active)
            {
                cmd.SetGlobalFloat(ShaderIDs.FilmSlope, UE4_TonemappingData.FilmSlope.value);
                cmd.SetGlobalFloat(ShaderIDs.FilmToe, UE4_TonemappingData.FilmToe.value);
                cmd.SetGlobalFloat(ShaderIDs.FilmShoulder, UE4_TonemappingData.FilmShoulder.value);
                cmd.SetGlobalFloat(ShaderIDs.FilmBlackClip, UE4_TonemappingData.FilmBlackClip.value);
                cmd.SetGlobalFloat(ShaderIDs.FilmWhiteClip, UE4_TonemappingData.FilmWhiteClip.value);

                // 开启故障宏
                cmd.EnableShaderKeyword("_TONEMAP_ACES_UE4");
            }
            else
            {
                // 关闭故障宏
                cmd.DisableShaderKeyword("_TONEMAP_ACES_UE4");
            }
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
      

       
    }
}

그런 다음 볼륨 추가를 위한 매개변수 패널 섹션이 있습니다.

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

[System.Serializable, VolumeComponentMenuForRenderPipeline("JJ/UETonemapping",typeof(UniversalRenderPipeline))]
public class UETonemappingSettings : VolumeComponent, IPostProcessComponent
{
    [Tooltip("UE 的Tonemapping ACES")]
    public BoolParameter enable = new BoolParameter(true);
    public ClampedFloatParameter FilmSlope = new ClampedFloatParameter(0.88f, 0, 1, false);
    public ClampedFloatParameter FilmToe = new ClampedFloatParameter(0.55f, 0, 1, false);
    public ClampedFloatParameter FilmShoulder = new ClampedFloatParameter(0.26f, 0, 1, false);
    public ClampedFloatParameter FilmBlackClip = new ClampedFloatParameter(0f, 0, 1, false);
    public ClampedFloatParameter FilmWhiteClip = new ClampedFloatParameter(0.035f, 0, 1, false);
    
    
    //接口的实现
    public bool IsActive()
    {
        return enable.value && active;
        // return (DistortionStrength.value > 0.0f) && active;
    }

    public bool IsTileCompatible()
    {
        return false;
    }
}

테스트를 위해 프레임 디버거를 열면 포스트 프로세싱에서 구성 요소를 활성화하면 이미 키워드가 나타나고 사용자 지정 포스트 프로세싱이 정상적으로 실행될 수 있으며 블룸을 열면 정상적인 후광 효과가 있음을 알 수 있습니다.

블룸 효과가 작동 중입니다.

요약:
이 프로세스에서는 기존 커스터마이징에 비해 더 많은 시간을 절약하고 수정해야 하는 양을 줄일 수 있는 Unity의 커스텀 포스트 프로세싱 모듈에서 커스텀 효과를 추가하고 수정하는 방법을 시도해 보았습니다. 이후에도 오류, 왜곡 및 기타 효과와 같이 추가할 유사한 효과가 있는 경우 커스텀 UberPost에서 위의 방법을 계속 따라 추가를 구성할 수 있습니다.

또한 이번에는 UE4 톤 매핑 효과도 추가하려고 시도했는데, UnityURP와 함께 제공되는 효과에 비해 UE4의 톤 매핑 조정 가능한 범위가 더 많고 대비가 작고 밝은 부분이 더 잘 보이지만 사람들의 지혜를 볼 수있는 자비로운 것의 효과는 더 좋습니다. 또한 인터넷의 모든 사람들에게 이타적인 공유에 대해 감사 드리며, 저에게는 정말 큰 이득입니다 ~!

자, 다 끝났습니다! 모두 건배!

역자의 후기.

직접 구현을 해서 검증한 기사가 아닙니다. 참조 해서 구현 해 보셔도 되겠네요...