Unity自定义SRP(十三):颜色分级

https://catlikecoding.com/unity/tutorials/custom-srp/color-grading/

1 调整颜色

1.1 在色调映射前进行颜色分级调整

​ shader中添加ColorGrade方法:

float3 ColorGrade (float3 color) 
{
    color = min(color, 60.0);
    return color;
}

​ 添加一个不进行色调映射pass,在每个色调映射pass中进行颜色分级:

float4 ToneMappingNonePassFragment (Varyings input) : SV_TARGET 
{
    float4 color = GetSource(input.screenUV);
    color.rgb = ColorGrade(color.rgb);
    return color;
}

float4 ToneMappingACESPassFragment (Varyings input) : SV_TARGET 
{
    float4 color = GetSource(input.screenUV);
    color.rgb = ColorGrade(color.rgb);
    color.rgb = AcesTonemap(unity_to_ACES(color.rgb));
    return color;
}

float4 ToneMappingNeutralPassFragment (Varyings input) : SV_TARGET 
{
    float4 color = GetSource(input.screenUV);
    color.rgb = ColorGrade(color.rgb);
    color.rgb = NeutralTonemap(color.rgb);
    return color;
}

float4 ToneMappingReinhardPassFragment (Varyings input) : SV_TARGET 
{
    float4 color = GetSource(input.screenUV);
    color.rgb = ColorGrade(color.rgb);
    color.rgb /= color.rgb + 1.0;
    return color;
}

1.2 设置

​ 在PostSettings中添加ColorAdjustmentsSettings结构体:

[CreateAssetMenu(menuName = "Rendering/Custom Post FX Settings")]
public class PostFXSettings : ScriptableObject 
{

    …

    [Serializable]
    public struct ColorAdjustmentsSettings {}

    [SerializeField]
    ColorAdjustmentsSettings colorAdjustments = default;

    public ColorAdjustmentsSettings ColorAdjustments => colorAdjustments;

    …
}

​ 添加属性:

    public struct ColorAdjustmentsSettings 
    {

        public float postExposure;

        [Range(-100f, 100f)]
        public float contrast;

        [ColorUsage(false, true)]
        public Color colorFilter;

        [Range(-180f, 180f)]
        public float hueShift;

        [Range(-100f, 100f)]
        public float saturation;
    }

​ 我们将DoToneMapping更名为DoColorGradingAndToneMapping,并添加一个ConfigureColorAdjustments来配置颜色调整:

using static PostFXSettings;

public partial class PostFXStack 
{
    
    …
    
    void ConfigureColorAdjustments () 
    {
        ColorAdjustmentsSettings colorAdjustments = settings.ColorAdjustments;
    }

    void DoColorGradingAndToneMapping (int sourceId) 
    {
        ConfigureColorAdjustments();

        ToneMappingSettings.Mode mode = settings.ToneMapping.mode;
        Pass pass = Pass.ToneMappingNone + (int)mode;
        Draw(sourceId, BuiltinRenderTextureType.CameraTarget, pass);
    }
    
    …
}

​ 我们将一些颜色属性提前调整到合适的范围:

        ColorAdjustmentsSettings colorAdjustments = settings.ColorAdjustments;
        buffer.SetGlobalVector(colorAdjustmentsId, new Vector4(
            Mathf.Pow(2f, colorAdjustments.postExposure),
            colorAdjustments.contrast * 0.01f + 1f,
            colorAdjustments.hueShift * (1f / 360f),
            colorAdjustments.saturation * 0.01f + 1f
        ));
        buffer.SetGlobalColor(colorFilterId, colorAdjustments.colorFilter.linear);

1.3 曝光

float4 _ColorAdjustments;
float4 _ColorFilter;

float3 ColorGradePostExposure (float3 color) 
{
    return color * _ColorAdjustments.x;
}

float3 ColorGrade (float3 color) 
{
    color = min(color, 60.0);
    color = ColorGradePostExposure(color);
    return color;
}

1.4 对比度

​ 将颜色减去中间灰度值,然后通过对比度进行缩放,然后加上中间灰度值,中间灰度值为0.4135884:

float3 ColorGradingContrast (float3 color) 
{
    return (color - ACEScc_MIDGRAY) * _ColorAdjustments.y + ACEScc_MIDGRAY;
}

