You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
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]
742 lines
23 KiB
Plaintext
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
|
|
|