235 lines
8.8 KiB
GLSL
235 lines
8.8 KiB
GLSL
//This shader file is part of FLAR - Advanced Renderer for Freelancer by Schmackbolzen
|
|
//If you use the supplied shader files you may not modify them unless you state in them what you changed
|
|
//and also mention the source or who the author is.
|
|
//Post processing shader for FLAR by Schmackbolzen
|
|
//Sources are mentioned in the comments
|
|
|
|
#version 330
|
|
#include "ColorConversion.inc"
|
|
#define USE_CURVE $USE_CURVE
|
|
#define USE_PBR_BLOOM $USE_PBR_BLOOM
|
|
|
|
in vec2 TexCoords;
|
|
|
|
uniform sampler2D scene;
|
|
#if USE_PBR_BLOOM == 1
|
|
uniform sampler2D bloomtex;
|
|
#endif
|
|
uniform float exposure;
|
|
uniform float exposureLowerLimit;
|
|
|
|
//https://github.com/TheRealMJP/BakingLab/blob/master/BakingLab/ACES.hlsl
|
|
//ACES fit by Steven Hill
|
|
|
|
// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
|
|
const mat3 ACESInputMat = mat3(
|
|
0.59719, 0.07600, 0.02840,
|
|
0.35458, 0.90834, 0.13383,
|
|
0.04823, 0.01566, 0.83777);
|
|
|
|
// ODT_SAT => XYZ => D60_2_D65 => sRGB
|
|
const mat3 ACESOutputMat = mat3(
|
|
1.60475, -0.10208, -0.00327,
|
|
-0.53108, 1.10813, -0.07276,
|
|
-0.07367, -0.00605, 1.07602
|
|
);
|
|
|
|
vec3 RRTAndODTFit(vec3 v)
|
|
{
|
|
vec3 a = v * (v + 0.0245786f) - 0.000090537f;
|
|
vec3 b = v * (0.983729f * v + 0.4329510f) + 0.238081f;
|
|
return a / b;
|
|
}
|
|
|
|
vec3 ACESFitted(vec3 color)
|
|
{
|
|
color = ACESInputMat * color;
|
|
|
|
// Apply RRT and ODT
|
|
color = RRTAndODTFit(color);
|
|
|
|
color = ACESOutputMat * color;
|
|
|
|
// Clamp to [0, 1]
|
|
color = clamp(color,0.,1.);
|
|
|
|
return color;
|
|
}
|
|
|
|
// Uchimura 2017, "HDR theory and practice"
|
|
// Math: https://www.desmos.com/calculator/gslcdxvipg
|
|
// Source: https://www.slideshare.net/nikuque/hdr-theory-and-practicce-jp
|
|
// Used in "Gran Turismo Sport" game
|
|
vec3 uchimura(vec3 x, float P, float a, float m, float l, float c, float b) {
|
|
float l0 = ((P - m) * l) / a;
|
|
float L0 = m - m / a;
|
|
float L1 = m + (1.0 - m) / a;
|
|
float S0 = m + l0;
|
|
float S1 = m + a * l0;
|
|
float C2 = (a * P) / (P - S1);
|
|
float CP = -C2 / P;
|
|
|
|
vec3 w0 = vec3(1.0 - smoothstep(0.0, m, x));
|
|
vec3 w2 = vec3(step(m + l0, x));
|
|
vec3 w1 = vec3(1.0 - w0 - w2);
|
|
|
|
vec3 T = vec3(m * pow(x / m, vec3(c)) + b);
|
|
vec3 S = vec3(P - (P - S1) * exp(CP * (x - S0)));
|
|
vec3 L = vec3(m + a * (x - m));
|
|
|
|
return T * w0 + L * w1 + S * w2;
|
|
}
|
|
|
|
vec3 uchimura(vec3 x) {
|
|
const float P = 1.0; // max display brightness
|
|
const float a = 1.0; // contrast
|
|
const float m = 0.22; // linear section start
|
|
const float l = 0.4; // linear section length
|
|
const float c = 1.33; // black
|
|
const float b = 0.0; // pedestal
|
|
|
|
return uchimura(x, P, a, m, l, c, b);
|
|
}
|
|
|
|
//new agx from https://github.com/godotengine/godot/pull/106940
|
|
//C++ Code moved to shader by me (Schmackbolzen):
|
|
|
|
const float tonemap_agx_white = 16.29; // Default to Blender's AgX white.
|
|
const float tonemap_agx_contrast = 1.25;
|
|
const float output_max_value = 1.0;
|
|
|
|
// Calculate allenwp tonemapping curve parameters on the CPU to improve shader performance.
|
|
// Source and details: https://allenwp.com/blog/2025/05/29/allenwp-tonemapping-curve/
|
|
|
|
// These constants must match the those in the shader code.
|
|
// 18% "middle gray" is perceptually 50% of the brightness of reference white.
|
|
const float awp_crossover_point = 0.18;
|
|
// When output_max_value and/or awp_crossover_point are no longer constant, awp_shoulder_max can
|
|
// be calculated on the CPU and passed in as tonemap_parameters.tonemap_e.
|
|
const float awp_shoulder_max = output_max_value - awp_crossover_point;
|
|
|
|
// awp_toe_a is a solution generated by Mathematica that ensures intersection at awp_crossover_point.
|
|
const float awp_toe_a = ((1.0 / awp_crossover_point) - 1.0) * pow(awp_crossover_point, tonemap_agx_contrast);
|
|
// Slope formula is simply the derivative of the toe function with an input of awp_crossover_point.
|
|
const float awp_slope_denom = pow(awp_crossover_point, tonemap_agx_contrast) + awp_toe_a;
|
|
const float awp_slope = (tonemap_agx_contrast * pow(awp_crossover_point, tonemap_agx_contrast - 1.0) * awp_toe_a) / (awp_slope_denom * awp_slope_denom);
|
|
|
|
const float awp_high_clip = tonemap_agx_white;
|
|
const float awp_w=pow(awp_high_clip - awp_crossover_point,2)/awp_shoulder_max*awp_slope;
|
|
|
|
const float awp_contrast = tonemap_agx_contrast;
|
|
//End c++ code
|
|
|
|
// allenwp tonemapping curve; developed for use in the Godot game engine.
|
|
// Source and details: https://allenwp.com/blog/2025/05/29/allenwp-tonemapping-curve/
|
|
// Input must be a non-negative linear scene value.
|
|
vec3 allenwp_curve(vec3 x) {
|
|
|
|
// Reinhard-like shoulder:
|
|
vec3 s = x - awp_crossover_point;
|
|
vec3 slope_s = awp_slope * s;
|
|
s = slope_s * (1.0 + s / awp_w) / (1.0 + (slope_s / awp_shoulder_max));
|
|
s += awp_crossover_point;
|
|
|
|
// Sigmoid power function toe:
|
|
vec3 t = pow(x, vec3(awp_contrast));
|
|
t = t / (t + awp_toe_a);
|
|
|
|
return mix(s, t, lessThan(x, vec3(awp_crossover_point)));
|
|
}
|
|
|
|
// This is an approximation and simplification of EaryChow's AgX implementation that is used by Blender.
|
|
// This code is based off of the script that generates the AgX_Base_sRGB.cube LUT that Blender uses.
|
|
// Source: https://github.com/EaryChow/AgX_LUT_Gen/blob/main/AgXBasesRGB.py
|
|
// Colorspace transformation source: https://www.colour-science.org:8010/apps/rgb_colourspace_transformation_matrix
|
|
vec3 tonemap_agx(vec3 color) {
|
|
// Input color should be non-negative!
|
|
// Large negative values in one channel and large positive values in other
|
|
// channels can result in a colour that appears darker and more saturated than
|
|
// desired after passing it through the inset matrix. For this reason, it is
|
|
// best to prevent negative input values.
|
|
// This is done before the Rec. 2020 transform to allow the Rec. 2020
|
|
// transform to be combined with the AgX inset matrix. This results in a loss
|
|
// of color information that could be correctly interpreted within the
|
|
// Rec. 2020 color space as positive RGB values, but is often not worth
|
|
// the performance cost of an additional matrix multiplication.
|
|
//
|
|
// Additionally, this AgX configuration was created subjectively based on
|
|
// output appearance in the Rec. 709 color gamut, so it is possible that these
|
|
// matrices will not perform well with non-Rec. 709 output (more testing with
|
|
// future wide-gamut displays is be needed).
|
|
// See this comment from the author on the decisions made to create the matrices:
|
|
// https://github.com/godotengine/godot-proposals/issues/12317#issuecomment-2835824250
|
|
|
|
// Combined Rec. 709 to Rec. 2020 and Blender AgX inset matrices:
|
|
const mat3 rec709_to_rec2020_agx_inset_matrix = mat3(
|
|
0.544814746488245, 0.140416948464053, 0.0888104196149096,
|
|
0.373787398372697, 0.754137554567394, 0.178871756420858,
|
|
0.0813978551390581, 0.105445496968552, 0.732317823964232);
|
|
|
|
// Combined inverse AgX outset matrix and Rec. 2020 to Rec. 709 matrices.
|
|
const mat3 agx_outset_rec2020_to_rec709_matrix = mat3(
|
|
1.96488741169489, -0.299313364904742, -0.164352742528393,
|
|
-0.855988495690215, 1.32639796461980, -0.238183969428088,
|
|
-0.108898916004672, -0.0270845997150571, 1.40253671195648);
|
|
|
|
// Apply inset matrix.
|
|
color = rec709_to_rec2020_agx_inset_matrix * color;
|
|
|
|
// Use the allenwp tonemapping curve to match the Blender AgX curve while
|
|
// providing stability across all variable dyanimc range (SDR, HDR, EDR).
|
|
color = allenwp_curve(color);
|
|
|
|
// Clipping to output_max_value is required to address a cyan colour that occurs
|
|
// with very bright inputs.
|
|
color = min(vec3(output_max_value), color);
|
|
|
|
// Apply outset to make the result more chroma-laden and then go back to Rec. 709.
|
|
color = agx_outset_rec2020_to_rec709_matrix * color;
|
|
|
|
// Blender's lusRGB.compensate_low_side is too complex for this shader, so
|
|
// simply return the color, even if it has negative components. These negative
|
|
// components may be useful for subsequent color adjustments.
|
|
return color;
|
|
}
|
|
|
|
#if USE_PBR_BLOOM == 1
|
|
const float bloomStrength = 0.2f;
|
|
vec4 bloom_new()
|
|
{
|
|
vec4 hdrColor = texture(scene, TexCoords);
|
|
//hdrColor.rgb=vec3(0);
|
|
vec3 bloomColor = texture(bloomtex, TexCoords).rgb;
|
|
//Blend in linear space
|
|
/*hdrColor.rgb=ToLinear(hdrColor.rgb);
|
|
bloomColor=ToLinear(bloomColor);*/
|
|
vec4 blended=vec4(mix(hdrColor.rgb, bloomColor, bloomStrength),hdrColor.a); // linear interpolation
|
|
//Blend in gamma corrected space
|
|
blended.rgb=ToLinear(blended.rgb);
|
|
return blended;
|
|
}
|
|
#endif
|
|
|
|
void main()
|
|
{
|
|
#if USE_PBR_BLOOM == 0
|
|
vec4 color = texture2D(scene, TexCoords);
|
|
//Not a nice solution to convert two times (ideally there would be none), but not easily solvable currently
|
|
color.rgb = ToLinear(color.rgb);
|
|
#else
|
|
vec4 color = bloom_new();
|
|
#endif
|
|
//Exposure is chosen so that all curves have about the same brightness and the look is fine with very bright starspheres like in Tau-37
|
|
#if USE_CURVE == 0
|
|
color.rgb =tonemap_agx(exposure*color.rgb);
|
|
#elif USE_CURVE == 1
|
|
color.rgb=ACESFitted(exposure*color.rgb);
|
|
#elif USE_CURVE == 2
|
|
color.rgb=uchimura(exposure*color.rgb);
|
|
#endif
|
|
|
|
color.rgb=ToGammaCorrected(color.rgb);
|
|
gl_FragColor = color;
|
|
|
|
}
|