float3 ColorGrade (float3 color) 
{
    color = min(color, 60.0);
    color = ColorGradePostExposure(color);
    color = ColorGradingContrast(color);
    return color;
}

​ 最好的效果是在对数空间进行:

float3 ColorGradingContrast (float3 color) 
{
    color = LinearToLogC(color);
    color = (color - ACEScc_MIDGRAY) * _ColorAdjustments.y + ACEScc_MIDGRAY;
    return LogCToLinear(color);
}

​ 对比度增加可能获得负的颜色值:

    color = ColorGradingContrast(color);
    color = max(color, 0.0);

1.5 颜色滤波

float3 ColorGradeColorFilter (float3 color) 
{
    return color * _ColorFilter.rgb;
}

float3 ColorGrade (float3 color) 
{
    color = min(color, 60.0);
    color = ColorGradePostExposure(color);
    color = ColorGradingContrast(color);
    color = ColorGradeColorFilter(color);
    color = max(color, 0.0);
    return color;
}

1.6 色相偏移

​ 使用RgbToHsv将颜色转换为HSV的,然后平移色相,并使用RotateHue保证色相不会超出范围,最后转换为RGB:

float3 ColorGradingHueShift (float3 color) 
{
    color = RgbToHsv(color);
    float hue = color.x + _ColorAdjustments.z;
    color.x = RotateHue(hue, 0.0, 1.0);
    return HsvToRgb(color);
}

float3 ColorGrade (float3 color) 
{
    color = min(color, 60.0);
    color = ColorGradePostExposure(color);
    color = ColorGradingContrast(color);
    color = ColorGradeColorFilter(color);
    color = max(color, 0.0);
    color = ColorGradingHueShift(color);
    return color;
}

1.7 饱和度

​ 首先使用Luminance获得颜色的亮度,然后调整:

float3 ColorGradingSaturation (float3 color) 
{
    float luminance = Luminance(color);
    return (color - luminance) * _ColorAdjustments.w + luminance;
}

float3 ColorGrade (float3 color) 
{
    color = min(color, 60.0);
    color = ColorGradePostExposure(color);
    color = ColorGradingContrast(color);
    color = ColorGradeColorFilter(color);
    color = max(color, 0.0);
    color = ColorGradingHueShift(color);
    color = ColorGradingSaturation(color);
    return max(color, 0.0);
}

2 更多的控制

2.1 白平衡

​ 新建WhiteBalanceSettings结构体,包含色温和颜色属性:

    [Serializable]
    public struct WhiteBalanceSettings 
    {

        [Range(-100f, 100f)]
        public float temperature, tint;
    }

    [SerializeField]
    WhiteBalanceSettings whiteBalance = default;

    public WhiteBalanceSettings WhiteBalance => whiteBalance;

​ 创建ConfigureWhiteBalance方法,我们可以使用ColorUtils.ColorBalanceToLMSCoeffs来设置白平衡:

    void ConfigureWhiteBalance () 
    {
        WhiteBalanceSettings whiteBalance = settings.WhiteBalance;
        buffer.SetGlobalVector(whiteBalanceId, ColorUtils.ColorBalanceToLMSCoeffs(
            whiteBalance.temperature, whiteBalance.tint
        ));
    }
    
    void DoColorGradingAndToneMapping (int sourceId) 
    {
        ConfigureColorAdjustments();
        ConfigureWhiteBalance();

        …
    }

​ shader中,首先将颜色转换到LMS空间,然后应用白平衡,之后回到线性空间:

float4 _ColorAdjustments;
float4 _ColorFilter;
float4 _WhiteBalance;

float3 ColorGradePostExposure (float3 color) { … }

float3 ColorGradeWhiteBalance (float3 color) 
{
    color = LinearToLMS(color);
    color *= _WhiteBalance.rgb;
    return LMSToLinear(color);
}

…

float3 ColorGrade (float3 color) 
{
    color = min(color, 60.0);
    color = ColorGradePostExposure(color);
    color = ColorGradeWhiteBalance(color);
    color = ColorGradingContrast(color);
    …
}

2.2 分离色调

​ 用于单独着色阴影和高光。

​ 设置:

    [Serializable]
    public struct SplitToningSettings 
    {

        [ColorUsage(false)]
        public Color shadows, highlights;

        [Range(-100f, 100f)]
        public float balance;
    }

    [SerializeField]
    SplitToningSettings splitToning = new SplitToningSettings {
        shadows = Color.gray,
        highlights = Color.gray
    };

    public SplitToningSettings SplitToning => splitToning;

