109 lines
4.3 KiB
HLSL

// SPDX-License-Identifier: (Not available for this version, you are only allowed to use this software if you have express permission from the copyright holder and agreed to the latest NiloToonURP EULA)
// Copyright (c) 2021 Kuroneko ShaderLab Limited
// For more information, visit -> https://github.com/ColinLeung-NiloCat/UnityURPToonLitShaderExample
// #pragma once is a safe guard best practice in almost every .hlsl,
// doing this can make sure your .hlsl's user can include this .hlsl anywhere anytime without producing any multi include conflict
#pragma once
// Source:
// https://github.com/KhronosGroup/ToneMapping/blob/main/PBR_Neutral/pbrNeutral.glsl
// https://modelviewer.dev/examples/tone-mapping
// Input color is non-negative and resides in the Linear Rec. 709 color space.
// Output color is also Linear Rec. 709, but in the [0, 1] range.
float3 KhronosPBRNeutralToneMapping(float3 color) {
const float startCompression = 0.8 - 0.04;
const float desaturation = 0.15;
float x = min(color.r, min(color.g, color.b));
float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;
color -= offset;
float peak = max(color.r, max(color.g, color.b));
if (peak < startCompression) return color;
const float d = 1. - startCompression;
float newPeak = 1. - d * d / (peak + d - startCompression);
color *= newPeak / peak;
float g = 1. - 1. / (desaturation * (peak - newPeak) + 1.);
return lerp(color, newPeak, g);
}
// similar to KhronosPBRNeutralToneMapping, but with linear NPR character color range in mind
half3 NiloNPRNeutralToneMappingV1(half3 color) {
const float startCompression = 0.85; // 0.7~1 is good
const float desaturation = 0.15;
// NiloToon_Character shader's usual range is mostly in 0~1 range, unless it is specular/rim light/emission.
// Here we use a linear multiply for this 0~1 input range to keep most of the character color range(especially skin) doing a linear remap only and exit,
// curve tone mapping will not be applied to any 0~1 input range
color *= startCompression;
float peak = max(color.r, max(color.g, color.b));
if (peak < startCompression) return color;
const float d = 1. - startCompression;
float newPeak = 1. - d * d / (peak + d - startCompression);
color *= newPeak / peak;
float g = 1. - 1. / (desaturation * (peak - newPeak) + 1.);
return lerp(color, newPeak, g);
}
float NiloApplyDarkenToToe(float x)
{
const float toeStrength = 1.0; // Adjust this to control the strength of the toe
const float toeThreshold = 0.08;
if (x >= toeThreshold)
return x;
float t = x / toeThreshold;
float toeValue = t * t * (3.0 - 2.0 * t); // Smoothstep function
return lerp(x * (1.0 - toeStrength), x, toeValue);
}
float3 NiloApplyDarkenToToeToColor(float3 color)
{
return float3(
NiloApplyDarkenToToe(color.r),
NiloApplyDarkenToToe(color.g),
NiloApplyDarkenToToe(color.b)
);
}
half3 NiloHybirdTonemap(half3 color, half3 originalTonemappedColor)
{
// settings
const float startCompression = 1.2; // any 1~max
const float transitionToACESSpeed = 4;
// keep low intensity range (0 ~ startCompression) unchanged and return as is
float peak = max(color.r, max(color.g, color.b));
if (peak < startCompression) return NiloApplyDarkenToToeToColor(color);
// the brighter the value, the more towards the original tonemapped color, so in extreme high value, the tonemapping result will be in sync
return lerp(originalTonemappedColor,color, 1.0 / (1.0 + (peak - startCompression) * transitionToACESSpeed));
/*
// [attempt 1]
// desaturate HDR (1~MAX)
//float compressionFactor = (peak - startCompression) / (peak * desaturation);
//float a = 1.0 / (1.0 + compressionFactor);
//return lerp(peak, color, a);
// [attempt 2]
// Apply ACES-inspired saturation to HDR range
float3 nColor = color / peak; // Normalize color
float3 mColor = saturate((nColor - 0.18) / (1.0 - 0.18)); // Apply shoulder
float3 sColor = lerp(mColor, nColor, desaturation); // Control saturation strength
// Blend between original color and saturated color based on how far into HDR we are
float blend = saturate((peak - startCompression) / (2.0 - startCompression));
return lerp(color, sColor * peak, blend);
*/
}