역자의 말.
종종 위쳇 마이크로 블로그에 글을 올리는 중국인 엔지니어 포스팅을 보다가... 원래의 내 글을 참조 해서 구현하는 기사를 봤습니다. 개인적으로는 굳이 저렇게? 라고 생각이 들지만 렌더피처 형식으로 구현하고자 할 경우 이 기사를 참조 하는 것도 좋을 듯 해서 번역 해 봤습니다.
원본 글.
중국인 저자가 참고 한 원래 내 포스팅.
필자는 이번 연구에 있어서 컴공 전공을 한 코더가 아니기 때문에 코드에 대한 오해가 있을 수 있으니 용서해 주시기 바랍니다.
동영상 튜토리얼 b사이트 링크는 아래에 있으며, git으로 이동하여 스크립트를 직접 다운로드할 수 있습니다:
선견지명:
톤 매핑은 하이 다이나믹 HDR 색상을 로우 다이나믹 LDR 색 영역에 매핑하는 일반적인 후처리 효과로, 모든 장치에서 사진이 제대로 표시되도록 하고 사진의 밝고 어두운 디테일을 높여 일반적으로 영화에서 더 흔하게 발생하는 과다 노출을 방지합니다. 초보자는 톤 매핑, 감마 보정 및 기타 개념을 구분하지 못할 수 있으므로 다음 블로거의 글을 참조할 수 있으며 여기서는 반복하지 않겠습니다.
UE4의 ACES 매핑 체계 포팅 (초기 버전)
실험용 버전은 Unity2021.3.24f1-URP이며, 저는 항상 Unity에는 톤 매핑 효과가 너무 적고 사용하기에 좋지 않다고 느꼈고, 특히 조정 가능한 파라미터가 많은 HDRP와 달리 일반적으로 ACES 효과가 함께 제공되므로 항상 UE4에 이식 된 톤 매핑 효과에서 UE4를 원했기 때문에 정보를 확인하기 시작했고 누군가 먼저 해법을 만든것을 발견했으며 각 다른 톤 매핑 체계의 홈 페이지에서 비디오를 볼 수 있습니다 (초기 버전). 그래서 UE4에서 Unity로 톤 매핑 효과를 포팅하려고 시도하고 있었기 때문에 정보를 확인하기 시작했고, 누군가 이미 비슷한 처리를 한 것을 발견했으며, 홈페이지의 비디오에서 다른 톤 매핑 체계의 차이점을 볼 수 있습니다:
이 방법은 소스 코드 수정이 많이 필요할 뿐만 아니라 레퍼런스도 수정해야 하고, 프로젝트를 업데이트할 때마다 다시 처리해야 하는 번거로움이 있어서 자체적인 후처리로 문제를 해결하고자 합니다. 첫 번째 아이디어는 화면의 픽셀을 톤 매핑하는 별도의 렌더 피처를 추가한 다음 셰이더에 자신만의 수정된 톤 매핑 함수를 추가하여 위에서 언급한 방법을 통해 픽셀을 매핑하는 것입니다.
유니티와 함께 제공되는 ACES_Tonemapping 메서드를 얻으려면 패키지에서 Color.hlsl 파일을 검색한 다음 열어 내부에서 AcesTonemap 메서드를 찾을 수 있으며, 여기에 입력된 메서드는 float3 aces이므로 이 메서드를 사용할 때는 셰이더의 선형 공간 색상을 먼저 에이스 색 공간으로 변환한 다음 셰이더에 자체 수정 톤 매핑 함수를 추가해야 한다는 점에 유의하세요. 셰이더의 선형 공간 색상을 에이스 색 공간으로 변환한 다음 에이스 색 공간에 매핑해야 합니다. 여기서는 새 셰이더를 생성하고 위에서 언급한 방법을 참조하여 수정할 수 있도록 Unity와 함께 제공되는 매핑 함수를 복사한 다음 이후 섹션에서 호출하는 데 사용합니다.
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 매핑 커브 대비가 더 제어 가능하며 유니티의 자체 톤 매핑 체계 대비만큼 크지는 않지만 채도가 약간 더 높다는 것을 알 수 있습니다.
포스트 프로세싱 효과의 실행 순서 문제
이 단계에서 효과가 있지만 치명적인 문제가 있습니다. 즉, 블룸 효과가 영향을받습니다. 톤 매핑 후에는 HDR 색상이 LDR로 축소되므로 원래의 라이트 오버플로 부분이 더 이상 유효하지 않기 때문입니다. 유니티와 함께 제공되는 포스트 프로세싱에는 실행 순서가 고정되어 있으며 URP의 소스 코드를 확인하면 블룸 효과가 고정되어 있음을 알 수 있습니다. 는 톤 매핑 이전이므로 이러한 문제가 없습니다.
소스 코드 파일의 위치입니다:
Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/UberPost.shader
Packages/com.unity.render-pipelines.universal/Runtime/Passes/PostProcessPass.cs
내 포스트 프로세싱이 시스템 포스트 프로세싱 전에 실행되도록 작성되기 전에 프레임 디버거를 확인하면 시스템 기본 포스트 프로세싱이 이 패스 전체에서 직접 실행되고 내부 포스트 프로세싱 순서가 고정되어 있으며, 우리는 우리 자신의 포스트 프로세싱 순서를 포스트 프로세싱 전후에 전체 시스템 포스트 프로세싱 패스에서만 정의할 수 있음을 알 수 있습니다. 따라서 커스텀 톤 매핑이 시스템 포스트 프로세싱 전에 실행되면 블룸 효과가 방해받는다는 문제가 있습니다.
이 매핑 효과를 시스템 포스트 프로세싱 후에 넣고 싶지만, 이 커스텀 포스트 프로세싱이 아무런 효과가 없는 상태에서 정보를 수집하기 위해 렌더 포스트 프로세싱 효과가 궁극적으로 백버퍼에 직접 그려지는 것을 발견했습니다. 그래서 우리는 우리 자신의 렌더 기능을 추가 기본 포스트 프로세싱 전에 시스템에만있을 수 있습니다. 따라서 시스템 기본 포스트 프로세싱 전에만 자체 렌더 피처를 추가할 수 있으므로 사용자 정의 포스트 프로세싱과 URP 자체 포스트 프로세싱의 실행 순서를 제어할 수 없다는 문제가 발생합니다. (물론 제가 아직 충분한 정보를 수집하지 못했을 수도 있고, 커스텀 패스를 백버퍼로 읽어 백버퍼에 출력하는 것이 가능할 수도 있겠죠?).
물론 어리석은 해결책도 있습니다. 즉, 커스텀 톤 매핑 앞에 또는 커스텀 톤 매핑 셰이더에 직접 배치 된 커스텀 블룸 효과를 작성하므로 블룸과 톤 매핑 사이의 문제를 해결할 수는 있지만 해결할 수 없고 다른 시스템에서 후 처리가 함께 제공됩니다.
블룸과 톤매핑 사이의 문제는 해결할 수 있지만 다른 시스템 포스트 프로세서와의 순서 문제를 해결할 수 없고, 새로운 렌더 기능을 열 때마다 추가 소모가 늘어나므로 이번에는 다루지 않겠습니다. (물론 Unity와 같은 포스트 프로세싱 시스템을 커스터마이징하고 다양한 매크로를 사용하여 처리하는 방법을 배울 수도 있지만 워크로드가 매우 무거워집니다).
UE4 포팅을 위한 ACES 매핑 방식(최적화 버전)
이전 질문에 대한 답변으로, 추가 패스를 추가하지 않고 자체 포스트 프로세싱과 원본 포스트 프로세싱 간의 실행 순서를 커스터마이징하려면 결국 유니티 소스 코드를 수정하는 UberPost를 수정할 수밖에 없는데, 다행히 시스템의 포스트 프로세싱 소스 코드를 수정하는 방법에 대한 기사를 발견했습니다. 이 기사는 매우 전문적이고 상세하며 주의 깊게 읽으면 후처리에 대한 대부분의 의문을 해결할 수 있습니다.
이제 위에서 설명한 내용을 바탕으로 포스트 프로세싱 순서를 커스터마이징할 수 있습니다. 먼저 Create-Rendering-URP postprocessingData를 마우스 오른쪽 버튼으로 클릭하여 새 사용자 지정 포스트 프로세싱 파일을 만든 다음 인스펙터 모드를 디버그로 조정하고(일반에서는 프로퍼티가 표시되지 않음), UberPost.shader의 복사본을 만들고 이름을 변경하고, 복사한 UberPost를 UberPost.shader를 복사하고 이름을 변경한 다음 복사한 UberPost를 "Uber Post PS"로 드래그 앤 드롭합니다. 아래 그림에서 볼 수 있듯이.
그런 다음 다음과 같이 파이프라인 렌더링 설정 파일의 "PostProcessData"에 이 PostProcessData 파일을 끌어다 놓습니다.
이제 엔진은 방금 복사한 UberPost를 후처리에 사용하므로 복사한 셰이더의 출력을 흰색으로 변경하여 테스트할 수 있으며, 화면이 흰색이면 교체에 성공했다는 의미입니다.
다음 단계는 톤 매핑에 대한 부분을 수정하는 것입니다. 이 부분은 앞서 언급 한 셰이더 메서드의 다음 부분에서 실행되며이 메서드는 참조 Common.hlsl 파일에 있으며이 메서드를 수정하는 방법에는 두 가지가 있습니다. 첫 번째는 소스 코드의 복사본을 복사 한 다음 처리 할 이름과 셰이더의 참조를 변경하는 것이고 다른 하나는 개별 방법을 사용자 정의 셰이더에 복사하거나 새 hlsl을 생성하는 것입니다. 다른 하나는 개별 메서드를 커스텀 셰이더에 복사하거나 직접 새 hlsl을 만드는 것입니다. 다른 셰이더에서 재사용하려면 방법 1이 가장 좋지만 여기서는 시간을 절약하기 위해 방법 2를 사용하겠습니다.
새 hlsl 파일을 만든 다음 공통 파일에서 이 메서드를 새 hlsl 파일에 복사하고 이름을 바꿉니다. 그런 다음 이 메서드에서 톤 매핑 메서드를 찾아 셰이더에 복사하고 이름을 바꿉니다. 그런 다음 UE4에서 톤 매핑 메서드를 복사하고 패스 파라미터를 변경합니다.
이전 복사본에서 사용자 정의 포스트 프로세싱 셰이더에 매크로와 참조를 추가하고 참조 경로는 직접 정의한 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의 톤 매핑 조정 가능한 범위가 더 많고 대비가 작고 밝은 부분이 더 잘 보이지만 사람들의 지혜를 볼 수있는 자비로운 것의 효과는 더 좋습니다. 또한 인터넷의 모든 사람들에게 이타적인 공유에 대해 감사 드리며, 저에게는 정말 큰 이득입니다 ~!
자, 다 끝났습니다! 모두 건배!
역자의 후기.
직접 구현을 해서 검증한 기사가 아닙니다. 참조 해서 구현 해 보셔도 되겠네요...
'TECH.ART.FLOW.IO' 카테고리의 다른 글
[번역]SDF Based Transition Blending for Shadow Threshold Map (1) | 2024.03.04 |
---|---|
[요약번역]GDC 2023 - Real-time Sparse Distance Fields for Games 파트-2 알고리즘 (2) | 2024.02.24 |
[번역]GPU Run-time Procedural Placement on Terrain (1) | 2024.02.05 |
[번역]Efficient GPU Rendering for Dynamic Instances in Game Development (1) | 2024.02.05 |
[요약번역]GDC 2023 - Real-time Sparse Distance Fields for Games 파트-1 개요 (0) | 2024.01.21 |