Files
UnrealEngineUWP/Engine/Shaders/Private/PostProcessTonemap.usf
eric renaudhoude ed1ddf6eb1 Post-processing: Reconnect r.LUT.Size with the combined LUT/tonemapping texture size.
Increasing the LUT resolution can become necessary to mitigate precision artifacts with wide-gamut working color spaces.

#jira UE-170301
#rb benjamin.rouveyrol, guillaume.abadie
#preflight 63696783843e6ac794811b27

[CL 23175071 by eric renaudhoude in ue5-main branch]
2022-11-17 09:03:21 -05:00

742 lines
23 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PostProcessTonemap.usf: PostProcessing tone mapping
=============================================================================*/
#define EYE_ADAPTATION_LOOSE_PARAMETERS 1
#ifndef SUPPORTS_SCENE_COLOR_APPLY_PARAMETERS
#define Undefined SUPPORTS_SCENE_COLOR_APPLY_PARAMETERS 0
#endif
#include "Common.ush"
#include "PostProcessCommon.ush"
#include "TonemapCommon.ush"
#include "EyeAdaptationCommon.ush"
#include "PostProcessHistogramCommon.ush"
#include "PixelQuadMessagePassing.ush"
#include "ScreenPass.ush"
#ifndef DIM_OUTPUT_DEVICE
#define DIM_OUTPUT_DEVICE (TONEMAPPER_OUTPUT_sRGB)
#endif
// 0 / 1(slower, visualize multiple internals side by side for the same image portion, see DebugTile)
#define DEBUG_TONEMAPPER 0
SCREEN_PASS_TEXTURE_VIEWPORT(Color)
SCREEN_PASS_TEXTURE_VIEWPORT(Output)
Texture2D ColorTexture;
SamplerState ColorSampler;
FScreenTransform ColorToBloom;
float2 BloomUVViewportBilinearMin;
float2 BloomUVViewportBilinearMax;
Texture2D BloomTexture;
SamplerState BloomSampler;
#if SUPPORTS_SCENE_COLOR_APPLY_PARAMETERS
StructuredBuffer<float4> SceneColorApplyParamaters;
#endif
Texture3D LumBilateralGrid;
Texture2D BlurredLogLum;
SamplerState LumBilateralGridSampler;
SamplerState BlurredLogLumSampler;
// xyz:SceneColorTint.rgb, w:unused
float4 ColorScale0;
// xyz:Bloom1Tint.rgb, w:unused
float4 ColorScale1;
// from the postprocess settings, x:VignetteIntensity, y:SharpenDiv6
float4 TonemapperParams;
// Grain quantization
float3 GrainRandomFull;
// Film grain
float FilmGrainIntensityShadows;
float FilmGrainIntensityMidtones;
float FilmGrainIntensityHighlights;
float FilmGrainShadowsMax;
float FilmGrainHighlightsMin;
float FilmGrainHighlightsMax;
Texture2D<half3> FilmGrainTexture;
SamplerState FilmGrainSampler;
float4 ScreenPosToFilmGrainTextureUV;
#if SUPPORTS_FILM_GRAIN
StructuredBuffer<float4> FilmGrainTextureConstants;
#endif // SUPPORTS_FILM_GRAIN
float EditorNITLevel;
uint bOutputInHDR;
float OutputMaxLuminance;
#if EYEADAPTATION_EXPOSURE_FIX != 1
// Default value used when eye adaptation is disabled (e.g fixed exposure) or not available.
float DefaultEyeExposure;
#endif
float4 BloomDirtMaskTint;
Texture2D BloomDirtMaskTexture;
SamplerState BloomDirtMaskSampler;
float4 LensPrincipalPointOffsetScale;
float4 LensPrincipalPointOffsetScaleInverse;
half GrainFromUV(float2 GrainUV)
{
half Grain = frac(sin(GrainUV.x + GrainUV.y * 543.31) * 493013.0);
return Grain;
}
// converts from screen [-1,1] space to the lens [-1,1] viewport space
float2 ConvertScreenViewportSpaceToLensViewportSpace(float2 UV)
{
return LensPrincipalPointOffsetScale.xy + UV * LensPrincipalPointOffsetScale.zw;
}
float2 ConvertLensViewportSpaceToScreenViewportSpace(float2 UV)
{
// reference version
//return (UV - LensPrincipalPointOffsetScale.xy)/LensPrincipalPointOffsetScale.zw;
// optimized version
return LensPrincipalPointOffsetScaleInverse.xy + UV * LensPrincipalPointOffsetScaleInverse.zw;
}
half3 LinearToPostTonemapSpace(half3 lin)
{
#if IOS
// Note, iOS native output is raw gamma 2.2 not sRGB!
return pow(lin, 1.0 / 2.2);
#else
return LinearToSrgbBranchless(lin);
#endif
}
// Nuke-style Color Correct
float ComputeFilmGrainIntensity(float3 LinearColorRGBGamut)
{
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 ColorAP1 = mul(sRGB_2_AP1, LinearColorRGBGamut);
//float Luma = dot(ColorAP1, AP1_RGB2Y);
float Luma = dot(LinearColorRGBGamut, mul(AP1_2_sRGB, AP1_RGB2Y));
float CCWeightShadows = 1 - smoothstep(0, FilmGrainShadowsMax, Luma);
float CCWeightHighlights = smoothstep(FilmGrainHighlightsMin, FilmGrainHighlightsMax, Luma);
float CCWeightMidtones = 1 - CCWeightShadows - CCWeightHighlights;
// Blend Shadow, Midtone and Highlight CCs
float FilmGrainIntensity = (
FilmGrainIntensityShadows * CCWeightShadows +
FilmGrainIntensityMidtones * CCWeightMidtones +
FilmGrainIntensityHighlights * CCWeightHighlights);
return FilmGrainIntensity;
}
// LUT for color grading
#if USE_VOLUME_LUT == 1
Texture3D ColorGradingLUT;
#else
Texture2D ColorGradingLUT;
#endif
SamplerState ColorGradingLUTSampler;
float LUTSize;
float InvLUTSize; // 1 / LUTSize
float LUTScale; // (LUTSize - 1) / LUTSize
float LUTOffset; // 0.5 / LUTSize
half3 ColorLookupTable( half3 LinearColor )
{
float3 LUTEncodedColor;
// Encode as ST-2084 (Dolby PQ) values
#if (DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES1000nitST2084 || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES2000nitST2084 || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES1000nitScRGB || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES2000nitScRGB || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_LinearEXR || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_NoToneCurve || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_WithToneCurve)
// ST2084 expects to receive linear values 0-10000 in nits.
// So the linear value must be multiplied by a scale factor to convert to nits.
LUTEncodedColor = LinearToST2084(LinearColor * LinearToNitsScale);
#else
LUTEncodedColor = LinToLog( LinearColor + LogToLin( 0 ) );
#endif
float3 UVW = LUTEncodedColor * LUTScale + LUTOffset;
#if USE_VOLUME_LUT == 1
half3 OutDeviceColor = Texture3DSample( ColorGradingLUT, ColorGradingLUTSampler, UVW ).rgb;
#else
half3 OutDeviceColor = UnwrappedTexture3DSample( ColorGradingLUT, ColorGradingLUTSampler, UVW, LUTSize, InvLUTSize ).rgb;
#endif
return OutDeviceColor * 1.05;
}
// can be optimized
float2 ScreenPosToUV(float2 ScreenPos, float2 ExtentInverse)
{
float2 UV = (ScreenPos * Color_ScreenPosToViewportScale + Color_ScreenPosToViewportBias) * ExtentInverse;
return UV;
}
float2 UVToScreenPos(float2 UV, float2 Extent)
{
return (UV * Extent - Color_ScreenPosToViewportBias) / Color_ScreenPosToViewportScale;
}
float4 ChromaticAberrationParams;
void TonemapCommonVS(
in float4 Position,
in float2 TexCoord,
out float2 OutVignette,
out float4 OutGrainUV,
out float2 OutScreenPos,
out float2 OutFullViewUV
)
{
// Forward renderer uses view size texture
// TODO: Looks to be Ronin specific.. check this:
OutFullViewUV.xy = Position.xy * float2(0.5,-0.5) + 0.5;
const float AspectRatio = Output_ViewportSize.y * Output_ViewportSizeInverse.x;
float2 ColorViewportPos = UVToScreenPos(TexCoord, Color_Extent);
// Scale vignette to always be a circle with consistent corner intensity.
float2 LensViewportPos = ConvertScreenViewportSpaceToLensViewportSpace(ColorViewportPos);
OutVignette = VignetteSpace(LensViewportPos, AspectRatio);
// Grain
OutGrainUV.xy = TexCoord + Color_ExtentInverse * float2(-0.5,0.5);
OutGrainUV.zw = TexCoord + GrainRandomFull.xy;
// Fringe
OutScreenPos = UVToScreenPos(TexCoord, Color_Extent);
}
// vertex shader entry point
void MainVS(
in float4 InPosition : ATTRIBUTE0,
in float2 InTexCoord : ATTRIBUTE1,
out noperspective float2 OutTexCoord : TEXCOORD0,
out noperspective float2 OutVignette : TEXCOORD1,
out noperspective float4 OutGrainUV : TEXCOORD2,
out noperspective float2 OutScreenPos : TEXCOORD3,
out noperspective float2 OutFullViewUV : TEXCOORD4,
out float4 OutPosition : SV_POSITION
)
{
DrawRectangle(InPosition, InTexCoord, OutPosition, OutTexCoord);
TonemapCommonVS(OutPosition, OutTexCoord, OutVignette, OutGrainUV, OutScreenPos, OutFullViewUV);
}
// Function graphing
float F0( float x )
{
return x*saturate( (x - 0.5)/2 );
}
float F1( float x )
{
return x;
}
float F2( float x )
{
return x;
}
float F3( float x )
{
return x;
}
float LineShade( float fx, float y, float dydx, float LineWidth )
{
return 1 - smoothstep( 0.5 * LineWidth, LineWidth, abs( fx - y ) / sqrt( 1 + Square( dydx ) ) );
}
float3 Graph( float2 ScreenSpacePos )
{
float2 WindowMin = float2( 0, 0 );
float2 WindowMax = float2( 1, 1 );
float2 p = ( (ScreenSpacePos + 1) * 0.5 - WindowMin ) * ( WindowMax - WindowMin );
float LineWidth = dot( WindowMax - WindowMin, 0.0005 );
float3 Color;
Color = float3( 1, 0, 0 ) * LineShade( F0(p.x), p.y, ( F0(p.x + LineWidth) - F0(p.x - LineWidth) ) / (2 * LineWidth), LineWidth );
Color += float3( 0, 1, 0 ) * LineShade( F1(p.x), p.y, ( F1(p.x + LineWidth) - F1(p.x - LineWidth) ) / (2 * LineWidth), LineWidth );
Color += float3( 0, 0, 1 ) * LineShade( F2(p.x), p.y, ( F2(p.x + LineWidth) - F2(p.x - LineWidth) ) / (2 * LineWidth), LineWidth );
Color += float3( 1, 1, 0 ) * LineShade( F3(p.x), p.y, ( F3(p.x + LineWidth) - F3(p.x - LineWidth) ) / (2 * LineWidth), LineWidth );
//Color += float3( 0, 1, 1 ) * LineShade( F4(p.x), p.y, ( F4(p.x + LineWidth) - F4(p.x - LineWidth) ) / (2 * LineWidth), LineWidth );
//Color += float3( 1, 1, 1 ) * LineShade( F5(p.x), p.y, ( F5(p.x + LineWidth) - F5(p.x - LineWidth) ) / (2 * LineWidth), LineWidth );
return Color;
}
// @return color in SRGB
float3 SimpleToneMap(float3 HDRColor)
{
// from http://filmicgames.com/archives/75
// float3 x = max(0, HDRColor - 0.004f); return (x * (6.2f * x + 0.5f)) / (x * (6.2f * x + 1.7f) + 0.06f);
// linear/no tonemapper
return HDRColor;
}
float max3(float3 In)
{
return max(In.x, max(In.y, In.z));
}
// @return 0 at low, 1 at high and linear interpolated inbetween
float RemapScalar(float Low, float High, float x)
{
return (x - Low) / (High - Low);
}
float max4(float4 x)
{
return max(max(x.r, x.g), max(x.b, x.a));
}
// useful debug helper (not optimized), could be moved into a more common place
// @param DebugTile 0,0 is center, -1,0 is left, 1,0 is right, ...
float2 ComputeCounterStepForTileDebugging(float4 SvPosition, int2 TileSize, out int2 DebugTile, out int2 LocalDebugTilePosition)
{
// ViewportRect is defined for postprocessing passes
float2 CenterPos = (Output_ViewportMin + Output_ViewportMax) / 2.0f;
float2 LeftTopPos = CenterPos - TileSize / 2;
float2 LocalPos = SvPosition.xy - LeftTopPos;
DebugTile = floor(LocalPos / TileSize);
LocalDebugTilePosition = LocalPos - DebugTile * TileSize;
float2 CounterStep = -DebugTile * TileSize;
return CounterStep;
}
// useful debug helper (works with ComputeCounterStepForTileDebugging()), could be moved into a more common place
#define OffsetInterpolator(CounterStep, InterpolatorName) \
InterpolatorName += ddx(InterpolatorName) * CounterStep.x + ddy(InterpolatorName) * CounterStep.y;
float4 SampleSceneColor(float2 SceneUV)
{
SceneUV = clamp(SceneUV, Color_UVViewportBilinearMin, Color_UVViewportBilinearMax);
return Texture2DSample(ColorTexture, ColorSampler, SceneUV);
}
half3 LookupSceneColor(float2 SceneUV, float2 PixelOffset)
{
float2 SampleUV = SceneUV + PixelOffset * Color_ExtentInverse;
return SampleSceneColor(SampleUV).xyz;
}
float4 TonemapCommonPS(
float2 UV,
float2 Vignette,
float4 GrainUV,
float2 ScreenPos, // [-1, 1]x[-1, 1]
float2 FullViewUV,
float4 SvPosition
)
{
float4 OutColor = 0;
#if DEBUG_TONEMAPPER
// @param DebugTile 0,0 is center, -1,0 is left, 1,0 is right, ...
int2 DebugTile;
int2 LocalDebugTilePosition;
bool bOutputDebugTile = false;
{
// split the screen in a 3x3 layout and add some border (0.3)
int2 TileSize = floor(Output_ViewportSize / float2(3.3f, 3.3f));
bool bBorder;
float2 CounterStep = ComputeCounterStepForTileDebugging(SvPosition, TileSize, DebugTile, LocalDebugTilePosition);
OffsetInterpolator(CounterStep, UV);
OffsetInterpolator(CounterStep, GrainUV);
OffsetInterpolator(CounterStep, ScreenPos);
OffsetInterpolator(CounterStep, FullViewUV);
}
if (all(DebugTile == int2(0, 0)))
{
// center is unaltered tonemapper output
bOutputDebugTile = true;
}
#endif
float2 SceneUV = UV.xy;
#if USE_COLOR_FRINGE
float2 SceneUVJitter = float2(0.0, 0.0);
float2 CAScale = ChromaticAberrationParams.rg;
float StartOffset = ChromaticAberrationParams.z;
float2 LensUV = ConvertScreenViewportSpaceToLensViewportSpace(ScreenPos);
float4 CAUV;
CAUV = LensUV.xyxy - sign(LensUV).xyxy * saturate(abs(LensUV) - StartOffset).xyxy * CAScale.rrgg;
CAUV.xy = ConvertLensViewportSpaceToScreenViewportSpace(CAUV.xy);
CAUV.zw = ConvertLensViewportSpaceToScreenViewportSpace(CAUV.zw);
CAUV.xy = ScreenPosToUV(CAUV.xy, Color_ExtentInverse);
CAUV.zw = ScreenPosToUV(CAUV.zw, Color_ExtentInverse);
half4 SceneColor = SampleSceneColor(CAUV.xy + SceneUVJitter.xy);
half SceneColorG = SampleSceneColor(CAUV.zw + SceneUVJitter.xy).g;
half SceneColorB = SampleSceneColor(SceneUV).b;
SceneColor.g = SceneColorG;
SceneColor.b = SceneColorB;
#else
half4 SceneColor = SampleSceneColor(SceneUV);
#endif
#if METAL_MSAA_HDR_DECODE && !USE_GAMMA_ONLY
// look for PreTonemapMSAA
SceneColor.rgb *= rcp(SceneColor.r * (-0.299) + SceneColor.g * (-0.587) + SceneColor.b * (-0.114) + 1.0);
// Try to kill negatives and NaNs here
SceneColor.rgb = max(SceneColor.rgb, 0);
#endif
SceneColor.rgb *= View.OneOverPreExposure;
#if EYEADAPTATION_EXPOSURE_FIX == 1
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM5
// texture can be GWhiteTexture which is 1x1. It's important we don't read outside bounds.
float2 EyeAdaptationData = EyeAdaptationTexture.Load(int3(0, 0, 0)).xw;
#else
float2 EyeAdaptationData = EyeAdaptationBuffer[0].xw;
#endif
#else
// If eye adaptation is disabled (e.g. fixed exposure level ) or not available.
float2 EyeAdaptationData = DefaultEyeExposure.xx;
#endif
float ExposureScale = EyeAdaptationData.x;
float LocalExposureOneOverPreExposure = View.OneOverPreExposure;
#if USE_LOCAL_EXPOSURE
{
float LuminanceVal = CalculateEyeAdaptationLuminance(SceneColor.rgb);
float LogLuminance = log2(LuminanceVal);
// Middle grey lum value adjusted by exposure compensation
float MiddleGreyLumValue = log2(0.18 * EyeAdaptationData.y * EyeAdaptation_LocalExposureMiddleGreyExposureCompensation);
float BaseLogLum = CalculateBaseLogLuminance(LogLuminance, EyeAdaptation_LocalExposureBlurredLuminanceBlend, ExposureScale, FullViewUV, LumBilateralGrid, BlurredLogLum, LumBilateralGridSampler, BlurredLogLumSampler);
float LocalExposure = CalculateLocalExposure(LogLuminance + log2(ExposureScale), BaseLogLum, MiddleGreyLumValue, EyeAdaptation_LocalExposureHighlightContrastScale, EyeAdaptation_LocalExposureShadowContrastScale, EyeAdaptation_LocalExposureDetailStrength);
// Apply before bloom
SceneColor.rgb *= LocalExposure;
LocalExposureOneOverPreExposure *= LocalExposure;
}
#endif
#if DEBUG_TONEMAPPER
if (all(DebugTile == int2(-1, 1)))
{
// left below to the center is: no ExposureScale
ExposureScale = 1.0f;
bOutputDebugTile = true;
}
#endif
// 0..x, 0:no effect .. 1:strong, from r.Tonemapper.Sharpen
// Div6 is to save one instruction
float SharpenMultiplierDiv6 = TonemapperParams.y;
#if DEBUG_TONEMAPPER
if (all(DebugTile == int2(0, -1)) || all(DebugTile == int2(-1, -1)))
{
// top row: no sharpen
SharpenMultiplierDiv6 = 0;
bOutputDebugTile = true;
}
#endif //DEBUG_TONEMAPPER
#if USE_SHARPEN
{
half A0 = Luminance(SceneColor.rgb);
#if HAS_PIXEL_QUAD_MESSAGE_PASSING_SUPPORT
// Use Wave Intrinsics to reduce texture taps
FPQMPContext PQMP = PQMPInit(floor(SceneUV* Color_Extent));
half4 LuminanceNeighbors;
half3 C1 = LookupSceneColor(SceneUV, float2(PQMP.QuadVector.x, 0)).rgb * LocalExposureOneOverPreExposure;
half3 C3 = LookupSceneColor(SceneUV, float2(0, PQMP.QuadVector.y)).rgb * LocalExposureOneOverPreExposure;
LuminanceNeighbors.x = Luminance(C1);
LuminanceNeighbors.y = Luminance(C3);
half3 C2 = PQMPReadX(PQMP, SceneColor.rgb);
half3 C4 = PQMPReadY(PQMP, SceneColor.rgb);
LuminanceNeighbors.z = PQMPReadX(PQMP,A0);
LuminanceNeighbors.w = PQMPReadY(PQMP,A0);
#else
half3 C1 = LookupSceneColor(SceneUV, float2(-1, 0)) * LocalExposureOneOverPreExposure;
half3 C2 = LookupSceneColor(SceneUV, float2( 1, 0)) * LocalExposureOneOverPreExposure;
half3 C3 = LookupSceneColor(SceneUV, float2( 0, -1)) * LocalExposureOneOverPreExposure;
half3 C4 = LookupSceneColor(SceneUV, float2( 0, 1)) * LocalExposureOneOverPreExposure;
half4 LuminanceNeighbors = half4(Luminance(C1), Luminance(C2), Luminance(C3), Luminance(C4));
#endif
// compute mask to not sharpen near very bright HDR content
// Note: using max instead of summming up saves 1 instruction
// Note: We could multiply this to tweak the edge weight but it might instroduce a cost
half HDREdge = ExposureScale * max4(abs(A0 - LuminanceNeighbors));
// 0..1
half EdgeMask = saturate(1.0f - HDREdge);
#if DEBUG_TONEMAPPER
if (all(DebugTile == int2(1, 0)))
{
// right to the center is: Sharpen EdgeMask
OutColor = EdgeMask;
return OutColor;
}
#endif //DEBUG_TONEMAPPER
// -1:sharpen, 0:no effect, 1:blur
float LerpFactor = -EdgeMask * SharpenMultiplierDiv6;
// optimized, Div6 went into the C++ code
half3 DeltaColor = (C1 + C2 + C3 + C4) - SceneColor.rgb * 4;
SceneColor.rgb += DeltaColor * LerpFactor;
}
#endif
#if USE_GAMMA_ONLY
SceneColor.rgb *= ExposureScale;
OutColor.rgb = pow(SceneColor.rgb, InverseGamma.x);
#else
{
half3 LinearColor = SceneColor.rgb * ColorScale0.rgb;
#if SUPPORTS_SCENE_COLOR_APPLY_PARAMETERS
{
float3 SceneColorMultiply = SceneColorApplyParamaters[0].xyz;
LinearColor *= SceneColorMultiply;
}
#endif
#if USE_BLOOM
{
float2 BloomUV;
#if ES3_1_PROFILE
{
BloomUV = FullViewUV.xy;
}
#else
{
BloomUV = ApplyScreenTransform(UV, ColorToBloom);
BloomUV = clamp(BloomUV, BloomUVViewportBilinearMin, BloomUVViewportBilinearMax);
}
#endif
float4 CombinedBloom = Texture2DSample(BloomTexture, BloomSampler, BloomUV);
CombinedBloom.rgb *= View.OneOverPreExposure;
//float2 DirtViewportUV = (SvPosition.xy - float2(Output_ViewportMin)) * Output_ViewportSizeInverse;
#if DEBUG_TONEMAPPER
if (all(DebugTile == int2(-1, -1)))
{
// left to the center is: no bloom
CombinedBloom = 0;
bOutputDebugTile = true;
}
if (all(DebugTile == int2(1, -1)))
{
// right to the center is: bloom only
LinearColor = 0;
bOutputDebugTile = true;
}
#endif //DEBUG_TONEMAPPER
#if FEATURE_LEVEL == FEATURE_LEVEL_ES3_1
{
// Support sunshaft and vignette for mobile, and we have applied the BloomIntensity and the BloomDirtMask at the sun merge pass.
LinearColor = LinearColor.rgb * CombinedBloom.a + CombinedBloom.rgb;
}
#else
{
float2 DirtLensUV = ConvertScreenViewportSpaceToLensViewportSpace(ScreenPos) * float2(1.0f, -1.0f);
float3 BloomDirtMaskColor = Texture2DSample(BloomDirtMaskTexture, BloomDirtMaskSampler, DirtLensUV * .5f + .5f).rgb * BloomDirtMaskTint.rgb;
LinearColor += CombinedBloom.rgb * (1.0 + BloomDirtMaskColor);
}
#endif
}
#endif
LinearColor *= ExposureScale;
#if USE_VIGNETTE
LinearColor.rgb *= ComputeVignetteMask( Vignette, TonemapperParams.x );
#endif
#if USE_FILM_GRAIN
{
float3 FilmGrainDecodeMultiply = FilmGrainTextureConstants[0].rgb;
half FilmGrainIntensity = ComputeFilmGrainIntensity(LinearColor);
float2 FilmGrainUV = ApplyScreenTransform(ScreenPos, ScreenPosToFilmGrainTextureUV);
half3 RawGrain = FilmGrainTexture.SampleLevel(FilmGrainSampler, FilmGrainUV, 0);
half3 FinalGrain = RawGrain * half3(FilmGrainDecodeMultiply);
LinearColor.rgb *= lerp(1.0, FinalGrain, FilmGrainIntensity);
}
#endif
half3 OutDeviceColor = ColorLookupTable(LinearColor);
#if USE_GRAIN_QUANTIZATION
half Grain = GrainFromUV(GrainUV.zw);
#if DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_NoToneCurve
#error LinearNoToneCurve device requires no Grain Quantization
#endif
#if DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_WithToneCurve
#error LinearWithToneCurve device requires no Grain Quantization
#endif
// Needs to go after tonemapping.
#if USE_LINEAR_FLOAT_RT
half GrainQuantization = 1.0/1024.0;
#else
half GrainQuantization = 1.0/256.0;
#endif
half GrainAdd = (Grain * GrainQuantization) + (-0.5 * GrainQuantization);
OutDeviceColor.rgb += GrainAdd;
#endif
// RETURN_COLOR not needed unless writing to SceneColor
OutColor.rgb = OutDeviceColor;
#if USE_LINEAR_FLOAT_RT
OutColor.rgb = sRGBToLinear( OutColor.rgb );
#endif
#if DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES1000nitScRGB || DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_ACES2000nitScRGB
OutColor.xyz = ST2084ToScRGB(OutColor.xyz, DIM_OUTPUT_DEVICE, OutputMaxLuminance);
#elif DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_LinearEXR
OutColor.xyz = ST2084ToLinear(OutColor.xyz);
#elif DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_NoToneCurve
OutColor.xyz = OutDeviceColor;
#elif DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_WithToneCurve
OutColor.xyz = OutDeviceColor;
#endif
}
#endif
#if POST_PROCESS_ALPHA == 2 || (POST_PROCESS_ALPHA == 1 && DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_NoToneCurve) || (POST_PROCESS_ALPHA == 1 && DIM_OUTPUT_DEVICE == TONEMAPPER_OUTPUT_WithToneCurve)
OutColor.a = SceneColor.a;
#endif
#if DEBUG_TONEMAPPER
if (!bOutputDebugTile)
{
// the original tonemapped color is only in the center
// other tiles can output like this:
// if(all(DebugTile == int2(1, 0)))
// { OutColor = ..; return; }
// or
// if(all(DebugTile == int2(1, 0)))
// { ..; bOutputDebugTile = true; }
OutColor = 0;
}
if (any(LocalDebugTilePosition == int2(0, 0)))
{
// border grid
OutColor = 1;
}
#endif //DEBUG_TONEMAPPER
#if PC_D3D && !USE_GAMMA_ONLY
// If HDR in the editor need to convert from PQ space to linear sRGB
BRANCH
if(bOutputInHDR)
{
OutColor.rgb = ST2084ToLinear(OutColor.rgb);
OutColor.rgb = OutColor.rgb / EditorNITLevel;
OutColor.rgb = LinearToPostTonemapSpace(OutColor.rgb);
}
#endif
return OutColor;
}
// pixel shader entry point
void MainPS(
in noperspective float2 UV : TEXCOORD0,
in noperspective float2 InVignette : TEXCOORD1,
in noperspective float4 GrainUV : TEXCOORD2,
in noperspective float2 ScreenPos : TEXCOORD3,
in noperspective float2 FullViewUV : TEXCOORD4,
float4 SvPosition : SV_POSITION, // after all interpolators
out float4 OutColor : SV_Target0
)
{
OutColor = TonemapCommonPS(UV, InVignette, GrainUV, ScreenPos, FullViewUV, SvPosition);
}
#if COMPUTESHADER
RWTexture2D<float4> RWOutputTexture;
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)]
void MainCS(uint2 DispatchThreadId : SV_DispatchThreadID)
{
float4 SvPosition = float4((float2)DispatchThreadId + Output_ViewportMin + 0.5f, 0.0f, 1.0f);
float2 UV = SvPosition.xy * Output_ExtentInverse;
float4 InScreenPos = float4(UV * float2(2, -2) - float2(1, -1), 0, 1);
if (IsComputeUVOutOfBounds(UV))
{
return;
}
float2 Vignette;
float4 GrainUV;
float2 FullViewUV, ScreenPos;
TonemapCommonVS(InScreenPos, UV, Vignette, GrainUV, ScreenPos, FullViewUV);
float4 OutColor = TonemapCommonPS(UV, Vignette, GrainUV, ScreenPos, FullViewUV, SvPosition);
uint2 PixelPos = DispatchThreadId + Output_ViewportMin;
RWOutputTexture[PixelPos] = OutColor;
}
#endif