Files
UnrealEngine/Engine/Shaders/Private/PostProcessMitchellNetravali.usf
2025-05-18 13:04:45 +08:00

161 lines
4.0 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
PostProcessDownsample.usf: PostProcessing down sample.
=============================================================================*/
#define EYE_ADAPTATION_LOOSE_PARAMETERS 1
#include "Common.ush"
#include "ScreenPass.ush"
#include "EyeAdaptationCommon.ush"
//------------------------------------------------------- COMPILE TIME CONSTANTS
#define THREADGROUP_SIZEX 8
#define THREADGROUP_SIZEY 8
static const int2 kOffsetsCross3x3[4] = {
int2(-1, -1),
int2( 1, -1),
int2(-1, 1),
int2( 1, 1),
};
//------------------------------------------------------- FUNCTIONS
// Faster but less accurate luma computation.
// Luma includes a scaling by 4.
float Luma4(float3 Color)
{
return (Color.g * 2.0) + (Color.r + Color.b);
}
// Optimized HDR weighting function.
float HdrWeight4(float3 Color, float Exposure)
{
return rcp(Luma4(Color) * Exposure + 4.0);
}
// Mitchell Netravali
// B-spline: B=1 C=0
// Mitchell: B=1/3 C=1/3
// Catmull-Rom: B=0 C=1/2
// Robidoux: B=0.3782 C=0.3109 (cylindrical)
// Robidoux Sharp: B=0.2620 C=0.3690 (cylindrical)
// Robidoux Soft: B=0.6796 C=0.1602 (cylindrical)
void MitchellNetravaliCoefs(const float B, const float C, out float OutQ0[4], out float OutQ1[4])
{
OutQ0[0] = (6.0 - 2.0 * B) / 6.0;
OutQ0[1] = 0.0;
OutQ0[2] = (-18.0 + 12.0 * B + 6.0 * C) / 6.0;
OutQ0[3] = (12.0 - 9.0 * B - 6.0 * C) / 6.0;
OutQ1[0] = (8 * B + 24 * C) / 6.0;
OutQ1[1] = (-12 * B - 48 * C) / 6.0;
OutQ1[2] = (6 * B + 30 * C) / 6.0;
OutQ1[3] = (-B - 6 * C) / 6.0;
}
float MitchellNetravali(float d, const float B, const float C)
{
// Coeficient ends up known at compile time.
float Q0[4];
float Q1[4];
MitchellNetravaliCoefs(B, C, Q0, Q1);
if (d < 1)
{
return Q0[0] + d * (Q0[1] + d * (Q0[2] + d * Q0[3]));
}
else if ((d >= 1) && (d < 2))
{
return Q1[0] + d * (Q1[1] + d * (Q1[2] + d * Q1[3]));
}
else
{
return 0;
}
}
Texture2D InputTexture;
SamplerState InputSampler;
SCREEN_PASS_TEXTURE_VIEWPORT(Input)
float2 DispatchThreadToInputUVScale;
float2 DispatchThreadToInputUVBias;
RWTexture2D<float4> OutputTexture;
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)]
void DownsampleMainCS(uint2 DispatchThreadId : SV_DispatchThreadID)
{
const float FrameExposureScale = ToScalarMemory(EyeAdaptationLookup() * View.OneOverPreExposure);
const float B = rcp(3.0);
const float C = rcp(3.0);
float2 InputBufferUV = DispatchThreadId * DispatchThreadToInputUVScale + DispatchThreadToInputUVBias;
float4 OutColor = 0;
float OutWeight = 0;
{
const uint KernelSize = 4;
// Position of the output pixel in the input buffer.
float2 OutputPixelPos = InputBufferUV * Input_Extent;
float2 TopLeftKernel = floor(OutputPixelPos + (0.5 - 0.5 * KernelSize)) + 0.5;
UNROLL
for (uint x = 0; x < KernelSize; x++)
{
UNROLL
for (uint y = 0; y < KernelSize; y++)
{
float2 SamplePixelPos = TopLeftKernel + float2(x, y);
float2 SampleUV = SamplePixelPos * Input_ExtentInverse;
SampleUV = clamp(SampleUV, Input_UVViewportBilinearMin, Input_UVViewportBilinearMax);
float4 SampleColor = InputTexture.SampleLevel(InputSampler, SampleUV, 0);
float2 PixelOffset = abs(SamplePixelPos - OutputPixelPos);
float Weight = MitchellNetravali(PixelOffset.x, B, C) * MitchellNetravali(PixelOffset.y, B, C);
float HdrWeight = HdrWeight4(SampleColor.rgb, FrameExposureScale);
OutColor += (HdrWeight * Weight) * SampleColor;
OutWeight += HdrWeight * Weight;
}
}
}
if (OutWeight > 0)
{
OutColor *= rcp(OutWeight);
}
// Remove unecessary ALU work for alpha channel if unsupported on project.
#if DIM_ALPHA_CHANNEL
{
OutColor[3] = select(OutColor[3] > 0.995, 1.0, OutColor[3]);
OutColor[3] = select(OutColor[3] < 0.005, 0.0, OutColor[3]);
}
#else
OutColor.a = 0;
#endif
if (all(DispatchThreadId < Input_ViewportMax))
{
OutputTexture[DispatchThreadId] = OutColor;
}
}