diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/Aces.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/Aces.azsli index f81a8cdf37..b7a6c63dc2 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/Aces.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/Aces.azsli @@ -75,6 +75,8 @@ UNDISCLOSED. //////////////////////////////////////////////////////////////////////////////// // Constants +#pragma once + #include static const float HALF_MAX = 65504.0f; @@ -90,7 +92,8 @@ static const float DIM_SURROUND_GAMMA = 0.9811; enum class ShaperType { ShaperLinear, - ShaperLog2 + ShaperLog2, + PqSmpteSt2084, }; //////////////////////////////////////////////////////////////////////////////// diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/Shapers.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/Shapers.azsli new file mode 100644 index 0000000000..a101867736 --- /dev/null +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/Shapers.azsli @@ -0,0 +1,52 @@ +/* + * 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 + * + */ + +#pragma once + +#include + +// Perceptual quantizer coefficients +static const float PqM1 = 1305.0 / 8192.0; +static const float PqM2 = 2533.0 / 32.0; +static const float PqC1 = 102.0 / 128.0; +static const float PqC2 = 2413.0 / 128.0; +static const float PqC3 = 2392.0 / 128.0; +static const float PqMaxNits = 10000.0; + +float3 ShaperToLinear(float3 shaperColor, ShaperType shaperType, float shaperBias, float shaperScale) +{ + // Apply the inverse of the shaper function to give the color in the working color space + switch (shaperType) + { + case ShaperType::ShaperLinear: + return (shaperColor - shaperBias) / shaperScale; + case ShaperType::ShaperLog2: + return pow(2.0, (shaperColor - shaperBias) / shaperScale); + case ShaperType::PqSmpteSt2084: + shaperColor = min(shaperColor, 1.0); + return PqMaxNits * pow(max(pow(shaperColor, 1.0 / PqM2) - PqC1, 0.0) / (PqC2 - PqC3 * pow(shaperColor, 1.0 / PqM2)), 1.0 / PqM1); + } + return shaperColor; +} + +float3 LinearToShaper(float3 linearColor, ShaperType shaperType, float shaperBias, float shaperScale) +{ + // Convert from working color space to lut coordinates by applying the shaper function + switch (shaperType) + { + case ShaperType::ShaperLinear: + return linearColor * shaperScale + shaperBias; + case ShaperType::ShaperLog2: + return log2(linearColor) * shaperScale + shaperBias; + case ShaperType::PqSmpteSt2084: + linearColor = min(linearColor, PqMaxNits); + linearColor = linearColor / PqMaxNits; + return pow((PqC1 + PqC2 * pow(linearColor, PqM1)) / (1.0 + PqC3 * pow(linearColor, PqM1)), PqM2); + } + return linearColor; +} diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/ApplyShaperLookupTable.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/ApplyShaperLookupTable.azsl index a180b09630..10d2eb1179 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/ApplyShaperLookupTable.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/ApplyShaperLookupTable.azsl @@ -11,9 +11,7 @@ #include #include #include - -static const int SHAPER_LINEAR = 0; -static const int SHAPER_LOG2 = 1; +#include ShaderResourceGroup PassSrg : SRG_PerPass { @@ -41,36 +39,22 @@ PSOutput MainPS(VSOutput IN) float2 uvCoord = float2(IN.m_texCoord.x, IN.m_texCoord.y); float3 color = PassSrg::m_colorTexture.Sample(PassSrg::LinearSampler, uvCoord).rgb; + ShaperType shaperType = (ShaperType)PassSrg::m_shaperType; + // Convert from working color space to lut coordinates by applying the shaper function - float3 lutCoordinate = color; - if (PassSrg::m_shaperType == SHAPER_LINEAR) - { - lutCoordinate = color * PassSrg::m_shaperScale + PassSrg::m_shaperBias; - } - else if (PassSrg::m_shaperType == SHAPER_LOG2) - { - lutCoordinate = log2(color) * PassSrg::m_shaperScale + PassSrg::m_shaperBias; - } + float3 lutCoordinate = LinearToShaper(color, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale); // Adjust coordinate to the domain excluding the outer half texel in all directions uint3 outputDimensions; PassSrg::m_lut.GetDimensions(outputDimensions.x, outputDimensions.y, outputDimensions.z); - float3 coordBias = 1.0/(2.0 * outputDimensions); - float3 coordScale = (outputDimensions-1.0)/outputDimensions; + float3 coordBias = 1.0 / (2.0 * outputDimensions); + float3 coordScale = (outputDimensions - 1.0) / outputDimensions; lutCoordinate = (lutCoordinate * coordScale) + coordBias; float3 lutColor = PassSrg::m_lut.Sample(PassSrg::LinearSampler, lutCoordinate).rgb; // Apply the inverse of the shaper function to give the color in the working color space - float3 finalColor = lutColor; - if (PassSrg::m_shaperType == SHAPER_LINEAR) - { - finalColor = (lutColor - PassSrg::m_shaperBias)/PassSrg::m_shaperScale; - } - else if (PassSrg::m_shaperType == SHAPER_LOG2) - { - finalColor = pow(2.0, (lutColor - PassSrg::m_shaperBias)/PassSrg::m_shaperScale); - } + float3 finalColor = ShaperToLinear(lutColor, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale); OUT.m_color.rgb = finalColor; OUT.m_color.a = 1.0; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/BlendColorGradingLuts.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/BlendColorGradingLuts.azsl index 6f97996c29..746e18cfc2 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/BlendColorGradingLuts.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/BlendColorGradingLuts.azsl @@ -8,6 +8,7 @@ #include #include +#include ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback { @@ -62,40 +63,18 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback [[range(0, 4)]] option uint o_numSourceLuts = 0; -float3 ShaperToLinear(float3 shaperColor, ShaperType shaperType, float shaperBias, float shaperScale) -{ - // Apply the inverse of the shaper function to give the color in the working color space - float3 linearColor = shaperColor; - if (shaperType == ShaperType::ShaperLinear) - { - linearColor = (shaperColor - shaperBias)/shaperScale; - } - else if (shaperType == ShaperType::ShaperLog2) - { - linearColor = pow(2.0, (shaperColor - shaperBias)/shaperScale); - } - return linearColor; -} - -float3 LinearToShaper(float3 linearColor, ShaperType shaperType, float shaperBias, float shaperScale) -{ - // Convert from working color space to lut coordinates by applying the shaper function - float3 shaperColor = linearColor; - if (shaperType == ShaperType::ShaperLinear) - { - shaperColor = linearColor * shaperScale + shaperBias; - } - else if (shaperType == ShaperType::ShaperLog2) - { - shaperColor = log2(linearColor) * shaperScale + shaperBias; - } - return shaperColor; -} - float3 GetSourceLutLinearColor(float3 baseColor, Texture3D sourceLut, ShaperType shaperType, float shaperBias, float shaperScale) { // Convert from reference linearColor to the lutCoordinate for this Lut float3 lutCoord = LinearToShaper(baseColor, shaperType, shaperBias, shaperScale); + + // Adjust coordinate to the domain excluding the outer half texel in all directions + uint3 outputDimensions; + sourceLut.GetDimensions(outputDimensions.x, outputDimensions.y, outputDimensions.z); + float3 coordBias = 1.0 / (2.0 * outputDimensions); + float3 coordScale = (outputDimensions - 1.0) / outputDimensions; + lutCoord = (lutCoord * coordScale) + coordBias; + float3 lutColor = sourceLut.SampleLevel(PassSrg::LinearSampler, lutCoord, 0).rgb; // Convert to linear float3 linearColor = ShaperToLinear(lutColor, shaperType, shaperBias, shaperScale); @@ -115,11 +94,7 @@ void MainCS(uint3 dispatch_id: SV_DispatchThreadID) } // Get coordinates within the blended LUT 3D texture - float3 baseCoord = float3 ( - (float)(dispatch_id.x)/(float)PassSrg::m_blendedLutDimensions.x, - (float)(dispatch_id.y)/(float)PassSrg::m_blendedLutDimensions.y, - (float)(dispatch_id.z)/(float)PassSrg::m_blendedLutDimensions.z - ); + float3 baseCoord = float3(outPixel) / float3(PassSrg::m_blendedLutDimensions - 1.0); // Convert to the base linear color (this is the color of the identity LUT) float3 baseColor = ShaperToLinear(baseCoord, (ShaperType)PassSrg::m_blendedLutShaperType, PassSrg::m_blendedLutShaperBias, PassSrg::m_blendedLutShaperScale); diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/LookModificationTransform.azsl b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/LookModificationTransform.azsl index 8b1841c5e1..4741b01617 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/LookModificationTransform.azsl +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/LookModificationTransform.azsl @@ -12,6 +12,7 @@ #include #include #include +#include #include "EyeAdaptationUtil.azsli" ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback @@ -42,12 +43,111 @@ option bool o_enableExposureControlFeature = false; // Option shader variable to enable color grading LUT. option bool o_enableColorGradingLut = false; +// Controls the sampling quality of the blended LUT. Setting this higher can improve the quality of particularly tricky luts. +// 0 - linear +// 1 - 7 tap b-spline +// 2 - 19 tap b-spline +[[range(0, 2)]] +option uint o_lutSampleQuality = 0; + +// Sample a 3dtexture with a 7 or 19 tap B-Spline. Consider ripping this out and putting in a more general location. +// This function samples a 4x4x4 neighborhood around the uv. Normally this would take 64 samples, but by taking +// advantage of bilinear filtering this can be done with 27 taps on the edges between pixels. The cost is further +// reduced by dropping either the 8 corners (19 total taps) or also dropping the 12 edges (7 total taps). +float4 SampleBSpline3D(Texture3D texture, SamplerState linearSampler, float3 uv, float3 textureSize, float3 rcpTextureSize) +{ + // Think of sample locations in the 4x4 neighborhood as having a top left coordinate of 0,0 and + // a bottom right coordinate of 3,3. + + // Find the position in texture space then round it to get the center of the 1,1 pixel (tc1) + float3 texelPos = uv * textureSize; + float3 tc1= floor(texelPos - 0.5) + 0.5; + + // Offset from center position to texel + float3 f = texelPos - tc1; + + // Compute B-Spline weights based on the offset + float3 OneMinusF = (1.0 - f); + float3 OneMinusF2 = OneMinusF * OneMinusF; + float3 OneMinusF3 = OneMinusF2 * OneMinusF; + float3 w0 = OneMinusF3; + float3 w1 = 4.0 + 3.0 * f * f * f - 6.0 * f * f; + float3 w2 = 4.0 + 3.0 * OneMinusF3 - 6.0 * OneMinusF2; + float3 w3 = f * f * f; + + float3 w12 = w1 + w2; + + // Compute uv coordinates for sampling the texture + float3 tc0 = (tc1 - 1.0f) * rcpTextureSize; + float3 tc3 = (tc1 + 2.0f) * rcpTextureSize; + float3 tc12 = (tc1 + w2 / w12) * rcpTextureSize; + + // Compute sample weights + float sw0 = w12.x * w0.y * w12.z; + float sw1 = w0.x * w12.y * w12.z; + float sw2 = w12.x * w12.y * w12.z; + float sw3 = w3.x * w12.y * w12.z; + float sw4 = w12.x * w3.y * w12.z; + float sw5 = w12.x * w12.y * w0.z; + float sw6 = w12.x * w12.y * w3.z; + + // total weight of samples to normalize result. + float totalWeight = sw0 + sw1 + sw2 + sw3 + sw4 + sw5 + sw6; + + float4 result = 0.0f; + result += texture.SampleLevel(linearSampler, float3(tc12.x, tc0.y, tc12.z), 0.0) * sw0; + result += texture.SampleLevel(linearSampler, float3( tc0.x, tc12.y, tc12.z), 0.0) * sw1; + result += texture.SampleLevel(linearSampler, float3(tc12.x, tc12.y, tc12.z), 0.0) * sw2; + result += texture.SampleLevel(linearSampler, float3( tc3.x, tc12.y, tc12.z), 0.0) * sw3; + result += texture.SampleLevel(linearSampler, float3(tc12.x, tc3.y, tc12.z), 0.0) * sw4; + result += texture.SampleLevel(linearSampler, float3(tc12.x, tc12.y, tc0.z), 0.0) * sw5; + result += texture.SampleLevel(linearSampler, float3(tc12.x, tc12.y, tc3.z), 0.0) * sw6; + + if (o_lutSampleQuality == 2) + { + // Extra 12 taps for Diagonals to increase the quality further. + + float sw7 = w0.x * w0.y * w12.z; + float sw8 = w0.x * w3.y * w12.z; + float sw9 = w3.x * w0.y * w12.z; + float sw10 = w3.x * w3.y * w12.z; + + float sw11 = w12.x * w0.y * w0.z; + float sw12 = w12.x * w0.y * w3.z; + float sw13 = w12.x * w3.y * w0.z; + float sw14 = w12.x * w3.y * w3.z; + + float sw15 = w0.x * w12.y * w0.z; + float sw16 = w0.x * w12.y * w3.z; + float sw17 = w3.x * w12.y * w0.z; + float sw18 = w3.x * w12.y * w3.z; + + totalWeight += sw7 + sw8 + sw9 + sw10 + sw11 + sw12 + sw13 + sw14 + sw15 + sw16 + sw17 + sw18; + + result += texture.SampleLevel(linearSampler, float3(tc0.x, tc0.y, tc12.z), 0.0) * sw7; + result += texture.SampleLevel(linearSampler, float3(tc0.x, tc3.y, tc12.z), 0.0) * sw8; + result += texture.SampleLevel(linearSampler, float3(tc3.x, tc0.y, tc12.z), 0.0) * sw9; + result += texture.SampleLevel(linearSampler, float3(tc3.x, tc3.y, tc12.z), 0.0) * sw10; + + result += texture.SampleLevel(linearSampler, float3(tc12.x, tc0.y, tc0.z), 0.0) * sw11; + result += texture.SampleLevel(linearSampler, float3(tc12.x, tc0.y, tc3.z), 0.0) * sw12; + result += texture.SampleLevel(linearSampler, float3(tc12.x, tc3.y, tc0.z), 0.0) * sw13; + result += texture.SampleLevel(linearSampler, float3(tc12.x, tc3.y, tc3.z), 0.0) * sw14; + + result += texture.SampleLevel(linearSampler, float3(tc0.x, tc12.y, tc0.z), 0.0) * sw15; + result += texture.SampleLevel(linearSampler, float3(tc0.x, tc12.y, tc3.z), 0.0) * sw16; + result += texture.SampleLevel(linearSampler, float3(tc3.x, tc12.y, tc0.z), 0.0) * sw17; + result += texture.SampleLevel(linearSampler, float3(tc3.x, tc12.y, tc3.z), 0.0) * sw18; + } + return result / totalWeight; +} + PSOutput MainPS(VSOutput IN) { PSOutput OUT; // Fetch the pixel color from the input texture - float3 color = PassSrg::m_framebuffer.Sample(PassSrg::LinearSampler, IN.m_texCoord).rgb; + float3 color = PassSrg::m_framebuffer.SampleLevel(PassSrg::LinearSampler, IN.m_texCoord, 0.0).rgb; if (o_enableExposureControlFeature) { @@ -63,36 +163,28 @@ PSOutput MainPS(VSOutput IN) if (o_enableColorGradingLut) { // Convert from working color space to lut coordinates by applying the shaper function - float3 lutCoordinate = color; - if (shaperType == ShaperType::ShaperLinear) - { - lutCoordinate = color * PassSrg::m_shaperScale + PassSrg::m_shaperBias; - } - else if (shaperType == ShaperType::ShaperLog2) - { - lutCoordinate = log2(color) * PassSrg::m_shaperScale + PassSrg::m_shaperBias; - } + float3 lutCoordinate = LinearToShaper(color, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale); // Adjust coordinate to the domain excluding the outer half texel in all directions uint3 outputDimensions; PassSrg::m_gradingLut.GetDimensions(outputDimensions.x, outputDimensions.y, outputDimensions.z); float3 coordBias = 0.5f / outputDimensions; - float3 coordScale = (outputDimensions-1.0)/outputDimensions; + float3 sizeMinusOne = outputDimensions - 1.0; + float3 coordScale = sizeMinusOne / outputDimensions; lutCoordinate = (lutCoordinate * coordScale) + coordBias; - float3 lutColor = PassSrg::m_gradingLut.Sample(PassSrg::LinearSampler, lutCoordinate).rgb; + float3 lutColor = float3(0.0, 0.0, 0.0); + if (o_lutSampleQuality == 0) + { + lutColor = PassSrg::m_gradingLut.SampleLevel(PassSrg::LinearSampler, lutCoordinate, 0.0).rgb; + } + else + { + lutColor = SampleBSpline3D(PassSrg::m_gradingLut, PassSrg::LinearSampler, lutCoordinate, float3(outputDimensions), 1.0 / float3(outputDimensions)).rgb; + } // Apply the inverse of the shaper function to give the color in the working color space - float3 finalColor = lutColor; - if (shaperType == ShaperType::ShaperLinear) - { - finalColor = (lutColor - PassSrg::m_shaperBias)/PassSrg::m_shaperScale; - } - else if (shaperType == ShaperType::ShaperLog2) - { - finalColor = pow(2.0, (lutColor - PassSrg::m_shaperBias)/PassSrg::m_shaperScale); - } - color = finalColor; + color = ShaperToLinear(lutColor, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale); } OUT.m_color.rgb = color; diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/LookModificationTransform.shadervariantlist b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/LookModificationTransform.shadervariantlist index 0de004bde6..88d6bf9b4e 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/LookModificationTransform.shadervariantlist +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/LookModificationTransform.shadervariantlist @@ -1,9 +1,13 @@ { "Shader" : "LookModificationTransform.shader", "Variants" : [ - { "StableId": 1, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "true" } }, - { "StableId": 2, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "false" } }, - { "StableId": 3, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "true" } }, - { "StableId": 4, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "false" } } + { "StableId": 1, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "false" } }, + { "StableId": 2, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "false" } }, + { "StableId": 3, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 0 } }, + { "StableId": 4, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 0 } }, + { "StableId": 5, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 1 } }, + { "StableId": 6, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 1 } }, + { "StableId": 7, "Options" : { "o_enableExposureControlFeature": "true", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 2 } }, + { "StableId": 8, "Options" : { "o_enableExposureControlFeature": "false", "o_enableColorGradingLut": "true", "o_lutSampleQuality": 2 } } ] } diff --git a/Gems/Atom/Feature/Common/Code/3rdParty/ACES/ACES/Aces.cpp b/Gems/Atom/Feature/Common/Code/3rdParty/ACES/ACES/Aces.cpp index 125614235f..79f16ee628 100644 --- a/Gems/Atom/Feature/Common/Code/3rdParty/ACES/ACES/Aces.cpp +++ b/Gems/Atom/Feature/Common/Code/3rdParty/ACES/ACES/Aces.cpp @@ -203,47 +203,37 @@ namespace AZ return ODT_48nits; } + ShaperParams GetLog2ShaperParameters(float minStops, float maxStops) + { + ShaperParams shaperParams; + + constexpr float Log2MediumGray = -2.47393118833f; // log2f(0.18f); + shaperParams.m_type = ShaperType::Log2; + shaperParams.m_scale = 1.0f / (maxStops - minStops); + shaperParams.m_bias = -((minStops + Log2MediumGray) * shaperParams.m_scale); + + return shaperParams; + } + ShaperParams GetAcesShaperParameters(OutputDeviceTransformType odtType) { AZ_Assert(static_cast(odtType) < static_cast(NumOutputDeviceTransformTypes), "Invalid ODT type specified."); - ShaperParams shaperParams; - - // These values represent and low and high end of the dynamic range in terms of stops from middle grey (0.18) - float lowerDynamicRangeInStops; - float higherDynamicRangeInStops; - const float MIDDLE_GREY = 0.18f; - switch (odtType) { case OutputDeviceTransformType_48Nits: - lowerDynamicRangeInStops = -6.5f; - higherDynamicRangeInStops = 6.5f; - break; + return GetLog2ShaperParameters(-6.5f, 6.5f); case OutputDeviceTransformType_1000Nits: - lowerDynamicRangeInStops = -12.f; - higherDynamicRangeInStops = 10.f; - break; + return GetLog2ShaperParameters(-12.0f, 10.0f); case OutputDeviceTransformType_2000Nits: - lowerDynamicRangeInStops = -12.f; - higherDynamicRangeInStops = 11.f; - break; + return GetLog2ShaperParameters(-12.0f, 11.0f); case OutputDeviceTransformType_4000Nits: - lowerDynamicRangeInStops = -12.f; - higherDynamicRangeInStops = 12.f; - break; + return GetLog2ShaperParameters(-12.0f, 12.0f); default: AZ_Assert(false, "Invalid output device transform type."); - return shaperParams; break; } - - float logMin = log2(MIDDLE_GREY * exp2(lowerDynamicRangeInStops)); - float logMax = log2(MIDDLE_GREY * exp2(higherDynamicRangeInStops)); - shaperParams.scale = 1.0f / (logMax - logMin); - shaperParams.bias = -shaperParams.scale * logMin; - shaperParams.type = ShaperType::Log2; - return shaperParams; + return ShaperParams(); } Matrix3x3 GetColorConvertionMatrix(ColorConvertionMatrixType type) diff --git a/Gems/Atom/Feature/Common/Code/3rdParty/ACES/ACES/Aces.h b/Gems/Atom/Feature/Common/Code/3rdParty/ACES/ACES/Aces.h index 94d6434b93..3ec2885a99 100644 --- a/Gems/Atom/Feature/Common/Code/3rdParty/ACES/ACES/Aces.h +++ b/Gems/Atom/Feature/Common/Code/3rdParty/ACES/ACES/Aces.h @@ -124,18 +124,19 @@ namespace AZ NumColorConvertionMatrixTypes }; - enum ShaperType + enum class ShaperType : uint32_t { Linear = 0, Log2 = 1, + PqSmpteSt2084 = 2, NumShaperTypes }; struct ShaperParams { - ShaperType type = ShaperType::Linear; - float bias = 0.f; - float scale = 1.f; + ShaperType m_type = ShaperType::Linear; + float m_bias = 0.0f; + float m_scale = 1.0f; }; enum class DisplayMapperOperationType : uint32_t @@ -151,10 +152,14 @@ namespace AZ enum class ShaperPresetType { None = 0, - Log2_48_nits, - Log2_1000_nits, - Log2_2000_nits, - Log2_4000_nits + LinearCustomRange, + Log2_48Nits, + Log2_1000Nits, + Log2_2000Nits, + Log2_4000Nits, + Log2CustomRange, + PqSmpteSt2084, + NumShaperTypes }; enum class ToneMapperType @@ -171,6 +176,7 @@ namespace AZ }; SegmentedSplineParamsC9 GetAcesODTParameters(OutputDeviceTransformType odtType); + ShaperParams GetLog2ShaperParameters(float minStops, float maxStops); ShaperParams GetAcesShaperParameters(OutputDeviceTransformType odtType); Matrix3x3 GetColorConvertionMatrix(ColorConvertionMatrixType type); diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h index 7d2aa18f29..6be81c8cae 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h @@ -78,7 +78,7 @@ namespace AZ static OutputDeviceTransformType GetOutputDeviceTransformType(RHI::Format bufferFormat); static void GetAcesDisplayMapperParameters(DisplayMapperParameters* displayMapperParameters, OutputDeviceTransformType odtType); - static ShaperParams GetShaperParameters(ShaperPresetType shaperPreset); + static ShaperParams GetShaperParameters(ShaperPresetType shaperPreset, float customMinEv = 0.0f, float customMaxEv = 0.0f); static void GetDefaultDisplayMapperConfiguration(DisplayMapperConfigurationDescriptor& config); // DisplayMapperFeatureProcessorInteface overrides... @@ -102,8 +102,6 @@ namespace AZ static constexpr const char* FeatureProcessorName = "AcesDisplayMapperFeatureProcessor"; - static const int LutSize = 32; - static const RHI::Format LutFormat = RHI::Format::R16G16B16A16_FLOAT; static const int ImagePoolBudget = 1 << 20; // 1 Megabyte // LUTs that are baked through shaders diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/PostProcess/LookModification/LookModificationParams.inl b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/PostProcess/LookModification/LookModificationParams.inl index 38a1987caa..4c3cf8ac92 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/PostProcess/LookModification/LookModificationParams.inl +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/PostProcess/LookModification/LookModificationParams.inl @@ -10,11 +10,9 @@ // PARAM(NAME, MEMBER_NAME, DEFAULT_VALUE, ...) AZ_GFX_BOOL_PARAM(Enabled, m_enabled, false) - AZ_GFX_COMMON_PARAM(Data::Asset, ColorGradingLut, m_colorGradingLut, {}) - -AZ_GFX_COMMON_PARAM(AZ::Render::ShaperPresetType, ShaperPresetType, m_shaperPresetType, AZ::Render::ShaperPresetType::Log2_48_nits) - +AZ_GFX_COMMON_PARAM(AZ::Render::ShaperPresetType, ShaperPresetType, m_shaperPresetType, AZ::Render::ShaperPresetType::Log2_48Nits) +AZ_GFX_COMMON_PARAM(float, CustomMinExposure, m_customMinExposure, -6.5) +AZ_GFX_COMMON_PARAM(float, CustomMaxExposure, m_customMaxExposure, 6.5) AZ_GFX_FLOAT_PARAM(ColorGradingLutIntensity, m_colorGradingLutIntensity, 1.0) - AZ_GFX_FLOAT_PARAM(ColorGradingLutOverride, m_colorGradingLutOverride, 1.0) diff --git a/Gems/Atom/Feature/Common/Code/Source/ACES/AcesDisplayMapperFeatureProcessor.cpp b/Gems/Atom/Feature/Common/Code/Source/ACES/AcesDisplayMapperFeatureProcessor.cpp index 4c057aab2a..c6c41edb31 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ACES/AcesDisplayMapperFeatureProcessor.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ACES/AcesDisplayMapperFeatureProcessor.cpp @@ -21,6 +21,7 @@ namespace { + static const AZ::RHI::Format LutFormat = AZ::RHI::Format::R16G16B16A16_FLOAT; uint16_t ConvertFloatToHalf(const float Value) { @@ -56,395 +57,409 @@ namespace } } -namespace AZ +namespace AZ::Render { - namespace Render + void AcesDisplayMapperFeatureProcessor::Reflect(ReflectContext* context) { - void AcesDisplayMapperFeatureProcessor::Reflect(ReflectContext* context) + if (auto* serializeContext = azrtti_cast(context)) { - if (auto* serializeContext = azrtti_cast(context)) - { - serializeContext - ->Class() - ->Version(0); - } + serializeContext + ->Class() + ->Version(0); + } + } + + void AcesDisplayMapperFeatureProcessor::Activate() + { + GetDefaultDisplayMapperConfiguration(m_displayMapperConfiguration); + } + + void AcesDisplayMapperFeatureProcessor::Deactivate() + { + m_ownedLuts.clear(); + } + + void AcesDisplayMapperFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet) + { + AZ_TRACE_METHOD(); + AZ_UNUSED(packet); + } + + void AcesDisplayMapperFeatureProcessor::Render([[maybe_unused]] const FeatureProcessor::RenderPacket& packet) + { + } + + void AcesDisplayMapperFeatureProcessor::ApplyLdrOdtParameters(DisplayMapperParameters* displayMapperParameters) + { + AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer."); + if (displayMapperParameters == nullptr) + { + return; } - void AcesDisplayMapperFeatureProcessor::Activate() + // These values in the ODT parameter are taken from the reference ACES transform. + // + // The original ACES references. + // Common: + // https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl + // For sRGB: + // https://github.com/ampas/aces-dev/tree/master/transforms/ctl/odt/sRGB + displayMapperParameters->m_cinemaLimits[0] = 0.02f; + displayMapperParameters->m_cinemaLimits[1] = 48.0f; + displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(OutputDeviceTransformType_48Nits); + displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65; + displayMapperParameters->m_OutputDisplayTransformMode = Srgb; + ColorConvertionMatrixType colorMatrixType = XYZ_To_Rec709; + switch (displayMapperParameters->m_OutputDisplayTransformMode) { - GetDefaultDisplayMapperConfiguration(m_displayMapperConfiguration); + case Srgb: + colorMatrixType = XYZ_To_Rec709; + break; + case PerceptualQuantizer: + case Ldr: + colorMatrixType = XYZ_To_Bt2020; + break; + default: + break; + } + displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType); + + displayMapperParameters->m_surroundGamma = 0.9811f; + displayMapperParameters->m_gamma = 2.2f; + } + + void AcesDisplayMapperFeatureProcessor::ApplyHdrOdtParameters(DisplayMapperParameters* displayMapperParameters, const OutputDeviceTransformType& odtType) + { + AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer."); + if (displayMapperParameters == nullptr) + { + return; } - void AcesDisplayMapperFeatureProcessor::Deactivate() + // Dynamic range limit values taken from NVIDIA HDR sample. + // These values represent and low and high end of the dynamic range in terms of stops from middle grey (0.18) + float lowerDynamicRangeInStops = -12.f; + float higherDynamicRangeInStops = 10.f; + const float MIDDLE_GREY = 0.18f; + + switch (odtType) { - m_ownedLuts.clear(); + case OutputDeviceTransformType_1000Nits: + higherDynamicRangeInStops = 10.f; + break; + case OutputDeviceTransformType_2000Nits: + higherDynamicRangeInStops = 11.f; + break; + case OutputDeviceTransformType_4000Nits: + higherDynamicRangeInStops = 12.f; + break; + default: + AZ_Assert(false, "Invalid output device transform type."); + break; } - void AcesDisplayMapperFeatureProcessor::Simulate(const FeatureProcessor::SimulatePacket& packet) + displayMapperParameters->m_cinemaLimits[0] = MIDDLE_GREY * exp2(lowerDynamicRangeInStops); + displayMapperParameters->m_cinemaLimits[1] = MIDDLE_GREY * exp2(higherDynamicRangeInStops); + displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(odtType); + displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65; + displayMapperParameters->m_OutputDisplayTransformMode = PerceptualQuantizer; + ColorConvertionMatrixType colorMatrixType = XYZ_To_Bt2020; + displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType); + + // Surround gamma value is from the dim surround gamma from the ACES reference transforms. + // https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl + displayMapperParameters->m_surroundGamma = 0.9811f; + displayMapperParameters->m_gamma = 1.0f; // gamma not used with perceptual quantizer, but just set to 1.0 anyways + } + + OutputDeviceTransformType AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(RHI::Format bufferFormat) + { + OutputDeviceTransformType outputDeviceTransformType = OutputDeviceTransformType_48Nits; + if (bufferFormat == RHI::Format::R8G8B8A8_UNORM || + bufferFormat == RHI::Format::B8G8R8A8_UNORM) { - AZ_TRACE_METHOD(); - AZ_UNUSED(packet); + outputDeviceTransformType = OutputDeviceTransformType_48Nits; + } + else if (bufferFormat == RHI::Format::R10G10B10A2_UNORM) + { + outputDeviceTransformType = OutputDeviceTransformType_1000Nits; + } + else + { + AZ_Assert(false, "Not yet supported."); + // To work normally on unsupported environment, initialize the display parameters by OutputDeviceTransformType_48Nits. + outputDeviceTransformType = OutputDeviceTransformType_48Nits; + } + return outputDeviceTransformType; + } + + void AcesDisplayMapperFeatureProcessor::GetAcesDisplayMapperParameters(DisplayMapperParameters* displayMapperParameters, OutputDeviceTransformType odtType) + { + switch (odtType) + { + case OutputDeviceTransformType_48Nits: + ApplyLdrOdtParameters(displayMapperParameters); + break; + case OutputDeviceTransformType_1000Nits: + case OutputDeviceTransformType_2000Nits: + case OutputDeviceTransformType_4000Nits: + ApplyHdrOdtParameters(displayMapperParameters, odtType); + break; + default: + AZ_Assert(false, "This ODT type[%d] is not supported.", odtType); + break; + } + } + + void AcesDisplayMapperFeatureProcessor::GetOwnedLut(DisplayMapperLut& displayMapperLut, const AZ::Name& lutName) + { + auto it = m_ownedLuts.find(lutName); + if (it == m_ownedLuts.end()) + { + InitializeLutImage(lutName); + it = m_ownedLuts.find(lutName); + AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create LUT %s", lutName.GetCStr()); + } + displayMapperLut = it->second; + } + + void AcesDisplayMapperFeatureProcessor::GetDisplayMapperLut(DisplayMapperLut& displayMapperLut) + { + const AZ::Name acesLutName("AcesLutImage"); + auto it = m_ownedLuts.find(acesLutName); + if (it == m_ownedLuts.end()) + { + InitializeLutImage(acesLutName); + + it = m_ownedLuts.find(acesLutName); + AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create ACES LUT image"); + } + displayMapperLut = it->second; + } + + void AcesDisplayMapperFeatureProcessor::GetLutFromAssetLocation(DisplayMapperAssetLut& displayMapperAssetLut, const AZStd::string& assetPath) + { + Data::AssetId assetId = RPI::AssetUtils::GetAssetIdForProductPath(assetPath.c_str(), RPI::AssetUtils::TraceLevel::Error); + GetLutFromAssetId(displayMapperAssetLut, assetId); + } + + void AcesDisplayMapperFeatureProcessor::GetLutFromAssetId(DisplayMapperAssetLut& displayMapperAssetLut, const AZ::Data::AssetId assetId) + { + if (!assetId.IsValid()) + { + return; } - void AcesDisplayMapperFeatureProcessor::Render([[maybe_unused]] const FeatureProcessor::RenderPacket& packet) + // Check first if this already exists + auto it = m_assetLuts.find(assetId.ToString()); + if (it != m_assetLuts.end()) { + displayMapperAssetLut = it->second; + return; } - void AcesDisplayMapperFeatureProcessor::ApplyLdrOdtParameters(DisplayMapperParameters* displayMapperParameters) + // Read the lut which is a .3dl file embedded within an azasset file. + Data::Asset asset = RPI::AssetUtils::LoadAssetById(assetId, RPI::AssetUtils::TraceLevel::Error); + const LookupTableAsset* lutAsset = RPI::GetDataFromAnyAsset(asset); + + if (lutAsset == nullptr) { - AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer."); - if (displayMapperParameters == nullptr) - { - return; - } - - // These values in the ODT parameter are taken from the reference ACES transform. - // - // The original ACES references. - // Common: - // https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl - // For sRGB: - // https://github.com/ampas/aces-dev/tree/master/transforms/ctl/odt/sRGB - displayMapperParameters->m_cinemaLimits[0] = 0.02f; - displayMapperParameters->m_cinemaLimits[1] = 48.0f; - displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(OutputDeviceTransformType_48Nits); - displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65; - displayMapperParameters->m_OutputDisplayTransformMode = Srgb; - ColorConvertionMatrixType colorMatrixType = XYZ_To_Rec709; - switch (displayMapperParameters->m_OutputDisplayTransformMode) - { - case Srgb: - colorMatrixType = XYZ_To_Rec709; - break; - case PerceptualQuantizer: - case Ldr: - colorMatrixType = XYZ_To_Bt2020; - break; - default: - break; - } - displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType); - - displayMapperParameters->m_surroundGamma = 0.9811f; - displayMapperParameters->m_gamma = 2.2f; + AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Unable to read LUT from asset."); + asset.Release(); + return; } - void AcesDisplayMapperFeatureProcessor::ApplyHdrOdtParameters(DisplayMapperParameters* displayMapperParameters, const OutputDeviceTransformType& odtType) + // The first row of numbers in a 3dl file is a number of vertices that partition the space from [0,..1023] + // This assumes that the vertices are evenly spaced apart. Non-uniform spacing is supported by the format, + // but haven't been encountered yet. + const size_t lutSize = lutAsset->m_intervals.size(); + + if (lutSize == 0) { - AZ_Assert(displayMapperParameters != nullptr, "The pOutParameters must not to be null pointer."); - if (displayMapperParameters == nullptr) - { - return; - } - - // Dynamic range limit values taken from NVIDIA HDR sample. - // These values represent and low and high end of the dynamic range in terms of stops from middle grey (0.18) - float lowerDynamicRangeInStops = -12.f; - float higherDynamicRangeInStops = 10.f; - const float MIDDLE_GREY = 0.18f; - - switch (odtType) - { - case OutputDeviceTransformType_1000Nits: - higherDynamicRangeInStops = 10.f; - break; - case OutputDeviceTransformType_2000Nits: - higherDynamicRangeInStops = 11.f; - break; - case OutputDeviceTransformType_4000Nits: - higherDynamicRangeInStops = 12.f; - break; - default: - AZ_Assert(false, "Invalid output device transform type."); - break; - } - - displayMapperParameters->m_cinemaLimits[0] = MIDDLE_GREY * exp2(lowerDynamicRangeInStops); - displayMapperParameters->m_cinemaLimits[1] = MIDDLE_GREY * exp2(higherDynamicRangeInStops); - displayMapperParameters->m_acesSplineParams = GetAcesODTParameters(odtType); - displayMapperParameters->m_OutputDisplayTransformFlags = AlterSurround | ApplyDesaturation | ApplyCATD60toD65; - displayMapperParameters->m_OutputDisplayTransformMode = PerceptualQuantizer; - ColorConvertionMatrixType colorMatrixType = XYZ_To_Bt2020; - displayMapperParameters->m_XYZtoDisplayPrimaries = GetColorConvertionMatrix(colorMatrixType); - - // Surround gamma value is from the dim surround gamma from the ACES reference transforms. - // https://github.com/ampas/aces-dev/blob/master/transforms/ctl/lib/ACESlib.ODT_Common.ctl - displayMapperParameters->m_surroundGamma = 0.9811f; - displayMapperParameters->m_gamma = 1.0f; // gamma not used with perceptual quantizer, but just set to 1.0 anyways + AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Lut asset has invalid size."); + asset.Release(); + return; } - OutputDeviceTransformType AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(RHI::Format bufferFormat) + // Create a buffer of half floats from the LUT and use it to initialize a 3d texture. + + const size_t kChannels = 4; + const size_t kChannelBytes = 2; + const size_t bytesPerRow = lutSize * kChannels * kChannelBytes; + const size_t bytesPerSlice = bytesPerRow * lutSize; + + AZStd::vector u16Buffer; + const size_t bufferSize = lutSize * lutSize * lutSize * kChannels; + u16Buffer.resize(bufferSize); + + for (size_t slice = 0; slice < lutSize; slice++) { - OutputDeviceTransformType outputDeviceTransformType = OutputDeviceTransformType_48Nits; - if (bufferFormat == RHI::Format::R8G8B8A8_UNORM || - bufferFormat == RHI::Format::B8G8R8A8_UNORM) + for (size_t column = 0; column < lutSize; column++) { - outputDeviceTransformType = OutputDeviceTransformType_48Nits; - } - else if (bufferFormat == RHI::Format::R10G10B10A2_UNORM) - { - outputDeviceTransformType = OutputDeviceTransformType_1000Nits; - } - else - { - AZ_Assert(false, "Not yet supported."); - // To work normally on unsupported environment, initialize the display parameters by OutputDeviceTransformType_48Nits. - outputDeviceTransformType = OutputDeviceTransformType_48Nits; - } - return outputDeviceTransformType; - } - - void AcesDisplayMapperFeatureProcessor::GetAcesDisplayMapperParameters(DisplayMapperParameters* displayMapperParameters, OutputDeviceTransformType odtType) - { - switch (odtType) - { - case OutputDeviceTransformType_48Nits: - ApplyLdrOdtParameters(displayMapperParameters); - break; - case OutputDeviceTransformType_1000Nits: - case OutputDeviceTransformType_2000Nits: - case OutputDeviceTransformType_4000Nits: - ApplyHdrOdtParameters(displayMapperParameters, odtType); - break; - default: - AZ_Assert(false, "This ODT type[%d] is not supported.", odtType); - break; - } - } - - void AcesDisplayMapperFeatureProcessor::GetOwnedLut(DisplayMapperLut& displayMapperLut, const AZ::Name& lutName) - { - auto it = m_ownedLuts.find(lutName); - if (it == m_ownedLuts.end()) - { - InitializeLutImage(lutName); - it = m_ownedLuts.find(lutName); - AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create LUT %s", lutName.GetCStr()); - } - displayMapperLut = it->second; - } - - void AcesDisplayMapperFeatureProcessor::GetDisplayMapperLut(DisplayMapperLut& displayMapperLut) - { - const AZ::Name acesLutName("AcesLutImage"); - auto it = m_ownedLuts.find(acesLutName); - if (it == m_ownedLuts.end()) - { - InitializeLutImage(acesLutName); - - it = m_ownedLuts.find(acesLutName); - AZ_Assert(it != m_ownedLuts.end(), "AcesDisplayMapperFeatureProcessor unable to create ACES LUT image"); - } - displayMapperLut = it->second; - } - - void AcesDisplayMapperFeatureProcessor::GetLutFromAssetLocation(DisplayMapperAssetLut& displayMapperAssetLut, const AZStd::string& assetPath) - { - Data::AssetId assetId = RPI::AssetUtils::GetAssetIdForProductPath(assetPath.c_str(), RPI::AssetUtils::TraceLevel::Error); - GetLutFromAssetId(displayMapperAssetLut, assetId); - } - - void AcesDisplayMapperFeatureProcessor::GetLutFromAssetId(DisplayMapperAssetLut& displayMapperAssetLut, const AZ::Data::AssetId assetId) - { - if (!assetId.IsValid()) - { - return; - } - - // Check first if this already exists - auto it = m_assetLuts.find(assetId.ToString()); - if (it != m_assetLuts.end()) - { - displayMapperAssetLut = it->second; - return; - } - - // Read the lut which is a .3dl file embedded within an azasset file. - Data::Asset asset = RPI::AssetUtils::LoadAssetById(assetId, RPI::AssetUtils::TraceLevel::Error); - const LookupTableAsset* lutAsset = RPI::GetDataFromAnyAsset(asset); - - if (lutAsset == nullptr) - { - AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Unable to read LUT from asset."); - asset.Release(); - return; - } - - // The first row of numbers in a 3dl file is a number of vertices that partition the space from [0,..1023] - // This assumes that the vertices are evenly spaced apart. Non-uniform spacing is supported by the format, - // but haven't been encountered yet. - uint32_t lutSize = static_cast(lutAsset->m_intervals.size()); - - if (lutSize == 0) - { - AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Lut asset has invalid size."); - asset.Release(); - return; - } - - // The vertices in the file are given as a positive integer value in [0,..4095] and need to be normalized - // and stored into a linear unaligned buffer used to initialize the streaming image. - const float normalizeValue = 4095.0f; - const int kChannels = 4; - const int kChannelBytes = 2; - int bytesPerRow = lutSize * kChannels * kChannelBytes; - int bytesPerSlice = bytesPerRow * lutSize; - - AZStd::vector u16Buffer; - size_t bufferSize = (bytesPerSlice * lutSize) / sizeof(uint16_t); - u16Buffer.resize(bufferSize); - uint16_t* data = u16Buffer.data(); - for (int slice = 0; slice < (int)lutSize; slice++) - { - for (int column = 0; column < (int)lutSize; column++) + for (size_t row = 0; row < lutSize; row++) { - for (int row = 0; row < (int)lutSize; row++) - { - // Index in the LUT texture data - int idx = (column * kChannels) + - (bytesPerRow * row / sizeof(uint16_t)) + - ((bytesPerSlice * slice) / sizeof(uint16_t)); + // Index in the LUT texture data + size_t idx = (column * kChannels) + + ((bytesPerRow * row) / kChannelBytes) + + ((bytesPerSlice * slice) / kChannelBytes); - // Vertices the .3dl file are listed first by increasing slice, then row, and finally column coordinate - // This corresponds to blue, green, and red channels, respectively. - int assetIdx = slice + lutSize * row + (lutSize * lutSize * column); + // Vertices the .3dl file are listed first by increasing slice, then row, and finally column coordinate + // This corresponds to blue, green, and red channels, respectively. + size_t assetIdx = slice + lutSize * row + (lutSize * lutSize * column); - AZ::u64 red = lutAsset->m_values[assetIdx * 3 + 0]; - AZ::u64 green = lutAsset->m_values[assetIdx * 3 + 1]; - AZ::u64 blue = lutAsset->m_values[assetIdx * 3 + 2]; - data[idx + 0] = ConvertFloatToHalf(static_cast(red) / normalizeValue); - data[idx + 1] = ConvertFloatToHalf(static_cast(green) / normalizeValue); - data[idx + 2] = ConvertFloatToHalf(static_cast(blue) / normalizeValue); - data[idx + 3] = 0x3b00; // 1.0 in half - } + AZ::u64 red = lutAsset->m_values[assetIdx * 3 + 0]; + AZ::u64 green = lutAsset->m_values[assetIdx * 3 + 1]; + AZ::u64 blue = lutAsset->m_values[assetIdx * 3 + 2]; + + // The vertices in the file are given as a positive integer value in [0,..4095] and need to be normalized + constexpr float NormalizeValue = 4095.0f; + + u16Buffer[idx + 0] = ConvertFloatToHalf(static_cast(red) / NormalizeValue); + u16Buffer[idx + 1] = ConvertFloatToHalf(static_cast(green) / NormalizeValue); + u16Buffer[idx + 2] = ConvertFloatToHalf(static_cast(blue) / NormalizeValue); + u16Buffer[idx + 3] = 0x3b00; // 1.0 in half } } - - asset.Release(); - - Data::Instance streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool(); - - RHI::Size imageSize; - imageSize.m_width = static_cast(lutSize); - imageSize.m_height = static_cast(lutSize); - imageSize.m_depth = static_cast(lutSize); - size_t imageDataSize = bytesPerSlice * lutSize; - - Data::Instance lutStreamingImage = RPI::StreamingImage::CreateFromCpuData( - *streamingImagePool, RHI::ImageDimension::Image3D, imageSize, LutFormat, data, imageDataSize); - - AZ_Error("AcesDisplayMapperFeatureProcessor", lutStreamingImage, "Failed to initialize the lut assetId %s.", assetId.ToString().c_str()); - - DisplayMapperAssetLut assetLut; - assetLut.m_lutStreamingImage = lutStreamingImage; - - // Add to the list of LUT asset resources - m_assetLuts.insert(AZStd::pair(assetId.ToString(), assetLut)); - displayMapperAssetLut = assetLut; } - void AcesDisplayMapperFeatureProcessor::InitializeImagePool() + asset.Release(); + + Data::Instance streamingImagePool = RPI::ImageSystemInterface::Get()->GetSystemStreamingPool(); + + RHI::Size imageSize; + imageSize.m_width = static_cast(lutSize); + imageSize.m_height = static_cast(lutSize); + imageSize.m_depth = static_cast(lutSize); + size_t imageDataSize = bytesPerSlice * lutSize; + + Data::Instance lutStreamingImage = RPI::StreamingImage::CreateFromCpuData( + *streamingImagePool, RHI::ImageDimension::Image3D, imageSize, LutFormat, u16Buffer.data(), imageDataSize); + + AZ_Error("AcesDisplayMapperFeatureProcessor", lutStreamingImage, "Failed to initialize the lut assetId %s.", assetId.ToString().c_str()); + + DisplayMapperAssetLut assetLut; + assetLut.m_lutStreamingImage = lutStreamingImage; + + // Add to the list of LUT asset resources + m_assetLuts.insert(AZStd::pair(assetId.ToString(), assetLut)); + displayMapperAssetLut = assetLut; + } + + void AcesDisplayMapperFeatureProcessor::InitializeImagePool() + { + AZ::RHI::Factory& factory = RHI::Factory::Get(); + m_displayMapperImagePool = factory.CreateImagePool(); + m_displayMapperImagePool->SetName(Name("DisplayMapperImagePool")); + + RHI::ImagePoolDescriptor imagePoolDesc = {}; + imagePoolDesc.m_bindFlags = RHI::ImageBindFlags::ShaderReadWrite; + imagePoolDesc.m_budgetInBytes = ImagePoolBudget; + + RHI::Device* device = RHI::RHISystemInterface::Get()->GetDevice(); + RHI::ResultCode resultCode = m_displayMapperImagePool->Init(*device, imagePoolDesc); + if (resultCode != RHI::ResultCode::Success) { - AZ::RHI::Factory& factory = RHI::Factory::Get(); - m_displayMapperImagePool = factory.CreateImagePool(); - m_displayMapperImagePool->SetName(Name("DisplayMapperImagePool")); - - RHI::ImagePoolDescriptor imagePoolDesc = {}; - imagePoolDesc.m_bindFlags = RHI::ImageBindFlags::ShaderReadWrite; - imagePoolDesc.m_budgetInBytes = ImagePoolBudget; - - RHI::Device* device = RHI::RHISystemInterface::Get()->GetDevice(); - RHI::ResultCode resultCode = m_displayMapperImagePool->Init(*device, imagePoolDesc); - if (resultCode != RHI::ResultCode::Success) - { - AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize image pool."); - return; - } + AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize image pool."); + return; } + } - void AcesDisplayMapperFeatureProcessor::InitializeLutImage(const AZ::Name& lutName) + void AcesDisplayMapperFeatureProcessor::InitializeLutImage(const AZ::Name& lutName) + { + if (!m_displayMapperImagePool) { - if (!m_displayMapperImagePool) - { - InitializeImagePool(); - } - - DisplayMapperLut lutResource; - lutResource.m_lutImage = RHI::Factory::Get().CreateImage(); - lutResource.m_lutImage->SetName(lutName); - - RHI::ImageInitRequest imageRequest; - imageRequest.m_image = lutResource.m_lutImage.get(); - imageRequest.m_descriptor = RHI::ImageDescriptor::Create3D(RHI::ImageBindFlags::ShaderReadWrite, LutSize, LutSize, LutSize, LutFormat); - RHI::ResultCode resultCode = m_displayMapperImagePool->InitImage(imageRequest); - - if (resultCode != RHI::ResultCode::Success) - { - AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image."); - return; - } - - lutResource.m_lutImageViewDescriptor = RHI::ImageViewDescriptor::Create(LutFormat, 0, 0); - lutResource.m_lutImageView = lutResource.m_lutImage->GetImageView(lutResource.m_lutImageViewDescriptor); - if (!lutResource.m_lutImageView.get()) - { - AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image view."); - return; - } - - // Add to the list of lut resources - lutResource.m_lutImageView->SetName(lutName); - m_ownedLuts[lutName] = lutResource; + InitializeImagePool(); } - ShaperParams AcesDisplayMapperFeatureProcessor::GetShaperParameters(ShaperPresetType shaperPreset) + DisplayMapperLut lutResource; + lutResource.m_lutImage = RHI::Factory::Get().CreateImage(); + lutResource.m_lutImage->SetName(lutName); + + RHI::ImageInitRequest imageRequest; + imageRequest.m_image = lutResource.m_lutImage.get(); + static const int LutSize = 32; + imageRequest.m_descriptor = RHI::ImageDescriptor::Create3D(RHI::ImageBindFlags::ShaderReadWrite, LutSize, LutSize, LutSize, LutFormat); + RHI::ResultCode resultCode = m_displayMapperImagePool->InitImage(imageRequest); + + if (resultCode != RHI::ResultCode::Success) { - // Default is a linear shaper with bias 0.0 and scale 1.0. That is, fx = x*1.0 + 0.0 - ShaperParams shaperParams = { ShaperType::Linear, 0.0, 1.f }; - OutputDeviceTransformType outputDeviceTransformType = OutputDeviceTransformType::NumOutputDeviceTransformTypes; - switch (shaperPreset) - { - case ShaperPresetType::None: - break; - case ShaperPresetType::Log2_48_nits: - outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_48Nits; - break; - case ShaperPresetType::Log2_1000_nits: - outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_1000Nits; - break; - case ShaperPresetType::Log2_2000_nits: - outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_2000Nits; - break; - case ShaperPresetType::Log2_4000_nits: - outputDeviceTransformType = OutputDeviceTransformType::OutputDeviceTransformType_4000Nits; - break; - default: - AZ_Error("DisplayMapperPass", false, "Invalid shaper preset type."); - break; - } - if (outputDeviceTransformType < OutputDeviceTransformType::NumOutputDeviceTransformTypes) - { - shaperParams = GetAcesShaperParameters(outputDeviceTransformType); - } - return shaperParams; + AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image."); + return; } - void AcesDisplayMapperFeatureProcessor::GetDefaultDisplayMapperConfiguration(DisplayMapperConfigurationDescriptor& config) + lutResource.m_lutImageViewDescriptor = RHI::ImageViewDescriptor::Create(LutFormat, 0, 0); + lutResource.m_lutImageView = lutResource.m_lutImage->GetImageView(lutResource.m_lutImageViewDescriptor); + if (!lutResource.m_lutImageView.get()) { - // Default configuration is ACES with LDR color grading LUT disabled. - config.m_operationType = DisplayMapperOperationType::Aces; - config.m_ldrGradingLutEnabled = false; - config.m_ldrColorGradingLut.Release(); + AZ_Error("AcesDisplayMapperFeatureProcessor", false, "Failed to initialize LUT image view."); + return; } - void AcesDisplayMapperFeatureProcessor::RegisterDisplayMapperConfiguration(const DisplayMapperConfigurationDescriptor& config) - { - m_displayMapperConfiguration = config; - } + // Add to the list of lut resources + lutResource.m_lutImageView->SetName(lutName); + m_ownedLuts[lutName] = lutResource; + } - DisplayMapperConfigurationDescriptor AcesDisplayMapperFeatureProcessor::GetDisplayMapperConfiguration() + ShaperParams AcesDisplayMapperFeatureProcessor::GetShaperParameters(ShaperPresetType shaperPreset, float customMinEv, float customMaxEv) + { + // Default is a linear shaper with bias 0.0 and scale 1.0. That is, fx = x*1.0 + 0.0 + ShaperParams shaperParams = { ShaperType::Linear, 0.0, 1.f }; + switch (shaperPreset) { - return m_displayMapperConfiguration; + case ShaperPresetType::None: + break; + case ShaperPresetType::Log2_48Nits: + shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_48Nits); + break; + case ShaperPresetType::Log2_1000Nits: + shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_1000Nits); + break; + case ShaperPresetType::Log2_2000Nits: + shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_2000Nits); + break; + case ShaperPresetType::Log2_4000Nits: + shaperParams = GetAcesShaperParameters(OutputDeviceTransformType::OutputDeviceTransformType_4000Nits); + break; + case ShaperPresetType::LinearCustomRange: + { + // Map the range min exposure - max exposure to 0-1. Convert EV values to linear values here to avoid that work in the shader. + // Shader equation becomes (x - bias) / scale; + constexpr float MediumGray = 0.18f; + const float minValue = MediumGray * powf(2, customMinEv); + const float maxValue = MediumGray * powf(2, customMaxEv); + shaperParams.m_type = ShaperType::Linear; + shaperParams.m_scale = 1.0f / (maxValue - minValue); + shaperParams.m_bias = -minValue * shaperParams.m_scale; + break; } - } // namespace Render -} // namespace AZ + case ShaperPresetType::Log2CustomRange: + shaperParams = GetLog2ShaperParameters(customMinEv, customMaxEv); + break; + case ShaperPresetType::PqSmpteSt2084: + shaperParams.m_type = ShaperType::PqSmpteSt2084; + break; + default: + AZ_Error("DisplayMapperPass", false, "Invalid shaper preset type."); + break; + } + return shaperParams; + } + + void AcesDisplayMapperFeatureProcessor::GetDefaultDisplayMapperConfiguration(DisplayMapperConfigurationDescriptor& config) + { + // Default configuration is ACES with LDR color grading LUT disabled. + config.m_operationType = DisplayMapperOperationType::Aces; + config.m_ldrGradingLutEnabled = false; + config.m_ldrColorGradingLut.Release(); + } + + void AcesDisplayMapperFeatureProcessor::RegisterDisplayMapperConfiguration(const DisplayMapperConfigurationDescriptor& config) + { + m_displayMapperConfiguration = config; + } + + DisplayMapperConfigurationDescriptor AcesDisplayMapperFeatureProcessor::GetDisplayMapperConfiguration() + { + return m_displayMapperConfiguration; + } +} // namespace AZ::Render diff --git a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/AcesOutputTransformLutPass.cpp b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/AcesOutputTransformLutPass.cpp index a00f879bca..3b2913aefd 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/AcesOutputTransformLutPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/AcesOutputTransformLutPass.cpp @@ -91,8 +91,8 @@ namespace AZ m_shaderResourceGroup->SetImageView(m_shaderInputLutImageIndex, m_displayMapperLut.m_lutImageView.get()); } - m_shaderResourceGroup->SetConstant(m_shaderInputShaperBiasIndex, m_shaperParams.bias); - m_shaderResourceGroup->SetConstant(m_shaderInputShaperScaleIndex, m_shaperParams.scale); + m_shaderResourceGroup->SetConstant(m_shaderInputShaperBiasIndex, m_shaperParams.m_bias); + m_shaderResourceGroup->SetConstant(m_shaderInputShaperScaleIndex, m_shaperParams.m_scale); } BindPassSrg(context, m_shaderResourceGroup); diff --git a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/ApplyShaperLookupTablePass.cpp b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/ApplyShaperLookupTablePass.cpp index eec511c07a..f4325f0421 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/ApplyShaperLookupTablePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/ApplyShaperLookupTablePass.cpp @@ -109,9 +109,9 @@ namespace AZ { m_shaderResourceGroup->SetImageView(m_shaderInputLutImageIndex, m_lutResource.m_lutStreamingImage->GetImageView()); - m_shaderResourceGroup->SetConstant(m_shaderShaperTypeIndex, m_shaperParams.type); - m_shaderResourceGroup->SetConstant(m_shaderShaperBiasIndex, m_shaperParams.bias); - m_shaderResourceGroup->SetConstant(m_shaderShaperScaleIndex, m_shaperParams.scale); + m_shaderResourceGroup->SetConstant(m_shaderShaperTypeIndex, m_shaperParams.m_type); + m_shaderResourceGroup->SetConstant(m_shaderShaperBiasIndex, m_shaperParams.m_bias); + m_shaderResourceGroup->SetConstant(m_shaderShaperScaleIndex, m_shaperParams.m_scale); } } } diff --git a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/BakeAcesOutputTransformLutPass.cpp b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/BakeAcesOutputTransformLutPass.cpp index f7ecfc3461..a8d1b68d63 100644 --- a/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/BakeAcesOutputTransformLutPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/DisplayMapper/BakeAcesOutputTransformLutPass.cpp @@ -94,8 +94,8 @@ namespace AZ m_shaderResourceGroup->SetImageView(m_shaderInputLutImageIndex, m_displayMapperLut.m_lutImageView.get()); - m_shaderResourceGroup->SetConstant(m_shaderInputShaperBiasIndex, m_shaperParams.bias); - m_shaderResourceGroup->SetConstant(m_shaderInputShaperScaleIndex, m_shaperParams.scale); + m_shaderResourceGroup->SetConstant(m_shaderInputShaperBiasIndex, m_shaperParams.m_bias); + m_shaderResourceGroup->SetConstant(m_shaderInputShaperScaleIndex, m_shaperParams.m_scale); } BindPassSrg(context, m_shaderResourceGroup); diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcess/LookModification/LookModificationSettings.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcess/LookModification/LookModificationSettings.cpp index d2e28e3a68..cbc357db61 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcess/LookModification/LookModificationSettings.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcess/LookModification/LookModificationSettings.cpp @@ -26,6 +26,8 @@ namespace AZ seed = TypeHash64(m_overrideStrength, seed); seed = TypeHash64(m_assetId.GetId(), seed); seed = TypeHash64(m_shaperPreset, seed); + seed = TypeHash64(m_customMinExposure, seed); + seed = TypeHash64(m_customMaxExposure, seed); return seed; } @@ -50,6 +52,9 @@ namespace AZ lutBlend.m_intensity = GetColorGradingLutIntensity(); lutBlend.m_overrideStrength = GetColorGradingLutOverride() * alpha; lutBlend.m_assetId = lutAssetId; + lutBlend.m_shaperPreset = GetShaperPresetType(); + lutBlend.m_customMinExposure = GetCustomMinExposure(); + lutBlend.m_customMaxExposure = GetCustomMaxExposure(); target->AddLutBlend(lutBlend); } } @@ -87,6 +92,9 @@ namespace AZ blendItem.m_intensity = GetColorGradingLutIntensity(); blendItem.m_overrideStrength = GetColorGradingLutOverride(); blendItem.m_assetId = GetColorGradingLut(); + blendItem.m_shaperPreset = GetShaperPresetType(); + blendItem.m_customMinExposure = GetCustomMinExposure(); + blendItem.m_customMaxExposure = GetCustomMaxExposure(); m_lutBlendStack.insert(m_lutBlendStack.begin(), blendItem); } } diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcess/LookModification/LookModificationSettings.h b/Gems/Atom/Feature/Common/Code/Source/PostProcess/LookModification/LookModificationSettings.h index 19bf07010b..d91a029ced 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcess/LookModification/LookModificationSettings.h +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcess/LookModification/LookModificationSettings.h @@ -33,7 +33,10 @@ namespace AZ //! Asset ID of LUT Data::Asset m_assetId; //! Shaper preset type - ShaperPresetType m_shaperPreset = AZ::Render::ShaperPresetType::Log2_48_nits; + ShaperPresetType m_shaperPreset = AZ::Render::ShaperPresetType::Log2_48Nits; + //! When shaper preset is custom, these values set min and max exposure. + float m_customMinExposure = -6.5; + float m_customMaxExposure = 6.5; HashValue64 GetHash(HashValue64 seed) const; }; diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.cpp index da680d80cf..8631ae2eb3 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.cpp @@ -21,7 +21,7 @@ namespace AZ namespace Render { static const char* const NumSourceLutsShaderVariantOptionName{ "o_numSourceLuts" }; - + RPI::Ptr BlendColorGradingLutsPass::Create(const RPI::PassDescriptor& descriptor) { RPI::Ptr pass = aznew BlendColorGradingLutsPass(descriptor); @@ -151,9 +151,9 @@ namespace AZ { m_shaderResourceGroup->SetImageView(m_shaderInputBlendedLutImageIndex, m_blendedLut.m_lutImageView.get()); m_shaderResourceGroup->SetConstant(m_shaderInputBlendedLutDimensionsIndex, m_blendedLutDimensions); - m_shaderResourceGroup->SetConstant(m_shaderInputBlendedLutShaperTypeIndex, m_blendedLutShaperParams.type); - m_shaderResourceGroup->SetConstant(m_shaderInputBlendededLutShaperBiasIndex, m_blendedLutShaperParams.bias); - m_shaderResourceGroup->SetConstant(m_shaderInputBlendededLutShaperScaleIndex, m_blendedLutShaperParams.scale); + m_shaderResourceGroup->SetConstant(m_shaderInputBlendedLutShaperTypeIndex, m_blendedLutShaperParams.m_type); + m_shaderResourceGroup->SetConstant(m_shaderInputBlendededLutShaperBiasIndex, m_blendedLutShaperParams.m_bias); + m_shaderResourceGroup->SetConstant(m_shaderInputBlendededLutShaperScaleIndex, m_blendedLutShaperParams.m_scale); m_shaderResourceGroup->SetConstant(m_shaderInputWeight0Index, m_weights[0]); m_shaderResourceGroup->SetConstant(m_shaderInputWeight1Index, m_weights[1]); m_shaderResourceGroup->SetConstant(m_shaderInputWeight2Index, m_weights[2]); @@ -163,33 +163,33 @@ namespace AZ if (m_colorGradingLuts[0].m_lutStreamingImage) { m_shaderResourceGroup->SetImageView(m_shaderInputSourceLut1ImageIndex, m_colorGradingLuts[0].m_lutStreamingImage->GetImageView()); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperTypeIndex, m_colorGradingShaperParams[0].type); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperBiasIndex, m_colorGradingShaperParams[0].bias); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperScaleIndex, m_colorGradingShaperParams[0].scale); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperTypeIndex, m_colorGradingShaperParams[0].m_type); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperBiasIndex, m_colorGradingShaperParams[0].m_bias); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut1ShaperScaleIndex, m_colorGradingShaperParams[0].m_scale); } if (m_colorGradingLuts[1].m_lutStreamingImage) { m_shaderResourceGroup->SetImageView(m_shaderInputSourceLut2ImageIndex, m_colorGradingLuts[1].m_lutStreamingImage->GetImageView()); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperTypeIndex, m_colorGradingShaperParams[1].type); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperBiasIndex, m_colorGradingShaperParams[1].bias); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperScaleIndex, m_colorGradingShaperParams[1].scale); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperTypeIndex, m_colorGradingShaperParams[1].m_type); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperBiasIndex, m_colorGradingShaperParams[1].m_bias); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut2ShaperScaleIndex, m_colorGradingShaperParams[1].m_scale); } if (m_colorGradingLuts[2].m_lutStreamingImage) { m_shaderResourceGroup->SetImageView(m_shaderInputSourceLut3ImageIndex, m_colorGradingLuts[2].m_lutStreamingImage->GetImageView()); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperTypeIndex, m_colorGradingShaperParams[2].type); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperBiasIndex, m_colorGradingShaperParams[2].bias); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperScaleIndex, m_colorGradingShaperParams[2].scale); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperTypeIndex, m_colorGradingShaperParams[2].m_type); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperBiasIndex, m_colorGradingShaperParams[2].m_bias); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut3ShaperScaleIndex, m_colorGradingShaperParams[2].m_scale); } if (m_colorGradingLuts[3].m_lutStreamingImage) { m_shaderResourceGroup->SetImageView(m_shaderInputSourceLut4ImageIndex, m_colorGradingLuts[3].m_lutStreamingImage->GetImageView()); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperTypeIndex, m_colorGradingShaperParams[3].type); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperBiasIndex, m_colorGradingShaperParams[3].bias); - m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperScaleIndex, m_colorGradingShaperParams[3].scale); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperTypeIndex, m_colorGradingShaperParams[3].m_type); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperBiasIndex, m_colorGradingShaperParams[3].m_bias); + m_shaderResourceGroup->SetConstant(m_shaderInputSourceLut4ShaperScaleIndex, m_colorGradingShaperParams[3].m_scale); } if (m_shaderResourceGroup->HasShaderVariantKeyFallbackEntry()) @@ -244,7 +244,170 @@ namespace AZ m_blendedLutShaperParams = shaperParams; } + + AZStd::optional BlendColorGradingLutsPass::GetCommonShaperParams() const + { + LookModificationSettings* settings = GetLookModificationSettings(); + if (settings) + { + settings->PrepareLutBlending(); + + ShaperPresetType type = ShaperPresetType::NumShaperTypes; + float customMinExposure = 0.0; + float customMaxExposure = 0.0; + + for (size_t lutIndex = 0; lutIndex < settings->GetLutBlendStackSize(); lutIndex++) + { + LutBlendItem& lutBlendItem = settings->GetLutBlendItem(lutIndex); + + if (lutIndex == 0) + { + type = lutBlendItem.m_shaperPreset; + customMinExposure = lutBlendItem.m_customMinExposure; + customMaxExposure = lutBlendItem.m_customMaxExposure; + } + else if (type != lutBlendItem.m_shaperPreset) + { + // Shapers are different + return AZStd::nullopt; + } + else if (type == ShaperPresetType::LinearCustomRange || type == ShaperPresetType::Log2CustomRange) + { + if (lutBlendItem.m_customMinExposure != customMinExposure || + lutBlendItem.m_customMaxExposure != customMaxExposure) + { + // Shapers are same, but custom exposure for custom type is different. + return AZStd::nullopt; + } + } + } + + // Only calculate shaper params when there's at least one lut blend. + if (settings->GetLutBlendStackSize() > 0) + { + return AcesDisplayMapperFeatureProcessor::GetShaperParameters(type, customMinExposure, customMaxExposure); + } + } + return AZStd::nullopt; + } + void BlendColorGradingLutsPass::CheckLutBlendSettings() + { + LookModificationSettings* settings = GetLookModificationSettings(); + if (settings) + { + settings->PrepareLutBlending(); + + // Early out if the settings have not chanced + HashValue64 hash = settings->GetHash(); + if (hash == m_lutBlendHash) + { + return; + } + m_lutBlendHash = hash; + + m_needToUpdateLut = true; + + // Calculate all the weights and LUT assets and check if there has been a change + // Only the top N LUTs will be blended where N = LookModificationSettings::MaxBlendLuts + // Weight 0 is used for the base color, and the other weights are for the LUTs in increasing priority + size_t numLuts = settings->GetLutBlendStackSize(); + + float intensity[LookModificationSettings::MaxBlendLuts]; + float one_intensity[LookModificationSettings::MaxBlendLuts]; + float over[LookModificationSettings::MaxBlendLuts]; + float one_over[LookModificationSettings::MaxBlendLuts]; + + for (int curLutIndex = 0; curLutIndex < LookModificationSettings::MaxBlendLuts; curLutIndex++) + { + intensity[curLutIndex] = 0.f; + one_intensity[curLutIndex] = 1.f; + over[curLutIndex] = 0.f; + one_over[curLutIndex] = 1.f; + } + + int current = 0; + for (size_t lutIndex = 0; lutIndex < numLuts; lutIndex++) + { + LutBlendItem& lutBlendItem = settings->GetLutBlendItem(lutIndex); + const auto assetId = lutBlendItem.m_assetId.GetId(); + + if (assetId.IsValid()) + { + AcesDisplayMapperFeatureProcessor* dmfp = GetScene()->GetFeatureProcessor(); + dmfp->GetLutFromAssetId(m_colorGradingLuts[current], assetId); + if (!m_colorGradingLuts[current].m_lutStreamingImage) + { + AZ_Warning("BlendColorGradingLutsPass", false, "Unable to load grading LUT from asset %s", + lutBlendItem.m_assetId.ToString().c_str()); + // Skip this LUT + continue; + } + } + + intensity[current] = lutBlendItem.m_intensity; + one_intensity[current] = 1.0f - lutBlendItem.m_intensity; + over[current] = lutBlendItem.m_overrideStrength; + one_over[current] = 1.0f - lutBlendItem.m_overrideStrength; + + m_colorGradingShaperParams[current] = AcesDisplayMapperFeatureProcessor::GetShaperParameters( + lutBlendItem.m_shaperPreset, + lutBlendItem.m_customMinExposure, + lutBlendItem.m_customMaxExposure + ); + + ++current; + if (current == LookModificationSettings::MaxBlendLuts) + { + break; + } + } + + m_weights[0] = 0.f; + // Handle the case where there are no LUTs to be blended, and hence an identity LUT will be generated + if (current == 0) + { + m_weights[0] = 1.f; + // These weights would not be used in the shader in this case, but setting to zero anyways. + for (int lutIndex = 1; lutIndex < LookModificationSettings::MaxBlendLuts + 1; lutIndex++) + { + m_weights[lutIndex] = 0.f; + } + } + else + { + // Compute all the weights + // First compute the weight of the ungraded color value + for (int lutIndex = 0; lutIndex < current; lutIndex++) + { + float weight = one_intensity[lutIndex] * over[lutIndex]; + for (int overrideLutIndex = lutIndex + 1; overrideLutIndex < LookModificationSettings::MaxBlendLuts; overrideLutIndex++) + { + weight *= one_over[overrideLutIndex]; + } + m_weights[0] += weight; + } + // Then compute the weights for the LUTs + for (int weightIndex = 0; weightIndex < current; weightIndex++) + { + m_weights[weightIndex + 1] = intensity[weightIndex] * over[weightIndex]; + for (int lutIndex = weightIndex + 1; lutIndex < LookModificationSettings::MaxBlendLuts; lutIndex++) + { + m_weights[weightIndex + 1] *= one_over[lutIndex]; + } + } + } + + // If the number of source LUTs have changed, the shader variant will need to be updated + if (m_numSourceLuts != current) + { + m_numSourceLuts = current; + m_needToUpdateShaderVariant = true; + } + } + } + + LookModificationSettings* BlendColorGradingLutsPass::GetLookModificationSettings() const { AZ::RPI::Scene* scene = GetScene(); if (scene) @@ -259,109 +422,12 @@ namespace AZ LookModificationSettings* settings = postProcessSettings->GetLookModificationSettings(); if (settings) { - settings->PrepareLutBlending(); - - // Early out if the settings have not chanced - HashValue64 hash = settings->GetHash(); - if (hash == m_lutBlendHash) - { - return; - } - m_lutBlendHash = hash; - - m_needToUpdateLut = true; - - // Calculate all the weights and LUT assets and check if there has been a change - // Only the top N LUTs will be blended where N = LookModificationSettings::MaxBlendLuts - // Weight 0 is used for the base color, and the other weights are for the LUTs in increasing priority - size_t numLuts = settings->GetLutBlendStackSize(); - float intensity[LookModificationSettings::MaxBlendLuts]; - float one_intensity[LookModificationSettings::MaxBlendLuts]; - float over[LookModificationSettings::MaxBlendLuts]; - float one_over[LookModificationSettings::MaxBlendLuts]; - for (int curLutIndex = 0; curLutIndex < LookModificationSettings::MaxBlendLuts; curLutIndex++) - { - intensity[curLutIndex] = 0.f; - one_intensity[curLutIndex] = 1.f; - over[curLutIndex] = 0.f; - one_over[curLutIndex] = 1.f; - } - - int current = 0; - for (size_t lutIndex = 0; lutIndex < numLuts; lutIndex++) - { - LutBlendItem& lutBlendItem = settings->GetLutBlendItem(lutIndex); - auto assetId = lutBlendItem.m_assetId.GetId(); - if (assetId.IsValid()) - { - AcesDisplayMapperFeatureProcessor* dmfp = scene->GetFeatureProcessor(); - dmfp->GetLutFromAssetId(m_colorGradingLuts[lutIndex], assetId); - if (!m_colorGradingLuts[lutIndex].m_lutStreamingImage) - { - AZ_Warning("BlendColorGradingLutsPass", false, "Unable to load grading LUT from asset %s", lutBlendItem.m_assetId.ToString().c_str()); - // Skip this LUT - continue; - } - } - intensity[current] = lutBlendItem.m_intensity; - one_intensity[current] = 1.f - intensity[lutIndex]; - over[current] = lutBlendItem.m_overrideStrength; - one_over[current] = 1.f - over[lutIndex]; - m_colorGradingLutAssets[current] = lutBlendItem.m_assetId; - m_colorGradingShaperPresets[current] = lutBlendItem.m_shaperPreset; - m_colorGradingShaperParams[current] = AcesDisplayMapperFeatureProcessor::GetShaperParameters(m_colorGradingShaperPresets[lutIndex]); - current++; - if (current == LookModificationSettings::MaxBlendLuts) - { - break; - } - } - - m_weights[0] = 0.f; - // Handle the case where there are no LUTs to be blended, and hence an identity LUT will be generated - if (current == 0) - { - m_weights[0] = 1.f; - // These weights would not be used in the shader in this case, but setting to zero anyways. - for (int lutIndex = 1; lutIndex < LookModificationSettings::MaxBlendLuts + 1; lutIndex++) - { - m_weights[lutIndex] = 0.f; - } - } - else - { - // Compute all the weights - // First compute the weight of the ungraded color value - for (int lutIndex = 0; lutIndex < LookModificationSettings::MaxBlendLuts; lutIndex++) - { - float weight = one_intensity[lutIndex] * over[lutIndex]; - for (int overrideLutIndex = lutIndex + 1; overrideLutIndex < LookModificationSettings::MaxBlendLuts; overrideLutIndex++) - { - weight *= one_over[overrideLutIndex]; - } - m_weights[0] += weight; - } - // Then compute the weights for the LUTs - for (int weightIndex = 0; weightIndex < LookModificationSettings::MaxBlendLuts; weightIndex++) - { - m_weights[weightIndex + 1] = intensity[weightIndex] * over[weightIndex]; - for (int lutIndex = weightIndex + 1; lutIndex < LookModificationSettings::MaxBlendLuts; lutIndex++) - { - m_weights[weightIndex + 1] *= one_over[lutIndex]; - } - } - } - - // If the number of source LUTs have changed, the shader variant will need to be updated - if (m_numSourceLuts != current) - { - m_numSourceLuts = current; - m_needToUpdateShaderVariant = true; - } + return settings; } } } } + return nullptr; } } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.h b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.h index d0e6aad4aa..7b37d2c4ec 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.h +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/BlendColorGradingLutsPass.h @@ -47,6 +47,7 @@ namespace AZ static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); void SetShaperParameters(const ShaperParams& shaperParams); + AZStd::optional GetCommonShaperParams() const; private: explicit BlendColorGradingLutsPass(const RPI::PassDescriptor& descriptor); @@ -66,6 +67,7 @@ namespace AZ void ReleaseLutImage(); void CheckLutBlendSettings(); + LookModificationSettings* GetLookModificationSettings() const; bool m_resourcesInitialized = false; @@ -111,8 +113,6 @@ namespace AZ AZStd::array m_blendedLutDimensions; float m_weights[LookModificationSettings::MaxBlendLuts + 1]; // The first index is reserved for the weight of the non color graded value - Data::Asset m_colorGradingLutAssets[LookModificationSettings::MaxBlendLuts]; - ShaperPresetType m_colorGradingShaperPresets[LookModificationSettings::MaxBlendLuts]; Render::ShaperParams m_colorGradingShaperParams[LookModificationSettings::MaxBlendLuts]; Render::DisplayMapperAssetLut m_colorGradingLuts[LookModificationSettings::MaxBlendLuts]; diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.cpp index 40f8f8355d..85c45de96b 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.cpp @@ -9,11 +9,15 @@ #include #include #include +#include +#include #include #include #include +#include + #include #include #include @@ -22,6 +26,22 @@ namespace AZ { namespace Render { + AZ_CVAR(uint8_t, + r_lutSampleQuality, + 0, + [](const uint8_t& value) + { + auto passes = RPI::PassSystem::Get()->FindPasses(RPI::PassClassFilter()); + for (auto* pass : passes) + { + LookModificationCompositePass* lookModPass = azrtti_cast(pass); + lookModPass->SetSampleQuality(LookModificationCompositePass::SampleQuality(value)); + } + }, + ConsoleFunctorFlags::Null, + "This can be increased to deal with particularly tricky luts. Range (0-2). 0 (default) - Standard linear sampling. 1 - 7 tap b-spline sampling. 2 - 19 tap b-spline sampling." + ); + RPI::Ptr LookModificationCompositePass::Create(const RPI::PassDescriptor& descriptor) { RPI::Ptr pass = aznew LookModificationCompositePass(descriptor); @@ -30,8 +50,6 @@ namespace AZ LookModificationCompositePass::LookModificationCompositePass(const RPI::PassDescriptor& descriptor) : AZ::RPI::FullscreenTrianglePass(descriptor) - , m_exposureShaderVariantOptionName(ExposureShaderVariantOptionName) - , m_colorGradingShaderVariantOptionName(ColorGradingShaderVariantOptionName) { } @@ -60,19 +78,38 @@ namespace AZ { AZ_Assert(m_shader != nullptr, "LookModificationCompositePass %s has a null shader when calling InitializeShaderVariant.", GetPathName().GetCStr()); - AZStd::vector exposureVariationTypes = { AZ::Name("true"), AZ::Name("false") }; - AZStd::vector colorGradingVariationTypes = { AZ::Name("true"), AZ::Name("false") }; + struct OptionSettings + { + AZ::Name m_enableExposureControl; + AZ::Name m_enableColorGrading; + RPI::ShaderOptionValue m_lutSampleQuality; - auto exposureVariationTypeCount = exposureVariationTypes.size(); - auto totalVariationCount = exposureVariationTypes.size() * colorGradingVariationTypes.size(); + OptionSettings(const char* enableExposureControl, const char* enableColorGrading, SampleQuality sampleQuality) + : m_enableExposureControl(Name(enableExposureControl)) + , m_enableColorGrading(Name(enableColorGrading)) + , m_lutSampleQuality(RPI::ShaderOptionValue(sampleQuality)) + {} + }; + + AZStd::vector options = + { + { "false", "false", SampleQuality::Linear }, + { "true", "false", SampleQuality::Linear }, + { "false", "true", SampleQuality::Linear }, + { "false", "true", SampleQuality::BSpline7Tap }, + { "false", "true", SampleQuality::BSpline19Tap }, + { "true", "true", SampleQuality::Linear }, + { "true", "true", SampleQuality::BSpline7Tap }, + { "true", "true", SampleQuality::BSpline19Tap }, + }; // Caching all pipeline state for each shader variation for performance reason. - for (auto shaderVariantIndex = 0; shaderVariantIndex < totalVariationCount; ++shaderVariantIndex) + for (auto shaderVariantIndex = 0; shaderVariantIndex < options.size(); ++shaderVariantIndex) { auto shaderOption = m_shader->CreateShaderOptionGroup(); - shaderOption.SetValue(m_exposureShaderVariantOptionName, exposureVariationTypes[shaderVariantIndex % exposureVariationTypeCount]); - shaderOption.SetValue(m_colorGradingShaderVariantOptionName, colorGradingVariationTypes[shaderVariantIndex / exposureVariationTypeCount]); - + shaderOption.SetValue(m_exposureShaderVariantOptionName, options.at(shaderVariantIndex).m_enableExposureControl); + shaderOption.SetValue(m_colorGradingShaderVariantOptionName, options.at(shaderVariantIndex).m_enableColorGrading); + shaderOption.SetValue(m_lutSampleQualityShaderVariantOptionName, options.at(shaderVariantIndex).m_lutSampleQuality); PreloadShaderVariant(m_shader, shaderOption, GetRenderAttachmentConfiguration(), GetMultisampleState()); } @@ -173,9 +210,9 @@ namespace AZ { m_shaderResourceGroup->SetImageView(m_shaderColorGradingLutImageIndex, m_blendedColorGradingLut.m_lutImageView.get()); - m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperTypeIndex, m_colorGradingShaperParams.type); - m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperBiasIndex, m_colorGradingShaperParams.bias); - m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperScaleIndex, m_colorGradingShaperParams.scale); + m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperTypeIndex, m_colorGradingShaperParams.m_type); + m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperBiasIndex, m_colorGradingShaperParams.m_bias); + m_shaderResourceGroup->SetConstant(m_shaderColorGradingShaperScaleIndex, m_colorGradingShaperParams.m_scale); } } @@ -192,7 +229,8 @@ namespace AZ // Decide which shader to use. shaderOption.SetValue(m_exposureShaderVariantOptionName, m_exposureControlEnabled ? AZ::Name("true") : AZ::Name("false")); shaderOption.SetValue(m_colorGradingShaderVariantOptionName, m_colorGradingLutEnabled ? AZ::Name("true") : AZ::Name("false")); - + shaderOption.SetValue(m_lutSampleQualityShaderVariantOptionName, RPI::ShaderOptionValue(m_sampleQuality)); + UpdateShaderVariant(shaderOption); m_needToUpdateShaderVariant = false; @@ -218,5 +256,12 @@ namespace AZ { m_colorGradingShaperParams = shaperParams; } + + void LookModificationCompositePass::SetSampleQuality(SampleQuality sampleQuality) + { + m_sampleQuality = sampleQuality; + m_needToUpdateShaderVariant = true; + } + } // namespace Render } // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.h b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.h index 49a0e88a54..b269225445 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.h +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationCompositePass.h @@ -30,8 +30,6 @@ namespace AZ namespace Render { static const char* const LookModificationTransformPassTemplateName{ "LookModificationTransformTemplate" }; - static const char* const ExposureShaderVariantOptionName{ "o_enableExposureControlFeature" }; - static const char* const ColorGradingShaderVariantOptionName{ "o_enableColorGradingLut" }; /** * The look modification composite pass. If color grading LUTs are enabled, this pass will apply the blended LUT. @@ -43,6 +41,14 @@ namespace AZ public: AZ_RTTI(LookModificationCompositePass, "{D7DF3E8A-B642-4D51-ABC2-ADB2B60FCE1D}", AZ::RPI::FullscreenTrianglePass); AZ_CLASS_ALLOCATOR(LookModificationCompositePass, SystemAllocator, 0); + + enum class SampleQuality : uint8_t + { + Linear = 0, + BSpline7Tap = 1, + BSpline19Tap = 2, + }; + virtual ~LookModificationCompositePass() = default; //! Creates a LookModificationPass @@ -54,6 +60,8 @@ namespace AZ //! Set shaper parameters void SetShaperParameters(const ShaperParams& shaperParams); + void SetSampleQuality(SampleQuality sampleQuality); + protected: LookModificationCompositePass(const RPI::PassDescriptor& descriptor); @@ -76,11 +84,15 @@ namespace AZ bool m_exposureControlEnabled = false; bool m_colorGradingLutEnabled = false; + SampleQuality m_sampleQuality = SampleQuality::Linear; + Render::DisplayMapperLut m_blendedColorGradingLut; Render::ShaperParams m_colorGradingShaperParams; - const AZ::Name m_exposureShaderVariantOptionName; - const AZ::Name m_colorGradingShaderVariantOptionName; + const AZ::Name m_exposureShaderVariantOptionName{ "o_enableExposureControlFeature" }; + const AZ::Name m_colorGradingShaderVariantOptionName{ "o_enableColorGradingLut" }; + const AZ::Name m_lutSampleQualityShaderVariantOptionName{ "o_lutSampleQuality" }; + bool m_needToUpdateShaderVariant = true; RHI::ShaderInputNameIndex m_shaderColorGradingLutImageIndex = "m_gradingLut"; diff --git a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationTransformPass.cpp b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationTransformPass.cpp index cde7d6586b..e33a63a4bb 100644 --- a/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationTransformPass.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/PostProcessing/LookModificationTransformPass.cpp @@ -47,29 +47,32 @@ namespace AZ swapChainFormat = m_swapChainAttachmentBinding->m_attachment->GetTransientImageDescriptor().m_imageDescriptor.m_format; } - if (m_displayBufferFormat != swapChainFormat) + // Update the children passes + RPI::Ptr blendPass = FindChildPass(); + if (blendPass) { - m_displayBufferFormat = swapChainFormat; - m_outputDeviceTransformType = AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(m_displayBufferFormat); - m_shaperParams = GetAcesShaperParameters(m_outputDeviceTransformType); - - // Update the children passes - for (const AZ::RPI::Ptr& child : m_children) + auto commonShaperParams = blendPass->GetCommonShaperParams(); + if (commonShaperParams) { - BlendColorGradingLutsPass* blendPass = azrtti_cast(child.get()); - if (blendPass) - { - blendPass->SetShaperParameters(m_shaperParams); - continue; - } - LookModificationCompositePass* compositePass = azrtti_cast(child.get()); - if (compositePass) - { - compositePass->SetShaperParameters(m_shaperParams); - continue; - } + m_shaperParams = *commonShaperParams; + } + else + { + // Mix of shapers used, so shape them based on the output transform type. + m_displayBufferFormat = swapChainFormat; + m_outputDeviceTransformType = AcesDisplayMapperFeatureProcessor::GetOutputDeviceTransformType(m_displayBufferFormat); + m_shaperParams = GetAcesShaperParameters(m_outputDeviceTransformType); + } + + blendPass->SetShaperParameters(m_shaperParams); + + RPI::Ptr compositePass = FindChildPass(); + if (compositePass) + { + compositePass->SetShaperParameters(m_shaperParams); } } + ParentPass::FrameBeginInternal(params); } } // namespace Render diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h index 7e341d43c3..36994ec03b 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Pass/ParentPass.h @@ -64,6 +64,9 @@ namespace AZ //! Find a child pass with a matching name and returns it. Return nullptr if none found. Ptr FindChildPass(const Name& passName) const; + + template + Ptr FindChildPass() const; //! Searches the tree for the first pass that has same pass name (Depth-first search). Return nullptr if none found. Ptr FindPassByNameRecursive(const Name& passName) const; @@ -132,5 +135,20 @@ namespace AZ // Generates child passes from source PassTemplate void CreatePassesFromTemplate(); }; + + template + inline Ptr ParentPass::FindChildPass() const + { + for (const Ptr& child : m_children) + { + PassType* pass = azrtti_cast(child.get()); + if (pass) + { + return pass; + } + } + return {}; + } + } // namespace RPI } // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/PostProcess/LookModification/LookModificationComponentConfig.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/PostProcess/LookModification/LookModificationComponentConfig.h index 5ee348a052..0bf9e752a2 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/PostProcess/LookModification/LookModificationComponentConfig.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/PostProcess/LookModification/LookModificationComponentConfig.h @@ -39,6 +39,11 @@ namespace AZ void CopySettingsTo(LookModificationSettingsInterface* settings); bool ArePropertiesReadOnly() const { return !m_enabled; } + + bool IsUsingCustomShaper() const { + return m_shaperPresetType == ShaperPresetType::LinearCustomRange + || m_shaperPresetType == ShaperPresetType::Log2CustomRange; + } }; } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/LookModification/EditorLookModificationComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/LookModification/EditorLookModificationComponent.cpp index a7dfb76e95..310762863a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/LookModification/EditorLookModificationComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/LookModification/EditorLookModificationComponent.cpp @@ -48,33 +48,44 @@ namespace AZ &LookModificationComponentConfig::m_enabled, "Enable look modification", "Enable look modification.") - ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &LookModificationComponentConfig::m_colorGradingLut, "Color Grading LUT", "Color grading LUT") - ->ClassElement(Edit::ClassElements::EditorData, "") ->DataElement(Edit::UIHandlers::ComboBox, - &LookModificationComponentConfig::m_shaperPresetType, - "Shaper Type", - "Shaper Type.") - ->EnumAttribute(ShaperPresetType::None, "None") - ->EnumAttribute(ShaperPresetType::Log2_48_nits, "Log2_48_nits") - ->EnumAttribute(ShaperPresetType::Log2_1000_nits, "Log2_1000_nits") - ->EnumAttribute(ShaperPresetType::Log2_2000_nits, "Log2_2000_nits") - ->EnumAttribute(ShaperPresetType::Log2_4000_nits, "Log2_4000_nits") - ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - + &LookModificationComponentConfig::m_shaperPresetType, "Shaper Type", "Shaper Type.") + ->EnumAttribute(ShaperPresetType::None, "None") + ->EnumAttribute(ShaperPresetType::LinearCustomRange, "Linear Custom Range") + ->EnumAttribute(ShaperPresetType::Log2_48Nits, "Log2 48 nits") + ->EnumAttribute(ShaperPresetType::Log2_1000Nits, "Log2 1000 nits") + ->EnumAttribute(ShaperPresetType::Log2_2000Nits, "Log2 2000 nits") + ->EnumAttribute(ShaperPresetType::Log2_4000Nits, "Log2 4000 nits") + ->EnumAttribute(ShaperPresetType::Log2CustomRange, "Log2 Custom Range") + ->EnumAttribute(ShaperPresetType::PqSmpteSt2084, "PQ (SMPTE ST 2084)") + ->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::EntireTree) + ->DataElement(AZ::Edit::UIHandlers::Slider, &LookModificationComponentConfig::m_customMinExposure, "Minimum Exposure", "The minimum exposure this LUT supports. Values smaller than this will be clamped to 0.") + ->Attribute(AZ::Edit::Attributes::Min, -50.0f) + ->Attribute(AZ::Edit::Attributes::Max, 0.0f) + ->Attribute(AZ::Edit::Attributes::SoftMin, -20.0f) + ->Attribute(AZ::Edit::Attributes::SoftMax, 0.0f) + ->Attribute(Edit::Attributes::Visibility, &LookModificationComponentConfig::IsUsingCustomShaper) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->DataElement(AZ::Edit::UIHandlers::Slider, &LookModificationComponentConfig::m_customMaxExposure, "Maximum Exposure", "The maximum exposure this LUT supports. Values larger than this will be clamped.") + ->Attribute(AZ::Edit::Attributes::Min, 0.0f) + ->Attribute(AZ::Edit::Attributes::Max, 50.0f) + ->Attribute(AZ::Edit::Attributes::SoftMin, 0.0f) + ->Attribute(AZ::Edit::Attributes::SoftMax, 20.0f) + ->Attribute(Edit::Attributes::Visibility, &LookModificationComponentConfig::IsUsingCustomShaper) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) ->DataElement(AZ::Edit::UIHandlers::Slider, &LookModificationComponentConfig::m_colorGradingLutIntensity, "LUT Intensity", "Blend intensity of this LUT.") - ->Attribute(AZ::Edit::Attributes::Min, 0.0f) - ->Attribute(AZ::Edit::Attributes::Max, 1.0f) - ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ->Attribute(Edit::Attributes::ReadOnly, &LookModificationComponentConfig::ArePropertiesReadOnly) - + ->Attribute(AZ::Edit::Attributes::Min, 0.0f) + ->Attribute(AZ::Edit::Attributes::Max, 1.0f) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->Attribute(Edit::Attributes::ReadOnly, &LookModificationComponentConfig::ArePropertiesReadOnly) ->DataElement(AZ::Edit::UIHandlers::Slider, &LookModificationComponentConfig::m_colorGradingLutOverride, "LUT Override", "Blend intensity of this LUT.") - ->Attribute(AZ::Edit::Attributes::Min, 0.0f) - ->Attribute(AZ::Edit::Attributes::Max, 1.0f) - ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) - ->Attribute(Edit::Attributes::ReadOnly, &LookModificationComponentConfig::ArePropertiesReadOnly) + ->Attribute(AZ::Edit::Attributes::Min, 0.0f) + ->Attribute(AZ::Edit::Attributes::Max, 1.0f) + ->Attribute(AZ::Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly) + ->Attribute(Edit::Attributes::ReadOnly, &LookModificationComponentConfig::ArePropertiesReadOnly) // Overrides ->ClassElement(AZ::Edit::ClassElements::Group, "Overrides")