85 lines
3.4 KiB
Plaintext
85 lines
3.4 KiB
Plaintext
static const float LUT_SIZE = 33.0;
|
|
static const float LUT_LAST_INDEX = 32.0;
|
|
|
|
float3 sampleLutCell(float3 index)
|
|
{
|
|
float r = floor(index.r + 0.5);
|
|
float g = floor(index.g + 0.5);
|
|
float b = floor(index.b + 0.5);
|
|
|
|
// The 33^3 cube is packed as blue slices laid horizontally, with red across
|
|
// each slice and green down the atlas.
|
|
float atlasWidth = LUT_SIZE * LUT_SIZE;
|
|
float2 lutUv;
|
|
lutUv.x = (r + b * LUT_SIZE + 0.5) / atlasWidth;
|
|
lutUv.y = (g + 0.5) / LUT_SIZE;
|
|
return lutTexture.Sample(lutUv).rgb;
|
|
}
|
|
|
|
float3 applyLut33(float3 color)
|
|
{
|
|
float3 lutCoord = saturate(color) * LUT_LAST_INDEX;
|
|
float3 baseIndex = floor(lutCoord);
|
|
float3 nextIndex = min(baseIndex + 1.0, LUT_LAST_INDEX);
|
|
float3 blend = lutCoord - baseIndex;
|
|
|
|
float3 c000 = sampleLutCell(float3(baseIndex.r, baseIndex.g, baseIndex.b));
|
|
float3 c100 = sampleLutCell(float3(nextIndex.r, baseIndex.g, baseIndex.b));
|
|
float3 c010 = sampleLutCell(float3(baseIndex.r, nextIndex.g, baseIndex.b));
|
|
float3 c110 = sampleLutCell(float3(nextIndex.r, nextIndex.g, baseIndex.b));
|
|
float3 c001 = sampleLutCell(float3(baseIndex.r, baseIndex.g, nextIndex.b));
|
|
float3 c101 = sampleLutCell(float3(nextIndex.r, baseIndex.g, nextIndex.b));
|
|
float3 c011 = sampleLutCell(float3(baseIndex.r, nextIndex.g, nextIndex.b));
|
|
float3 c111 = sampleLutCell(float3(nextIndex.r, nextIndex.g, nextIndex.b));
|
|
|
|
// Tetrahedral interpolation chooses one of six paths through the cube.
|
|
// This avoids the muddy diagonals that simple trilinear LUT sampling can
|
|
// introduce for strong grades.
|
|
if (blend.r > blend.g)
|
|
{
|
|
if (blend.g > blend.b)
|
|
return c000 + blend.r * (c100 - c000) + blend.g * (c110 - c100) + blend.b * (c111 - c110);
|
|
if (blend.r > blend.b)
|
|
return c000 + blend.r * (c100 - c000) + blend.b * (c101 - c100) + blend.g * (c111 - c101);
|
|
return c000 + blend.b * (c001 - c000) + blend.r * (c101 - c001) + blend.g * (c111 - c101);
|
|
}
|
|
|
|
if (blend.b > blend.g)
|
|
return c000 + blend.b * (c001 - c000) + blend.g * (c011 - c001) + blend.r * (c111 - c011);
|
|
if (blend.b > blend.r)
|
|
return c000 + blend.g * (c010 - c000) + blend.b * (c011 - c010) + blend.r * (c111 - c011);
|
|
return c000 + blend.g * (c010 - c000) + blend.r * (c110 - c010) + blend.b * (c111 - c110);
|
|
}
|
|
|
|
float hash12(float2 value)
|
|
{
|
|
float3 p = frac(float3(value.xyx) * 0.1031);
|
|
p += dot(p, p.yzx + 33.33);
|
|
return frac((p.x + p.y) * p.z);
|
|
}
|
|
|
|
float3 outputDither(float2 pixel)
|
|
{
|
|
// Subtract paired hashes to center the dither around zero, then scale to
|
|
// roughly one 8-bit code value.
|
|
float r = hash12(pixel + float2(17.0, 31.0)) - hash12(pixel + float2(83.0, 47.0));
|
|
float g = hash12(pixel + float2(29.0, 71.0)) - hash12(pixel + float2(53.0, 19.0));
|
|
float b = hash12(pixel + float2(61.0, 11.0)) - hash12(pixel + float2(7.0, 97.0));
|
|
return float3(r, g, b) / 255.0;
|
|
}
|
|
|
|
float4 shadeVideo(ShaderContext context)
|
|
{
|
|
float4 source = context.sourceColor;
|
|
float3 inputColor = source.rgb * pow(2.0, preExposure);
|
|
if (clampInput)
|
|
inputColor = saturate(inputColor);
|
|
|
|
float3 lutColor = applyLut33(inputColor);
|
|
float3 graded = lerp(inputColor, lutColor, lutStrength);
|
|
graded = (graded - 0.5) * postContrast + 0.5;
|
|
graded += outputDither(context.uv * context.outputResolution) * ditherAmount;
|
|
|
|
return float4(saturate(graded), source.a);
|
|
}
|