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);
}
在ColorGradeSplitToning
,ColorGradingShadowsMidtonesHighlights
,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);