16
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-07-18 00:49:19 +02:00

AGX tonemapping

This commit is contained in:
2026-06-27 14:39:33 +02:00
parent f07c8fdcd2
commit d21cd54f89

View File

@@ -40,23 +40,116 @@ vec3 filmic(vec3 x)
return filmicF(x) / filmicF(vec3(11.2f));
}
// AgX tonemapping based on nxrighthere / Missing Deadlines implementation.
// 0: Default, 1: Golden, 2: Punchy
#ifndef AGX_LOOK
#define AGX_LOOK 2
#endif
vec3 AgxDefaultContrastApprox(vec3 x)
{
vec3 x2 = x * x;
vec3 x4 = x2 * x2;
return 15.5 * x4 * x2
- 40.14 * x4 * x
+ 31.96 * x4
- 6.868 * x2 * x
+ 0.4298 * x2
+ 0.1191 * x
- 0.00232;
}
vec3 Agx(vec3 val)
{
mat3 agx_mat = mat3(
0.842479062253094, 0.0784335999999992, 0.0792237451477643,
0.0423282422610123, 0.878468636469772, 0.0791661274605434,
0.0423756549057051, 0.0784336, 0.879142973793104
);
// DEFAULT_LOG2_MIN = -10.0
// DEFAULT_LOG2_MAX = +6.5
// MIDDLE_GRAY = 0.18
// log2(pow(2, VALUE) * MIDDLE_GRAY)
const float min_ev = -12.47393;
const float max_ev = 0.526069;
const float agx_eps = 1e-6;
// Input transform (inset)
val = agx_mat * val;
// Log2 space encoding. max() avoids -INF/NaN for zero/negative inputs.
val = clamp(log2(max(val, vec3(agx_eps))), min_ev, max_ev);
val = (val - min_ev) / (max_ev - min_ev);
// Apply sigmoid function approximation.
return AgxDefaultContrastApprox(val);
}
vec3 AgxEotf(vec3 val)
{
mat3 agx_mat_inv = mat3(
1.19687900512017, -0.0980208811401368, -0.0990297440797205,
-0.0528968517574562, 1.15190312990417, -0.0989611768448433,
-0.0529716355144438, -0.0980434501171241, 1.15107367264116
);
// Inverse input transform (outset)
val = agx_mat_inv * val;
// sRGB IEC 61966-2-1 2.2 Exponent Reference EOTF Display.
// If your render target already applies sRGB conversion, replace this with:
// return max(val, vec3(0.0));
return pow(max(val, vec3(0.0)), vec3(2.2));
}
vec3 AgxLook(vec3 val)
{
vec3 lw = vec3(0.2126, 0.7152, 0.0722);
float luma = dot(val, lw);
vec3 offset = vec3(0.0);
vec3 slope = vec3(1.0);
vec3 power = vec3(1.0);
float sat = 1.0;
#if AGX_LOOK == 1
// Golden
slope = vec3(1.0, 0.9, 0.5);
power = vec3(0.8);
sat = 0.8;
#elif AGX_LOOK == 2
// Punchy
slope = vec3(1.0);
power = vec3(1.35);
sat = 1.4;
#endif
// ASC CDL
val = pow(max(val * slope + offset, vec3(0.0)), power);
return vec3(luma) + sat * (val - vec3(luma));
}
vec3 ApplyAgX(vec3 linearColorRec709)
{
linearColorRec709 = Agx(linearColorRec709);
linearColorRec709 = AgxLook(linearColorRec709);
linearColorRec709 = AgxEotf(linearColorRec709);
return linearColorRec709;
}
vec4 tonemap(vec4 x)
{
// Use ACES Filmic by default. Reinhard above kept for reference, but
// with pureWhite=1.0 it collapses to identity (L*(1+L)/(1+L) = L) and
// just clips HDR>1.0 at the framebuffer -> washed-out / burnt look.
// ACES gives a smooth highlight shoulder + slight toe contrast.
// Use AgX by default. Reinhard and ACES above are kept for reference.
// Last-line-of-defense sanitize. ACESFilm has the form
// (x*(a*x+b)) / (x*(c*x+d)+e)
// which maps NaN -> NaN and +Inf -> NaN (Inf/Inf). Either turns the
// pixel black after framebuffer clamp. A negative HDR input feeds a
// negative numerator/denominator and can produce non-physical output
// that also looks like a black flash. Clamp to a sensible range
// before the curve so a single bad upstream pixel can't escape.
// Last-line-of-defense sanitize so NaN/Inf/negative HDR values do not
// escape into log2()/pow() and produce black flashes or invalid output.
vec3 hdr = x.rgb;
hdr = mix(hdr, vec3(0.0), vec3(any(isnan(hdr)) || any(isinf(hdr))));
hdr = max(hdr, vec3(0.0));
return FBOUT(vec4(ACESFilm(hdr), x.a));
return FBOUT(vec4(ApplyAgX(hdr), x.a));
//return FBOUT(vec4(ACESFilm(hdr), x.a));
//return FBOUT(vec4(reinhard(x.rgb), x.a));
}