mirror of
https://github.com/RHeavenStudioPlus/HeavenStudioPlus.git
synced 2024-11-28 20:43:03 +00:00
f14d2a423f
* Super Retro VFX! * Updated Screen Jump default * also airboarder works now --------- Co-authored-by: minenice55 <star.elementa@gmail.com>
650 lines
17 KiB
HLSL
650 lines
17 KiB
HLSL
#ifndef UNITY_POSTFX_COLOR
|
|
#define UNITY_POSTFX_COLOR
|
|
|
|
#include "StdLib.hlsl"
|
|
#include "ACES.hlsl"
|
|
|
|
#define LUT_SPACE_ENCODE(x) LinearToLogC(x)
|
|
#define LUT_SPACE_DECODE(x) LogCToLinear(x)
|
|
|
|
#ifndef USE_PRECISE_LOGC
|
|
// Set to 1 to use more precise but more expensive log/linear conversions. I haven't found a proper
|
|
// use case for the high precision version yet so I'm leaving this to 0.
|
|
#define USE_PRECISE_LOGC 0
|
|
#endif
|
|
|
|
#ifndef TONEMAPPING_USE_FULL_ACES
|
|
// Set to 1 to use the full reference ACES tonemapper. This should only be used for research
|
|
// purposes as it's quite heavy and generally overkill.
|
|
#define TONEMAPPING_USE_FULL_ACES 0
|
|
#endif
|
|
|
|
#ifndef DEFAULT_MAX_PQ
|
|
// PQ ST.2048 max value
|
|
// 1.0 = 100nits, 100.0 = 10knits
|
|
#define DEFAULT_MAX_PQ 100.0
|
|
#endif
|
|
|
|
#ifndef USE_VERY_FAST_SRGB
|
|
#if defined(SHADER_API_MOBILE)
|
|
#define USE_VERY_FAST_SRGB 1
|
|
#else
|
|
#define USE_VERY_FAST_SRGB 0
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef USE_FAST_SRGB
|
|
#if defined(SHADER_API_CONSOLE)
|
|
#define USE_FAST_SRGB 1
|
|
#else
|
|
#define USE_FAST_SRGB 0
|
|
#endif
|
|
#endif
|
|
|
|
//
|
|
// Alexa LogC converters (El 1000)
|
|
// See http://www.vocas.nl/webfm_send/964
|
|
// Max range is ~58.85666
|
|
//
|
|
struct ParamsLogC
|
|
{
|
|
float cut;
|
|
float a, b, c, d, e, f;
|
|
};
|
|
|
|
static const ParamsLogC LogC =
|
|
{
|
|
0.011361, // cut
|
|
5.555556, // a
|
|
0.047996, // b
|
|
0.244161, // c
|
|
0.386036, // d
|
|
5.301883, // e
|
|
0.092819 // f
|
|
};
|
|
|
|
float LinearToLogC_Precise(half x)
|
|
{
|
|
float o;
|
|
if (x > LogC.cut)
|
|
o = LogC.c * log10(LogC.a * x + LogC.b) + LogC.d;
|
|
else
|
|
o = LogC.e * x + LogC.f;
|
|
return o;
|
|
}
|
|
|
|
float3 LinearToLogC(float3 x)
|
|
{
|
|
#if USE_PRECISE_LOGC
|
|
return float3(
|
|
LinearToLogC_Precise(x.x),
|
|
LinearToLogC_Precise(x.y),
|
|
LinearToLogC_Precise(x.z)
|
|
);
|
|
#else
|
|
return LogC.c * log10(LogC.a * x + LogC.b) + LogC.d;
|
|
#endif
|
|
}
|
|
|
|
float LogCToLinear_Precise(float x)
|
|
{
|
|
float o;
|
|
if (x > LogC.e * LogC.cut + LogC.f)
|
|
o = (pow(10.0, (x - LogC.d) / LogC.c) - LogC.b) / LogC.a;
|
|
else
|
|
o = (x - LogC.f) / LogC.e;
|
|
return o;
|
|
}
|
|
|
|
float3 LogCToLinear(float3 x)
|
|
{
|
|
#if USE_PRECISE_LOGC
|
|
return float3(
|
|
LogCToLinear_Precise(x.x),
|
|
LogCToLinear_Precise(x.y),
|
|
LogCToLinear_Precise(x.z)
|
|
);
|
|
#else
|
|
return (pow(10.0, (x - LogC.d) / LogC.c) - LogC.b) / LogC.a;
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// SMPTE ST.2084 (PQ) transfer functions
|
|
// Used for HDR Lut storage, max range depends on the maxPQValue parameter
|
|
//
|
|
struct ParamsPQ
|
|
{
|
|
float N, M;
|
|
float C1, C2, C3;
|
|
};
|
|
|
|
static const ParamsPQ PQ =
|
|
{
|
|
2610.0 / 4096.0 / 4.0, // N
|
|
2523.0 / 4096.0 * 128.0, // M
|
|
3424.0 / 4096.0, // C1
|
|
2413.0 / 4096.0 * 32.0, // C2
|
|
2392.0 / 4096.0 * 32.0, // C3
|
|
};
|
|
|
|
float3 LinearToPQ(float3 x, float maxPQValue)
|
|
{
|
|
x = PositivePow(x / maxPQValue, PQ.N);
|
|
float3 nd = (PQ.C1 + PQ.C2 * x) / (1.0 + PQ.C3 * x);
|
|
return PositivePow(nd, PQ.M);
|
|
}
|
|
|
|
float3 LinearToPQ(float3 x)
|
|
{
|
|
return LinearToPQ(x, DEFAULT_MAX_PQ);
|
|
}
|
|
|
|
float3 PQToLinear(float3 x, float maxPQValue)
|
|
{
|
|
x = PositivePow(x, rcp(PQ.M));
|
|
float3 nd = max(x - PQ.C1, 0.0) / (PQ.C2 - (PQ.C3 * x));
|
|
return PositivePow(nd, rcp(PQ.N)) * maxPQValue;
|
|
}
|
|
|
|
float3 PQToLinear(float3 x)
|
|
{
|
|
return PQToLinear(x, DEFAULT_MAX_PQ);
|
|
}
|
|
|
|
//
|
|
// sRGB transfer functions
|
|
// Fast path ref: http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
|
|
//
|
|
half SRGBToLinear(half c)
|
|
{
|
|
#if USE_VERY_FAST_SRGB
|
|
return c * c;
|
|
#elif USE_FAST_SRGB
|
|
return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878);
|
|
#else
|
|
half linearRGBLo = c / 12.92;
|
|
half linearRGBHi = PositivePow((c + 0.055) / 1.055, 2.4);
|
|
half linearRGB = (c <= 0.04045) ? linearRGBLo : linearRGBHi;
|
|
return linearRGB;
|
|
#endif
|
|
}
|
|
|
|
half3 SRGBToLinear(half3 c)
|
|
{
|
|
#if USE_VERY_FAST_SRGB
|
|
return c * c;
|
|
#elif USE_FAST_SRGB
|
|
return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878);
|
|
#else
|
|
half3 linearRGBLo = c / 12.92;
|
|
half3 linearRGBHi = PositivePow((c + 0.055) / 1.055, half3(2.4, 2.4, 2.4));
|
|
half3 linearRGB = (c <= 0.04045) ? linearRGBLo : linearRGBHi;
|
|
return linearRGB;
|
|
#endif
|
|
}
|
|
|
|
half4 SRGBToLinear(half4 c)
|
|
{
|
|
return half4(SRGBToLinear(c.rgb), c.a);
|
|
}
|
|
|
|
half LinearToSRGB(half c)
|
|
{
|
|
#if USE_VERY_FAST_SRGB
|
|
return sqrt(c);
|
|
#elif USE_FAST_SRGB
|
|
return max(1.055 * PositivePow(c, 0.416666667) - 0.055, 0.0);
|
|
#else
|
|
half sRGBLo = c * 12.92;
|
|
half sRGBHi = (PositivePow(c, 1.0 / 2.4) * 1.055) - 0.055;
|
|
half sRGB = (c <= 0.0031308) ? sRGBLo : sRGBHi;
|
|
return sRGB;
|
|
#endif
|
|
}
|
|
|
|
half3 LinearToSRGB(half3 c)
|
|
{
|
|
#if USE_VERY_FAST_SRGB
|
|
return sqrt(c);
|
|
#elif USE_FAST_SRGB
|
|
return max(1.055 * PositivePow(c, 0.416666667) - 0.055, 0.0);
|
|
#else
|
|
half3 sRGBLo = c * 12.92;
|
|
half3 sRGBHi = (PositivePow(c, half3(1.0 / 2.4, 1.0 / 2.4, 1.0 / 2.4)) * 1.055) - 0.055;
|
|
half3 sRGB = (c <= 0.0031308) ? sRGBLo : sRGBHi;
|
|
return sRGB;
|
|
#endif
|
|
}
|
|
|
|
half4 LinearToSRGB(half4 c)
|
|
{
|
|
return half4(LinearToSRGB(c.rgb), c.a);
|
|
}
|
|
|
|
//
|
|
// Convert rgb to luminance with rgb in linear space with sRGB primaries and D65 white point
|
|
//
|
|
half Luminance(half3 linearRgb)
|
|
{
|
|
return dot(linearRgb, float3(0.2126729, 0.7151522, 0.0721750));
|
|
}
|
|
|
|
half Luminance(half4 linearRgba)
|
|
{
|
|
return Luminance(linearRgba.rgb);
|
|
}
|
|
|
|
//
|
|
// Quadratic color thresholding
|
|
// curve = (threshold - knee, knee * 2, 0.25 / knee)
|
|
//
|
|
half4 QuadraticThreshold(half4 color, half threshold, half3 curve)
|
|
{
|
|
// Pixel brightness
|
|
half br = Max3(color.r, color.g, color.b);
|
|
|
|
// Under-threshold part: quadratic curve
|
|
half rq = clamp(br - curve.x, 0.0, curve.y);
|
|
rq = curve.z * rq * rq;
|
|
|
|
// Combine and apply the brightness response curve.
|
|
color *= max(rq, br - threshold) / max(br, EPSILON);
|
|
|
|
return color;
|
|
}
|
|
|
|
//
|
|
// Fast reversible tonemapper
|
|
// http://gpuopen.com/optimized-reversible-tonemapper-for-resolve/
|
|
//
|
|
float3 FastTonemap(float3 c)
|
|
{
|
|
return c * rcp(Max3(c.r, c.g, c.b) + 1.0);
|
|
}
|
|
|
|
float4 FastTonemap(float4 c)
|
|
{
|
|
return float4(FastTonemap(c.rgb), c.a);
|
|
}
|
|
|
|
float3 FastTonemap(float3 c, float w)
|
|
{
|
|
return c * (w * rcp(Max3(c.r, c.g, c.b) + 1.0));
|
|
}
|
|
|
|
float4 FastTonemap(float4 c, float w)
|
|
{
|
|
return float4(FastTonemap(c.rgb, w), c.a);
|
|
}
|
|
|
|
float3 FastTonemapInvert(float3 c)
|
|
{
|
|
return c * rcp(1.0 - Max3(c.r, c.g, c.b));
|
|
}
|
|
|
|
float4 FastTonemapInvert(float4 c)
|
|
{
|
|
return float4(FastTonemapInvert(c.rgb), c.a);
|
|
}
|
|
|
|
//
|
|
// Neutral tonemapping (Hable/Hejl/Frostbite)
|
|
// Input is linear RGB
|
|
//
|
|
float3 NeutralCurve(float3 x, float a, float b, float c, float d, float e, float f)
|
|
{
|
|
return ((x * (a * x + c * b) + d * e) / (x * (a * x + b) + d * f)) - e / f;
|
|
}
|
|
|
|
float3 NeutralTonemap(float3 x)
|
|
{
|
|
// Tonemap
|
|
float a = 0.2;
|
|
float b = 0.29;
|
|
float c = 0.24;
|
|
float d = 0.272;
|
|
float e = 0.02;
|
|
float f = 0.3;
|
|
float whiteLevel = 5.3;
|
|
float whiteClip = 1.0;
|
|
|
|
float3 whiteScale = (1.0).xxx / NeutralCurve(whiteLevel, a, b, c, d, e, f);
|
|
x = NeutralCurve(x * whiteScale, a, b, c, d, e, f);
|
|
x *= whiteScale;
|
|
|
|
// Post-curve white point adjustment
|
|
x /= whiteClip.xxx;
|
|
|
|
return x;
|
|
}
|
|
|
|
//
|
|
// Raw, unoptimized version of John Hable's artist-friendly tone curve
|
|
// Input is linear RGB
|
|
//
|
|
float EvalCustomSegment(float x, float4 segmentA, float2 segmentB)
|
|
{
|
|
const float kOffsetX = segmentA.x;
|
|
const float kOffsetY = segmentA.y;
|
|
const float kScaleX = segmentA.z;
|
|
const float kScaleY = segmentA.w;
|
|
const float kLnA = segmentB.x;
|
|
const float kB = segmentB.y;
|
|
|
|
float x0 = (x - kOffsetX) * kScaleX;
|
|
float y0 = (x0 > 0.0) ? exp(kLnA + kB * log(x0)) : 0.0;
|
|
return y0 * kScaleY + kOffsetY;
|
|
}
|
|
|
|
float EvalCustomCurve(float x, float3 curve, float4 toeSegmentA, float2 toeSegmentB, float4 midSegmentA, float2 midSegmentB, float4 shoSegmentA, float2 shoSegmentB)
|
|
{
|
|
float4 segmentA;
|
|
float2 segmentB;
|
|
|
|
if (x < curve.y)
|
|
{
|
|
segmentA = toeSegmentA;
|
|
segmentB = toeSegmentB;
|
|
}
|
|
else if (x < curve.z)
|
|
{
|
|
segmentA = midSegmentA;
|
|
segmentB = midSegmentB;
|
|
}
|
|
else
|
|
{
|
|
segmentA = shoSegmentA;
|
|
segmentB = shoSegmentB;
|
|
}
|
|
|
|
return EvalCustomSegment(x, segmentA, segmentB);
|
|
}
|
|
|
|
// curve: x: inverseWhitePoint, y: x0, z: x1
|
|
float3 CustomTonemap(float3 x, float3 curve, float4 toeSegmentA, float2 toeSegmentB, float4 midSegmentA, float2 midSegmentB, float4 shoSegmentA, float2 shoSegmentB)
|
|
{
|
|
float3 normX = x * curve.x;
|
|
float3 ret;
|
|
ret.x = EvalCustomCurve(normX.x, curve, toeSegmentA, toeSegmentB, midSegmentA, midSegmentB, shoSegmentA, shoSegmentB);
|
|
ret.y = EvalCustomCurve(normX.y, curve, toeSegmentA, toeSegmentB, midSegmentA, midSegmentB, shoSegmentA, shoSegmentB);
|
|
ret.z = EvalCustomCurve(normX.z, curve, toeSegmentA, toeSegmentB, midSegmentA, midSegmentB, shoSegmentA, shoSegmentB);
|
|
return ret;
|
|
}
|
|
|
|
//
|
|
// Filmic tonemapping (ACES fitting, unless TONEMAPPING_USE_FULL_ACES is set to 1)
|
|
// Input is ACES2065-1 (AP0 w/ linear encoding)
|
|
//
|
|
float3 AcesTonemap(float3 aces)
|
|
{
|
|
#if TONEMAPPING_USE_FULL_ACES
|
|
|
|
float3 oces = RRT(aces);
|
|
float3 odt = ODT_RGBmonitor_100nits_dim(oces);
|
|
return odt;
|
|
|
|
#else
|
|
|
|
// --- Glow module --- //
|
|
float saturation = rgb_2_saturation(aces);
|
|
float ycIn = rgb_2_yc(aces);
|
|
float s = sigmoid_shaper((saturation - 0.4) / 0.2);
|
|
float addedGlow = 1.0 + glow_fwd(ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID);
|
|
aces *= addedGlow;
|
|
|
|
// --- Red modifier --- //
|
|
float hue = rgb_2_hue(aces);
|
|
float centeredHue = center_hue(hue, RRT_RED_HUE);
|
|
float hueWeight;
|
|
{
|
|
//hueWeight = cubic_basis_shaper(centeredHue, RRT_RED_WIDTH);
|
|
hueWeight = smoothstep(0.0, 1.0, 1.0 - abs(2.0 * centeredHue / RRT_RED_WIDTH));
|
|
hueWeight *= hueWeight;
|
|
}
|
|
|
|
aces.r += hueWeight * saturation * (RRT_RED_PIVOT - aces.r) * (1.0 - RRT_RED_SCALE);
|
|
|
|
// --- ACES to RGB rendering space --- //
|
|
float3 acescg = max(0.0, ACES_to_ACEScg(aces));
|
|
|
|
// --- Global desaturation --- //
|
|
//acescg = mul(RRT_SAT_MAT, acescg);
|
|
acescg = lerp(dot(acescg, AP1_RGB2Y).xxx, acescg, RRT_SAT_FACTOR.xxx);
|
|
|
|
// Luminance fitting of *RRT.a1.0.3 + ODT.Academy.RGBmonitor_100nits_dim.a1.0.3*.
|
|
// https://github.com/colour-science/colour-unity/blob/master/Assets/Colour/Notebooks/CIECAM02_Unity.ipynb
|
|
// RMSE: 0.0012846272106
|
|
const float a = 278.5085;
|
|
const float b = 10.7772;
|
|
const float c = 293.6045;
|
|
const float d = 88.7122;
|
|
const float e = 80.6889;
|
|
float3 x = acescg;
|
|
float3 rgbPost = (x * (a * x + b)) / (x * (c * x + d) + e);
|
|
|
|
// Scale luminance to linear code value
|
|
// float3 linearCV = Y_2_linCV(rgbPost, CINEMA_WHITE, CINEMA_BLACK);
|
|
|
|
// Apply gamma adjustment to compensate for dim surround
|
|
float3 linearCV = darkSurround_to_dimSurround(rgbPost);
|
|
|
|
// Apply desaturation to compensate for luminance difference
|
|
//linearCV = mul(ODT_SAT_MAT, color);
|
|
linearCV = lerp(dot(linearCV, AP1_RGB2Y).xxx, linearCV, ODT_SAT_FACTOR.xxx);
|
|
|
|
// Convert to display primary encoding
|
|
// Rendering space RGB to XYZ
|
|
float3 XYZ = mul(AP1_2_XYZ_MAT, linearCV);
|
|
|
|
// Apply CAT from ACES white point to assumed observer adapted white point
|
|
XYZ = mul(D60_2_D65_CAT, XYZ);
|
|
|
|
// CIE XYZ to display primaries
|
|
linearCV = mul(XYZ_2_REC709_MAT, XYZ);
|
|
|
|
return linearCV;
|
|
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// 3D LUT grading
|
|
// scaleOffset = (1 / lut_size, lut_size - 1)
|
|
//
|
|
half3 ApplyLut3D(TEXTURE3D_ARGS(tex, samplerTex), float3 uvw, float2 scaleOffset)
|
|
{
|
|
uvw.xyz = uvw.xyz * scaleOffset.yyy * scaleOffset.xxx + scaleOffset.xxx * 0.5;
|
|
return SAMPLE_TEXTURE3D(tex, samplerTex, uvw).rgb;
|
|
}
|
|
|
|
//
|
|
// 2D LUT grading
|
|
// scaleOffset = (1 / lut_width, 1 / lut_height, lut_height - 1)
|
|
//
|
|
half3 ApplyLut2D(TEXTURE2D_ARGS(tex, samplerTex), float3 uvw, float3 scaleOffset)
|
|
{
|
|
// Strip format where `height = sqrt(width)`
|
|
uvw.z *= scaleOffset.z;
|
|
float shift = floor(uvw.z);
|
|
uvw.xy = uvw.xy * scaleOffset.z * scaleOffset.xy + scaleOffset.xy * 0.5;
|
|
uvw.x += shift * scaleOffset.y;
|
|
uvw.xyz = lerp(
|
|
SAMPLE_TEXTURE2D(tex, samplerTex, uvw.xy).rgb,
|
|
SAMPLE_TEXTURE2D(tex, samplerTex, uvw.xy + float2(scaleOffset.y, 0.0)).rgb,
|
|
uvw.z - shift
|
|
);
|
|
return uvw;
|
|
}
|
|
|
|
//
|
|
// Returns the default value for a given position on a 2D strip-format color lookup table
|
|
// params = (lut_height, 0.5 / lut_width, 0.5 / lut_height, lut_height / lut_height - 1)
|
|
//
|
|
float3 GetLutStripValue(float2 uv, float4 params)
|
|
{
|
|
uv -= params.yz;
|
|
float3 color;
|
|
color.r = frac(uv.x * params.x);
|
|
color.b = uv.x - color.r / params.x;
|
|
color.g = uv.y;
|
|
return color * params.w;
|
|
}
|
|
|
|
//
|
|
// White balance
|
|
// Recommended workspace: ACEScg (linear)
|
|
//
|
|
static const float3x3 LIN_2_LMS_MAT = {
|
|
3.90405e-1, 5.49941e-1, 8.92632e-3,
|
|
7.08416e-2, 9.63172e-1, 1.35775e-3,
|
|
2.31082e-2, 1.28021e-1, 9.36245e-1
|
|
};
|
|
|
|
static const float3x3 LMS_2_LIN_MAT = {
|
|
2.85847e+0, -1.62879e+0, -2.48910e-2,
|
|
-2.10182e-1, 1.15820e+0, 3.24281e-4,
|
|
-4.18120e-2, -1.18169e-1, 1.06867e+0
|
|
};
|
|
|
|
float3 WhiteBalance(float3 c, float3 balance)
|
|
{
|
|
float3 lms = mul(LIN_2_LMS_MAT, c);
|
|
lms *= balance;
|
|
return mul(LMS_2_LIN_MAT, lms);
|
|
}
|
|
|
|
//
|
|
// RGB / Full-range YCbCr conversions (ITU-R BT.601)
|
|
//
|
|
float3 RgbToYCbCr(float3 c)
|
|
{
|
|
float Y = 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
|
|
float Cb = -0.169 * c.r - 0.331 * c.g + 0.500 * c.b;
|
|
float Cr = 0.500 * c.r - 0.419 * c.g - 0.081 * c.b;
|
|
return float3(Y, Cb, Cr);
|
|
}
|
|
|
|
float3 YCbCrToRgb(float3 c)
|
|
{
|
|
float R = c.x + 0.000 * c.y + 1.403 * c.z;
|
|
float G = c.x - 0.344 * c.y - 0.714 * c.z;
|
|
float B = c.x - 1.773 * c.y + 0.000 * c.z;
|
|
return float3(R, G, B);
|
|
}
|
|
|
|
//
|
|
// Hue, Saturation, Value
|
|
// Ranges:
|
|
// Hue [0.0, 1.0]
|
|
// Sat [0.0, 1.0]
|
|
// Lum [0.0, HALF_MAX]
|
|
//
|
|
float3 RgbToHsv(float3 c)
|
|
{
|
|
float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
|
float4 p = lerp(float4(c.bg, K.wz), float4(c.gb, K.xy), step(c.b, c.g));
|
|
float4 q = lerp(float4(p.xyw, c.r), float4(c.r, p.yzx), step(p.x, c.r));
|
|
float d = q.x - min(q.w, q.y);
|
|
float e = EPSILON;
|
|
return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
|
}
|
|
|
|
float3 HsvToRgb(float3 c)
|
|
{
|
|
float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
|
float3 p = abs(frac(c.xxx + K.xyz) * 6.0 - K.www);
|
|
return c.z * lerp(K.xxx, saturate(p - K.xxx), c.y);
|
|
}
|
|
|
|
float RotateHue(float value, float low, float hi)
|
|
{
|
|
return (value < low)
|
|
? value + hi
|
|
: (value > hi)
|
|
? value - hi
|
|
: value;
|
|
}
|
|
|
|
//
|
|
// RGB Saturation (closer to a vibrance effect than actual saturation)
|
|
// Recommended workspace: ACEScg (linear)
|
|
// Optimal range: [0.0, 2.0]
|
|
//
|
|
float3 Saturation(float3 c, float sat)
|
|
{
|
|
float luma = Luminance(c);
|
|
return luma.xxx + sat.xxx * (c - luma.xxx);
|
|
}
|
|
|
|
//
|
|
// Contrast (reacts better when applied in log)
|
|
// Optimal range: [0.0, 2.0]
|
|
//
|
|
float3 Contrast(float3 c, float midpoint, float contrast)
|
|
{
|
|
return (c - midpoint) * contrast + midpoint;
|
|
}
|
|
|
|
//
|
|
// Lift, Gamma (pre-inverted), Gain tuned for HDR use - best used with the ACES tonemapper as
|
|
// negative values will creep in the result
|
|
// Expected workspace: ACEScg (linear)
|
|
//
|
|
float3 LiftGammaGainHDR(float3 c, float3 lift, float3 invgamma, float3 gain)
|
|
{
|
|
c = c * gain + lift;
|
|
|
|
// ACEScg will output negative values, as clamping to 0 will lose precious information we'll
|
|
// mirror the gamma function instead
|
|
return FastSign(c) * pow(abs(c), invgamma);
|
|
}
|
|
|
|
//
|
|
// Lift, Gamma (pre-inverted), Gain tuned for LDR use
|
|
// Input is linear RGB
|
|
//
|
|
float3 LiftGammaGainLDR(float3 c, float3 lift, float3 invgamma, float3 gain)
|
|
{
|
|
c = saturate(PositivePow(saturate(c), invgamma));
|
|
return gain * c + lift * (1.0 - c);
|
|
}
|
|
|
|
//
|
|
// Remaps Y/R/G/B values
|
|
// curveTex has to be 128 pixels wide
|
|
//
|
|
float3 YrgbCurve(float3 c, TEXTURE2D_ARGS(curveTex, sampler_curveTex))
|
|
{
|
|
const float kHalfPixel = (1.0 / 128.0) / 2.0;
|
|
|
|
// Y (master)
|
|
c += kHalfPixel.xxx;
|
|
float mr = SAMPLE_TEXTURE2D(curveTex, sampler_curveTex, float2(c.r, 0.75)).a;
|
|
float mg = SAMPLE_TEXTURE2D(curveTex, sampler_curveTex, float2(c.g, 0.75)).a;
|
|
float mb = SAMPLE_TEXTURE2D(curveTex, sampler_curveTex, float2(c.b, 0.75)).a;
|
|
c = saturate(float3(mr, mg, mb));
|
|
|
|
// RGB
|
|
c += kHalfPixel.xxx;
|
|
float r = SAMPLE_TEXTURE2D(curveTex, sampler_curveTex, float2(c.r, 0.75)).r;
|
|
float g = SAMPLE_TEXTURE2D(curveTex, sampler_curveTex, float2(c.g, 0.75)).g;
|
|
float b = SAMPLE_TEXTURE2D(curveTex, sampler_curveTex, float2(c.b, 0.75)).b;
|
|
return saturate(float3(r, g, b));
|
|
}
|
|
|
|
//
|
|
// Channel mixing (same as Photoshop's and DaVinci's Resolve)
|
|
// Recommended workspace: ACEScg (linear)
|
|
// Input mixers should be in range [-2.0; 2.0]
|
|
//
|
|
float3 ChannelMixer(float3 c, float3 red, float3 green, float3 blue)
|
|
{
|
|
return float3(
|
|
dot(c, red),
|
|
dot(c, green),
|
|
dot(c, blue)
|
|
);
|
|
}
|
|
|
|
#endif // UNITY_POSTFX_COLOR
|