ConfigureSplitToning:

    void ConfigureSplitToning () 
    {
        SplitToningSettings splitToning = settings.SplitToning;
        Color splitColor = splitToning.shadows;
        splitColor.a = splitToning.balance * 0.01f;
        buffer.SetGlobalColor(splitToningShadowsId, splitColor);
        buffer.SetGlobalColor(splitToningHighlightsId, splitToning.highlights);
    }
    
    void DoColorGradingAndToneMapping (int sourceId) 
    {
        ConfigureColorAdjustments();
        ConfigureWhiteBalance();
        ConfigureSplitToning();

        …
    }

​ shader中,我们在gamma空间执行分离色调,然后回到线性空间:

float4 _WhiteBalance;
float4 _SplitToningShadows, _SplitToningHighlights;

…

float3 ColorGradeSplitToning (float3 color) 
{
    color = PositivePow(color, 1.0 / 2.2);
    float t = saturate(Luminance(saturate(color)) + _SplitToningShadows.w);
    float3 shadows = lerp(0.5, _SplitToningShadows.rgb, 1.0 - t);
    float3 highlights = lerp(0.5, _SplitToningHighlights.rgb, t);
    color = SoftLight(color, shadows);
    color = SoftLight(color, highlights);
    return PositivePow(color, 2.2);
}

…

float3 ColorGrade (float3 color) 
{
    …
    color = ColorGradeColorFilter(color);
    color = max(color, 0.0);
    color = ColorGradeSplitToning(color);
    …
}

2.3 通道混合

​ 设置:

    [Serializable]
    public struct ChannelMixerSettings 
    {

        public Vector3 red, green, blue;
    }
    
    [SerializeField]
    ChannelMixerSettings channelMixer = new ChannelMixerSettings 
    {
        red = Vector3.right,
        green = Vector3.up,
        blue = Vector3.forward
    };

    public ChannelMixerSettings ChannelMixer => channelMixer;

​ 配置:

    void ConfigureChannelMixer () 
    {
        ChannelMixerSettings channelMixer = settings.ChannelMixer;
        buffer.SetGlobalVector(channelMixerRedId, channelMixer.red);
        buffer.SetGlobalVector(channelMixerGreenId, channelMixer.green);
        buffer.SetGlobalVector(channelMixerBlueId, channelMixer.blue);
    }

    void DoColorGradingAndToneMapping (int sourceId) 
    {
        …
        ConfigureSplitToning();
        ConfigureChannelMixer();
        …
    }

​ shader中执行:

float4 _ChannelMixerRed, _ChannelMixerGreen, _ChannelMixerBlue;

…

float3 ColorGradingChannelMixer (float3 color) 
{
    return mul(
        float3x3(_ChannelMixerRed.rgb, _ChannelMixerGreen.rgb, _ChannelMixerBlue.rgb),
        color
    );
}

float3 ColorGrade (float3 color) 
{
    …
    ColorGradeSplitToning(color);
    color = ColorGradingChannelMixer(color);
    color = max(color, 0.0);
    color = ColorGradingHueShift(color);
    …
}

2.4 阴影中间色调高光

​ 设置:

    [Serializable]
    public struct ShadowsMidtonesHighlightsSettings 
    {

        [ColorUsage(false, true)]
        public Color shadows, midtones, highlights;

        [Range(0f, 2f)]
        public float shadowsStart, shadowsEnd, highlightsStart, highLightsEnd;
    }

    [SerializeField]
    ShadowsMidtonesHighlightsSettings
        shadowsMidtonesHighlights = new ShadowsMidtonesHighlightsSettings 
    {
            shadows = Color.white,
            midtones = Color.white,
            highlights = Color.white,
            shadowsEnd = 0.3f,
            highlightsStart = 0.55f,
            highLightsEnd = 1f
        };

    public ShadowsMidtonesHighlightsSettings ShadowsMidtonesHighlights =>
        shadowsMidtonesHighlights;

