You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/MSAAResolveCustom.azsl

183 lines
5.8 KiB
Plaintext

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/Features/SrgSemantics.azsli>
#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
#include <Atom/Features/PostProcessing/PostProcessUtil.azsli>
// --- NOTES ---
//
// This shader performs a custom MSAA resolve by applying tonemapping logic to each sub-pixel before they are average
// This yields more accurate/less aliased results (see https://mynameismjp.wordpress.com/2012/10/24/msaa-overview/)
// Additionally, aliasing can be further reduced by applying neighborhood lumanince clamping. This will clamp luminace
// values of sampled sub-pixels based on how bright neighboring sub-pixels are.
#define PROVISIONAL_TONEMAP_GAMMA 2.2f
// Tonemapping strategy
#define REINHARD_TONEMAPPING_STRATEGY 0
#define LOG_TONEMAPPING_STRATEGY 1
#define TONEMAPPING_STRATEGY LOG_TONEMAPPING_STRATEGY
// Neighbor clamping strategy
#define CLAMP_NEIGHBOR_MAX_LUMINANCE 0
#define CLAMP_NEIGHBOR_AVERAGE_LUMINANCE 1
#define CLAMPING_STRATEGY CLAMP_NEIGHBOR_MAX_LUMINANCE
ShaderResourceGroup PassSrg : SRG_PerPass
{
Texture2DMS<float4> inputTexture;
// Whether to use neighborhood clamping to reduce aliasing
uint enableNeighborClamping;
// Maximum factor by which a sub-pixel can exceed the luminance of it's neighboring sub-pixels
float maxNeighborContrast;
}
float3 ApplyProvisionalTonemap(float3 color)
{
// Provisional tone mapping will be applied using Reinhard, then applying gamma to handle the color in perceptual color space.
float3 toneMappedColor = color.xyz / (1.0 + color.xyz);
return float3(pow(toneMappedColor, 1.0 / PROVISIONAL_TONEMAP_GAMMA));
}
float3 ApplyInverseProvisionalTonemap(float3 color)
{
// This is inverse function of provisional tone mapping.
float3 linearValue = pow(color.xyz, PROVISIONAL_TONEMAP_GAMMA);
return float3(linearValue / (1.0 - linearValue));
}
float3 ConvertLighting(float3 color)
{
#if TONEMAPPING_STRATEGY == LOG_TONEMAPPING_STRATEGY
return log(color);
#elif TONEMAPPING_STRATEGY == REINHARD_TONEMAPPING_STRATEGY
return ApplyProvisionalTonemap(color);
#endif
}
float3 ConvertBack(float3 color)
{
#if TONEMAPPING_STRATEGY == LOG_TONEMAPPING_STRATEGY
return exp(color);
#elif TONEMAPPING_STRATEGY == REINHARD_TONEMAPPING_STRATEGY
return ApplyInverseProvisionalTonemap(color);
#endif
}
float CalcLuminance(float3 color)
{
return dot(color, float3(0.299f, 0.587f, 0.114f));
}
struct PSOutput
{
float4 m_color : SV_Target0;
};
PSOutput MainPS(VSOutput IN)
{
PSOutput OUT;
uint width = 0;
uint height = 0;
uint numberOfSamples = 0;
PassSrg::inputTexture.GetDimensions(width, height, numberOfSamples);
// The brightest luminance value of all neighbor sub-pixels
float brightestNeighborLuminance = 0.0f;
// The average luminance of all neighbor sub-pixels
float averageNeighborLuminance = 0.0f;
int2 coord = int2(width * IN.m_texCoord.x, height * IN.m_texCoord.y);
if(PassSrg::enableNeighborClamping)
{
// Get average + brightest neighborhood sub-pixel luminance value
for (uint i = 0; i < numberOfSamples; ++i)
{
float3 sampleColor00 = PassSrg::inputTexture.Load(int2(coord.x + 1, coord.y + 1), i).rgb;
float luminance00 = CalcLuminance(sampleColor00);
brightestNeighborLuminance = max(brightestNeighborLuminance, luminance00);
averageNeighborLuminance += luminance00;
float3 sampleColor01 = PassSrg::inputTexture.Load(int2(coord.x + 1, coord.y - 1), i).rgb;
float luminance01 = CalcLuminance(sampleColor01);
brightestNeighborLuminance = max(brightestNeighborLuminance, luminance01);
averageNeighborLuminance += luminance01;
float3 sampleColor10 = PassSrg::inputTexture.Load(int2(coord.x - 1, coord.y + 1), i).rgb;
float luminance10 = CalcLuminance(sampleColor10);
brightestNeighborLuminance = max(brightestNeighborLuminance, luminance10);
averageNeighborLuminance += luminance10;
float3 sampleColor11 = PassSrg::inputTexture.Load(int2(coord.x - 1, coord.y - 1), i).rgb;
float luminance11 = CalcLuminance(sampleColor11);
brightestNeighborLuminance = max(brightestNeighborLuminance, luminance11);
averageNeighborLuminance += luminance11;
}
// Normalize sum
averageNeighborLuminance = averageNeighborLuminance / (4.0f * float(numberOfSamples));
}
float3 color = float3(0, 0, 0);
// Accumulate sub-pixels
for (uint i = 0; i < numberOfSamples; ++i)
{
// Get sub-pixel i
float3 sampleColor = PassSrg::inputTexture.Load(coord, i).rgb;
if(PassSrg::enableNeighborClamping)
{
float sampleLuminance = CalcLuminance(sampleColor);
#if CLAMPING_STRATEGY == CLAMP_NEIGHBOR_MAX_LUMINANCE
float lumFactor = PassSrg::maxNeighborContrast * brightestNeighborLuminance / sampleLuminance;
#elif CLAMPING_STRATEGY == CLAMP_NEIGHBOR_AVERAGE_LUMINANCE
float lumFactor = PassSrg::maxNeighborContrast * averageNeighborLuminance / sampleLuminance;
#endif
sampleColor *= min(1.0f, lumFactor);
}
// Apply tonemapping strategy
sampleColor = ConvertLighting(sampleColor);
// Accumulate sample
color += sampleColor;
}
// Sample normalization and inverse tone mapping
color = color / float(numberOfSamples);
// Apply inverse tonemapping st
color = ConvertBack(color);
OUT.m_color = float4(color, 1.0);
return OUT;
}