using System;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
namespace UnityEngine.Rendering.Universal.Internal
{
// Note: this pass can't be done at the same time as post-processing as it needs to be done in
// advance in case we're doing on-tile color grading.
///
/// Renders a color grading LUT texture.
///
public class ColorGradingLutPass : ScriptableRenderPass
{
readonly Material m_LutBuilderLdr;
readonly Material m_LutBuilderHdr;
internal readonly GraphicsFormat m_HdrLutFormat;
internal readonly GraphicsFormat m_LdrLutFormat;
PassData m_PassData;
RTHandle m_InternalLut;
bool m_AllowColorGradingACESHDR = true;
///
/// Creates a new ColorGradingLutPass instance.
///
/// The RenderPassEvent to use.
/// The PostProcessData resources to use.
///
///
public ColorGradingLutPass(RenderPassEvent evt, PostProcessData data)
{
profilingSampler = new ProfilingSampler("Blit Color LUT");
renderPassEvent = evt;
overrideCameraTarget = true;
Material Load(Shader shader)
{
if (shader == null)
{
Debug.LogError($"Missing shader. ColorGradingLutPass render pass will not execute. Check for missing reference in the renderer resources.");
return null;
}
return CoreUtils.CreateEngineMaterial(shader);
}
m_LutBuilderLdr = Load(data.shaders.lutBuilderLdrPS);
m_LutBuilderHdr = Load(data.shaders.lutBuilderHdrPS);
// Warm up lut format as IsFormatSupported adds GC pressure...
// UUM-41070: We require `Linear | Render` but with the deprecated FormatUsage this was checking `Blend`
// For now, we keep checking for `Blend` until the performance hit of doing the correct checks is evaluated
const GraphicsFormatUsage kFlags = GraphicsFormatUsage.Blend;
if (SystemInfo.IsFormatSupported(GraphicsFormat.R16G16B16A16_SFloat, kFlags))
m_HdrLutFormat = GraphicsFormat.R16G16B16A16_SFloat;
else if (SystemInfo.IsFormatSupported(GraphicsFormat.B10G11R11_UFloatPack32, kFlags))
// Precision can be too low, if FP16 primary renderTarget is requested by the user.
// But it's better than falling back to R8G8B8A8_UNorm in the worst case.
m_HdrLutFormat = GraphicsFormat.B10G11R11_UFloatPack32;
else
// Obviously using this for log lut encoding is a very bad idea for precision but we
// need it for compatibility reasons and avoid black screens on platforms that don't
// support floating point formats. Expect banding and posterization artifact if this
// ends up being used.
m_HdrLutFormat = GraphicsFormat.R8G8B8A8_UNorm;
m_LdrLutFormat = GraphicsFormat.R8G8B8A8_UNorm;
base.useNativeRenderPass = false;
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.OpenGLES3 && Graphics.minOpenGLESVersion <= OpenGLESVersion.OpenGLES30 && SystemInfo.graphicsDeviceName.StartsWith("Adreno (TM) 3"))
m_AllowColorGradingACESHDR = false;
m_PassData = new PassData();
}
///
/// Sets up the pass.
///
/// The RTHandle to use to render to.
///
public void Setup(in RTHandle internalLut)
{
m_InternalLut = internalLut;
}
///
/// Get a descriptor and filter mode for the required texture for this pass
///
/// The pass will use settings from PostProcessingData for the pass.
/// The RenderTextureDescriptor used by the pass.
/// The FilterMode used by the pass.
public void ConfigureDescriptor(in PostProcessingData postProcessingData, out RenderTextureDescriptor descriptor, out FilterMode filterMode)
{
ConfigureDescriptor(postProcessingData.universalPostProcessingData, out descriptor, out filterMode);
}
///
/// Get a descriptor and filter mode for the required texture for this pass
///
/// The pass will use settings from PostProcessingData for the pass.
/// The RenderTextureDescriptor used by the pass.
/// The FilterMode used by the pass.
public void ConfigureDescriptor(in UniversalPostProcessingData postProcessingData, out RenderTextureDescriptor descriptor, out FilterMode filterMode)
{
bool hdr = postProcessingData.gradingMode == ColorGradingMode.HighDynamicRange;
int lutHeight = postProcessingData.lutSize;
int lutWidth = lutHeight * lutHeight;
var format = hdr ? m_HdrLutFormat : m_LdrLutFormat;
descriptor = new RenderTextureDescriptor(lutWidth, lutHeight, format, 0);
descriptor.vrUsage = VRTextureUsage.None; // We only need one for both eyes in VR
filterMode = FilterMode.Bilinear;
}
///
[Obsolete(DeprecationMessage.CompatibilityScriptingAPIObsolete, false)]
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
ContextContainer frameData = renderingData.frameData;
UniversalCameraData cameraData = frameData.Get();
UniversalPostProcessingData postProcessingData = frameData.Get();
m_PassData.cameraData = cameraData;
m_PassData.postProcessingData = postProcessingData;
m_PassData.lutBuilderLdr = m_LutBuilderLdr;
m_PassData.lutBuilderHdr = m_LutBuilderHdr;
m_PassData.allowColorGradingACESHDR = m_AllowColorGradingACESHDR;
#if ENABLE_VR && ENABLE_XR_MODULE
if (renderingData.cameraData.xr.supportsFoveatedRendering)
renderingData.commandBuffer.SetFoveatedRenderingMode(FoveatedRenderingMode.Disabled);
#endif
CoreUtils.SetRenderTarget(renderingData.commandBuffer, m_InternalLut, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, ClearFlag.None, Color.clear);
ExecutePass(CommandBufferHelpers.GetRasterCommandBuffer(renderingData.commandBuffer), m_PassData, m_InternalLut);
}
private class PassData
{
internal UniversalCameraData cameraData;
internal UniversalPostProcessingData postProcessingData;
internal Material lutBuilderLdr;
internal Material lutBuilderHdr;
internal bool allowColorGradingACESHDR;
internal TextureHandle internalLut;
}
private static void ExecutePass(RasterCommandBuffer cmd, PassData passData, RTHandle internalLutTarget)
{
var lutBuilderLdr = passData.lutBuilderLdr;
var lutBuilderHdr = passData.lutBuilderHdr;
var allowColorGradingACESHDR = passData.allowColorGradingACESHDR;
using (new ProfilingScope(cmd, ProfilingSampler.Get(URPProfileId.ColorGradingLUT)))
{
// Fetch all color grading settings
var stack = VolumeManager.instance.stack;
var channelMixer = stack.GetComponent();
var colorAdjustments = stack.GetComponent();
var curves = stack.GetComponent();
var liftGammaGain = stack.GetComponent();
var shadowsMidtonesHighlights = stack.GetComponent();
var splitToning = stack.GetComponent();
var tonemapping = stack.GetComponent();
var whiteBalance = stack.GetComponent();
bool hdr = passData.postProcessingData.gradingMode == ColorGradingMode.HighDynamicRange;
// Prepare texture & material
var material = hdr ? lutBuilderHdr : lutBuilderLdr;
// Prepare data
var lmsColorBalance = ColorUtils.ColorBalanceToLMSCoeffs(whiteBalance.temperature.value, whiteBalance.tint.value);
var hueSatCon = new Vector4(colorAdjustments.hueShift.value / 360f, colorAdjustments.saturation.value / 100f + 1f, colorAdjustments.contrast.value / 100f + 1f, 0f);
var channelMixerR = new Vector4(channelMixer.redOutRedIn.value / 100f, channelMixer.redOutGreenIn.value / 100f, channelMixer.redOutBlueIn.value / 100f, 0f);
var channelMixerG = new Vector4(channelMixer.greenOutRedIn.value / 100f, channelMixer.greenOutGreenIn.value / 100f, channelMixer.greenOutBlueIn.value / 100f, 0f);
var channelMixerB = new Vector4(channelMixer.blueOutRedIn.value / 100f, channelMixer.blueOutGreenIn.value / 100f, channelMixer.blueOutBlueIn.value / 100f, 0f);
var shadowsHighlightsLimits = new Vector4(
shadowsMidtonesHighlights.shadowsStart.value,
shadowsMidtonesHighlights.shadowsEnd.value,
shadowsMidtonesHighlights.highlightsStart.value,
shadowsMidtonesHighlights.highlightsEnd.value
);
var (shadows, midtones, highlights) = ColorUtils.PrepareShadowsMidtonesHighlights(
shadowsMidtonesHighlights.shadows.value,
shadowsMidtonesHighlights.midtones.value,
shadowsMidtonesHighlights.highlights.value
);
var (lift, gamma, gain) = ColorUtils.PrepareLiftGammaGain(
liftGammaGain.lift.value,
liftGammaGain.gamma.value,
liftGammaGain.gain.value
);
var (splitShadows, splitHighlights) = ColorUtils.PrepareSplitToning(
splitToning.shadows.value,
splitToning.highlights.value,
splitToning.balance.value
);
int lutHeight = passData.postProcessingData.lutSize;
int lutWidth = lutHeight * lutHeight;
var lutParameters = new Vector4(lutHeight, 0.5f / lutWidth, 0.5f / lutHeight,
lutHeight / (lutHeight - 1f));
// Fill in constants
material.SetVector(ShaderConstants._Lut_Params, lutParameters);
material.SetVector(ShaderConstants._ColorBalance, lmsColorBalance);
material.SetVector(ShaderConstants._ColorFilter, colorAdjustments.colorFilter.value.linear);
material.SetVector(ShaderConstants._ChannelMixerRed, channelMixerR);
material.SetVector(ShaderConstants._ChannelMixerGreen, channelMixerG);
material.SetVector(ShaderConstants._ChannelMixerBlue, channelMixerB);
material.SetVector(ShaderConstants._HueSatCon, hueSatCon);
material.SetVector(ShaderConstants._Lift, lift);
material.SetVector(ShaderConstants._Gamma, gamma);
material.SetVector(ShaderConstants._Gain, gain);
material.SetVector(ShaderConstants._Shadows, shadows);
material.SetVector(ShaderConstants._Midtones, midtones);
material.SetVector(ShaderConstants._Highlights, highlights);
material.SetVector(ShaderConstants._ShaHiLimits, shadowsHighlightsLimits);
material.SetVector(ShaderConstants._SplitShadows, splitShadows);
material.SetVector(ShaderConstants._SplitHighlights, splitHighlights);
// YRGB curves
material.SetTexture(ShaderConstants._CurveMaster, curves.master.value.GetTexture());
material.SetTexture(ShaderConstants._CurveRed, curves.red.value.GetTexture());
material.SetTexture(ShaderConstants._CurveGreen, curves.green.value.GetTexture());
material.SetTexture(ShaderConstants._CurveBlue, curves.blue.value.GetTexture());
// Secondary curves
material.SetTexture(ShaderConstants._CurveHueVsHue, curves.hueVsHue.value.GetTexture());
material.SetTexture(ShaderConstants._CurveHueVsSat, curves.hueVsSat.value.GetTexture());
material.SetTexture(ShaderConstants._CurveLumVsSat, curves.lumVsSat.value.GetTexture());
material.SetTexture(ShaderConstants._CurveSatVsSat, curves.satVsSat.value.GetTexture());
// Tonemapping (baked into the lut for HDR)
if (hdr)
{
material.shaderKeywords = null;
switch (tonemapping.mode.value)
{
case TonemappingMode.Neutral: material.EnableKeyword(ShaderKeywordStrings.TonemapNeutral); break;
case TonemappingMode.ACES: material.EnableKeyword(allowColorGradingACESHDR ? ShaderKeywordStrings.TonemapACES : ShaderKeywordStrings.TonemapNeutral); break;
default: break; // None
}
// HDR output is active
if (passData.cameraData.isHDROutputActive)
{
Vector4 hdrOutputLuminanceParams;
Vector4 hdrOutputGradingParams;
UniversalRenderPipeline.GetHDROutputLuminanceParameters(passData.cameraData.hdrDisplayInformation, passData.cameraData.hdrDisplayColorGamut, tonemapping, out hdrOutputLuminanceParams);
UniversalRenderPipeline.GetHDROutputGradingParameters(tonemapping, out hdrOutputGradingParams);
material.SetVector(ShaderPropertyId.hdrOutputLuminanceParams, hdrOutputLuminanceParams);
material.SetVector(ShaderPropertyId.hdrOutputGradingParams, hdrOutputGradingParams);
HDROutputUtils.ConfigureHDROutput(material, passData.cameraData.hdrDisplayColorGamut, HDROutputUtils.Operation.ColorConversion);
}
}
passData.cameraData.xr.StopSinglePass(cmd);
// Render the lut.
Blitter.BlitTexture(cmd, internalLutTarget, Vector2.one, material, 0);
passData.cameraData.xr.StartSinglePass(cmd);
}
}
internal void Render(RenderGraph renderGraph, ContextContainer frameData, out TextureHandle internalColorLut)
{
UniversalCameraData cameraData = frameData.Get();
UniversalPostProcessingData postProcessingData= frameData.Get();
using (var builder = renderGraph.AddRasterRenderPass(passName, out var passData, profilingSampler))
{
this.ConfigureDescriptor(in postProcessingData, out var lutDesc, out var filterMode);
internalColorLut = UniversalRenderer.CreateRenderGraphTexture(renderGraph, lutDesc, "_InternalGradingLut", true, filterMode);
passData.cameraData = cameraData;
passData.postProcessingData = postProcessingData;
passData.internalLut = internalColorLut;
builder.SetRenderAttachment(internalColorLut, 0, AccessFlags.WriteAll);
passData.lutBuilderLdr = m_LutBuilderLdr;
passData.lutBuilderHdr = m_LutBuilderHdr;
passData.allowColorGradingACESHDR = m_AllowColorGradingACESHDR;
// TODO RENDERGRAPH: culling? force culling off for testing
builder.AllowPassCulling(false);
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
{
ExecutePass(context.cmd, data, data.internalLut);
});
return;
}
}
///
/// Cleans up resources used by the pass.
///
public void Cleanup()
{
CoreUtils.Destroy(m_LutBuilderLdr);
CoreUtils.Destroy(m_LutBuilderHdr);
}
// Precomputed shader ids to same some CPU cycles (mostly affects mobile)
static class ShaderConstants
{
public static readonly int _Lut_Params = Shader.PropertyToID("_Lut_Params");
public static readonly int _ColorBalance = Shader.PropertyToID("_ColorBalance");
public static readonly int _ColorFilter = Shader.PropertyToID("_ColorFilter");
public static readonly int _ChannelMixerRed = Shader.PropertyToID("_ChannelMixerRed");
public static readonly int _ChannelMixerGreen = Shader.PropertyToID("_ChannelMixerGreen");
public static readonly int _ChannelMixerBlue = Shader.PropertyToID("_ChannelMixerBlue");
public static readonly int _HueSatCon = Shader.PropertyToID("_HueSatCon");
public static readonly int _Lift = Shader.PropertyToID("_Lift");
public static readonly int _Gamma = Shader.PropertyToID("_Gamma");
public static readonly int _Gain = Shader.PropertyToID("_Gain");
public static readonly int _Shadows = Shader.PropertyToID("_Shadows");
public static readonly int _Midtones = Shader.PropertyToID("_Midtones");
public static readonly int _Highlights = Shader.PropertyToID("_Highlights");
public static readonly int _ShaHiLimits = Shader.PropertyToID("_ShaHiLimits");
public static readonly int _SplitShadows = Shader.PropertyToID("_SplitShadows");
public static readonly int _SplitHighlights = Shader.PropertyToID("_SplitHighlights");
public static readonly int _CurveMaster = Shader.PropertyToID("_CurveMaster");
public static readonly int _CurveRed = Shader.PropertyToID("_CurveRed");
public static readonly int _CurveGreen = Shader.PropertyToID("_CurveGreen");
public static readonly int _CurveBlue = Shader.PropertyToID("_CurveBlue");
public static readonly int _CurveHueVsHue = Shader.PropertyToID("_CurveHueVsHue");
public static readonly int _CurveHueVsSat = Shader.PropertyToID("_CurveHueVsSat");
public static readonly int _CurveLumVsSat = Shader.PropertyToID("_CurveLumVsSat");
public static readonly int _CurveSatVsSat = Shader.PropertyToID("_CurveSatVsSat");
}
}
}