​ 配置:

    void ConfigureShadowsMidtonesHighlights () 
    {
        ShadowsMidtonesHighlightsSettings smh = settings.ShadowsMidtonesHighlights;
        buffer.SetGlobalColor(smhShadowsId, smh.shadows.linear);
        buffer.SetGlobalColor(smhMidtonesId, smh.midtones.linear);
        buffer.SetGlobalColor(smhHighlightsId, smh.highlights.linear);
        buffer.SetGlobalVector(smhRangeId, new Vector4(
            smh.shadowsStart, smh.shadowsEnd, smh.highlightsStart, smh.highLightsEnd
        ));
    }

    void DoColorGradingAndToneMapping (int sourceId) 
    {
        ConfigureColorAdjustments();
        ConfigureWhiteBalance();
        ConfigureSplitToning();
        ConfigureChannelMixer();
        ConfigureShadowsMidtonesHighlights();

        …
    }

​ shader:

float4 _SMHShadows, _SMHMidtones, _SMHHighlights, _SMHRange;

…

float3 ColorGradingShadowsMidtonesHighlights (float3 color) 
{
    float luminance = Luminance(color);
    float shadowsWeight = 1.0 - smoothstep(_SMHRange.x, _SMHRange.y, luminance);
    float highlightsWeight = smoothstep(_SMHRange.z, _SMHRange.w, luminance);
    float midtonesWeight = 1.0 - shadowWeight - highlightsWeight;
    return
        color * _SMHShadows.rgb * shadowsWeight +
        color * _SMHMidtones.rgb * midtonesWeight +
        color * _SMHHighlights.rgb * highlightsWeight;
}

float3 ColorGrade (float3 color) 
{
    …
    color = ColorGradingChannelMixer(color);
    color = max(color, 0.0);
    color = ColorGradingShadowsMidtonesHighlights(color);
    …
}

2.5 ACES颜色空间

​ 使用ACES色调映射时,Unity会在ACES中执行大部分颜色分级操作来得到更好的效果,我们也这么支持。加入useACES布尔参数:

float3 ColorGradingContrast (float3 color, bool useACES) 
{
    color = useACES ? ACES_to_ACEScc(unity_to_ACES(color)) : LinearToLogC(color);
    color = (color - ACEScc_MIDGRAY) * _ColorAdjustments.y + ACEScc_MIDGRAY;
    return useACES ? ACES_to_ACEScg(ACEScc_to_ACES(color)) : LogCToLinear(color);
}

​ 加入Luminance变体:

float Luminance (float3 color, bool useACES) 
{
    return useACES ? AcesLuminance(color) : Luminance(color);
}

​ 在ColorGradeSplitToningColorGradingShadowsMidtonesHighlights,ColorGradingSaturation中调用。

​ 在ColorGrade中加入参数,并在最后选择是否使用ACES颜色空间:

float3 ColorGrade (float3 color, bool useACES = false) 
{
    color = min(color, 60.0);
    color = ColorGradePostExposure(color);
    color = ColorGradeWhiteBalance(color);
    color = ColorGradingContrast(color, useACES);
    color = ColorGradeColorFilter(color);
    color = max(color, 0.0);
    ColorGradeSplitToning(color, useACES);
    color = ColorGradingChannelMixer(color);
    color = max(color, 0.0);
    color = ColorGradingShadowsMidtonesHighlights(color, useACES);
    color = ColorGradingHueShift(color);
    color = ColorGradingSaturation(color, useACES);
    return max(useACES ? ACEScg_to_ACES(color) : color, 0.0);
}

3 LUT

​ 逐像素执行所有的颜色分级步骤是很耗性能的,我们引入LUT,即查找表。将颜色分级烘培到LUT中,然后采样来转换颜色。

3.1 LUT分辨率

​ 支持多种不同分辨率的LUT,CustomRenderPipelineAsset中:

    public enum ColorLUTResolution { _16 = 16, _32 = 32, _64 = 64 }

    [SerializeField]
    ColorLUTResolution colorLUTResolution = ColorLUTResolution._32;

    protected override RenderPipeline CreatePipeline () 
    {
        return new CustomRenderPipeline(
            allowHDR, useDynamicBatching, useGPUInstancing, useSRPBatcher,
            useLightsPerObject, shadows, postFXSettings, (int)colorLUTResolution
        );
    }

​ 在所有的对应文件中添加相应的参数和变量。

3.2 渲染到2DLUT纹理

