It's not something to do tonemapping by itself, but it's a way of altering the effect the tonemap has on the colors in your image.
You have to provide your own tonemapping function, but the processing is tonemapping curve agnostic, and should work fine with any curve.
What it does is give you control over the amount of hueshift and desaturation that occurs during the tonemap, allowing for hues to be more accurately preserved in highlights while retaining their perceived brightness.
You will need this helper file: There's a number of variables you will need to define as UI parameters or as static consts:
Code: Select all
float Desaturation:
Changes the amount of pre-tonemap desaturation to apply to the image.
Min: 0.0, Max: 1.0, Default: 0.7
float HueShift:
Changes the amount of hueshift allowed.
Min: 0.0, Max: 1.0, Default: 0.4
float Resaturation:
A midrange saturation boost that happens post-tonemapping.
Min: 0.0, Max: 1.0+, Default: 0.3
float Saturation:
Just a standard saturation control, might as well add it here to enjoy the quality saturation of ICtCp
Min: 0.0, Max: 2.0+, Default: 1.0
To match standard tonemapping (essentially disabling the effect), set the parameters to the following:
Desaturation = 0
HueShift = 1
Resaturation = 0
Code: Select all
float3 applyTonemap(float3 color)
{
// insert your tonemapping here
}
#include "ictcp_colorspaces.fx"
float3 frostbyteTonemap(float3 color)
{
float3 ictcp = rgb2ictcp(color.xyz);
float saturation = pow(smoothstep(1.0, 1.0 - Desaturation, ictcp.x), 1.3);
color.xyz = ictcp2rgb(ictcp * float3(1.0, saturation.xx));
float3 perChannel = applyTonemap(color.xyz);
float peak = max(color.x, max(color.y, color.z));
color.xyz *= rcp(peak + 1e-6);
color.xyz *= applyTonemap(peak);
color.xyz = lerp(color.xyz, perChannel, HueShift);
color.xyz = rgb2ictcp(color.xyz);
float saturationBoost = Resaturation * smoothstep(1.0, 0.5, ictcp.x);
color.yz = lerp(color.yz, ictcp.yz * color.x / max(1e-3, ictcp.x), saturationBoost);
color.yz *= Saturation;
color.xyz = ictcp2rgb(color.xyz);
return color;
}
Code: Select all
float naturalShoulder(float x)
{
return 1.0 - exp(-x);
}
float naturalShoulder(float x, float t)
{
float v1 = x;
float v2 = t + (1.0 - t) * naturalShoulder((x - t) / (1.0 - t));
return x < t ? v1 : v2;
}
float3 naturalShoulder(float3 x, float t)
{
return float3(
naturalShoulder(x.x, t),
naturalShoulder(x.y, t),
naturalShoulder(x.z, t)
);
}
float3 applyTonemap(float3 color)
{
// TODO: Add parameters or consts for LinearSection and Whitepoint.
// Try 0.25 for LinearSection and 4.0 for Whitepoint as a start
return naturalShoulder(color.xyz, LinearSection) * rcp(naturalShoulder(Whitepoint, LinearSection));
}
After:
Another comparison: http://www.screenshotcomparison.com/comparison/130281