​ LUT是3D纹理,但一般的shader不能渲染到3D纹理中,我们可以用一个长条2D纹理来模拟3D纹理。LUT纹理的高为可配置的分辨率,宽为分辨率的平方。在DoColorGradingAndToneMapping中定义渲染纹理:

        ConfigureShadowsMidtonesHighlights();

        int lutHeight = colorLUTResolution;
        int lutWidth = lutHeight * lutHeight;
        buffer.GetTemporaryRT(
            colorGradingLUTId, lutWidth, lutHeight, 0,
            FilterMode.Bilinear, RenderTextureFormat.DefaultHDR
        );

​ 现在我们将颜色分级和色调映射渲染到LUT中。:

        ToneMappingSettings.Mode mode = settings.ToneMapping.mode;
        Pass pass = Pass.ColorGradingNone + (int)mode;
        Draw(sourceId, colorGradingLUTId, pass);
        
        Draw(sourceId, BuiltinRenderTextureType.CameraTarget, Pass.Copy);
        buffer.ReleaseTemporaryRT(colorGradingLUTId);

3.3 LUT颜色矩阵

​ 为创建一个恰当的LUT,我们需要使用一个颜色转换矩阵来填充。添加GetColorGradedLUT方法,我们使用GetLutStripValue方法来获得LUT输入颜色:

float4 _ColorGradingLUTParameters;

float3 GetColorGradedLUT (float2 uv, bool useACES = false) 
{
    float3 color = GetLutStripValue(uv, _ColorGradingLUTParameters);
    return ColorGrade(color, useACES);
}

​ 在所有的色调映射pass中应用。

_ColorGradingLUTParamenters的四个参数为LUT高,0.5/宽,0.5/高,高/高-1:

        buffer.GetTemporaryRT(
            colorGradingLUTId, lutWidth, lutHeight, 0,
            FilterMode.Bilinear, RenderTextureFormat.DefaultHDR
        );
        buffer.SetGlobalVector(colorGradingLUTParametersId, new Vector4(
            lutHeight, 0.5f / lutWidth, 0.5f / lutHeight, lutHeight / (lutHeight - 1f)
        ));

3.4 对数LUT

​ 为支持HDR,LUT中的颜色最好为对数空间的。我们可以引入一个布尔值来判断:

bool _ColorGradingLUTInLogC;

float3 GetColorGradedLUT (float2 uv, bool useACES = false) 
{
    float3 color = GetLutStripValue(uv, _ColorGradingLUTParameters);
    return ColorGrade(_ColorGradingLUTInLogC ? LogCToLinear(color) : color, useACES);
}

​ 如果应用了HDR就是用对数空间:

        ToneMappingSettings.Mode mode = settings.ToneMapping.mode;
        Pass pass = Pass.ColorGradingNone + (int)mode;
        buffer.SetGlobalFloat(
            colorGradingLUTInLogId, useHDR && pass != Pass.ColorGradingNone ? 1f : 0f
        );
        Draw(sourceId, colorGradingLUTId, pass);

3.5 最终pass

​ 创建最终pass,来应用LUT的颜色分级:

float3 ApplyColorGradingLUT (float3 color) 
{
    return color;
}

float4 FinalPassFragment (Varyings input) : SV_TARGET
{
    float4 color = GetSource(input.screenUV);
    color.rgb = ApplyColorGradingLUT(color.rgb);
    return color;
}

​ 在ApplyColorGradingLUT中,调用ApplyLut2D方法:

TEXTURE2D(_ColorGradingLUT);

float3 ApplyColorGradingLUT (float3 color) 
{
    return ApplyLut2D(
        TEXTURE2D_ARGS(_ColorGradingLUT, sampler_linear_clamp),
        saturate(_ColorGradingLUTInLogC ? LinearToLogC(color) : color),
        _ColorGradingLUTParameters.xyz
    );
}

​ 此时的参数应该为1/LUT宽,1/高,高-1,在最后的绘制前设置:

        buffer.SetGlobalVector(colorGradingLUTParametersId,
            new Vector4(1f / lutWidth, 1f / lutHeight, lutHeight - 1f)
        );
        Draw(sourceId, BuiltinRenderTextureType.CameraTarget, Pass.Final);
        buffer.ReleaseTemporaryRT(colorGradingLUTId);

你可能感兴趣的:(Unity自定义SRP(十三):颜色分级)