Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions source/Renderer/shaders/pbr.frag
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ void main()
#if ALPHAMODE == ALPHAMODE_OPAQUE
baseColor.a = 1.0;
#endif
#ifdef MATERIAL_UNLIT
#if ALPHAMODE == ALPHAMODE_MASK
if (baseColor.a < u_AlphaCutoff)
{
discard;
}
baseColor.a = 1.0;
#endif
#ifdef LINEAR_OUTPUT
// If used for transmission, we need to invert exposure and tone mapping, so the original color is computed in the general render pass
g_finalColor = vec4(toneMapInverse(baseColor.rgb), baseColor.a);
#else
g_finalColor = vec4(linearTosRGB(baseColor.rgb), baseColor.a);
#endif
// PBR Flow. This else goes all the way to the end of the file
#else
vec3 color = vec3(0);

vec3 v = normalize(u_Camera - v_Position);
Expand Down Expand Up @@ -363,10 +379,7 @@ void main()
f_emissive *= texture(u_EmissiveSampler, getEmissiveUV()).rgb;
#endif


#ifdef MATERIAL_UNLIT
color = baseColor.rgb;
#elif defined(NOT_TRIANGLE) && !defined(HAS_NORMAL_VEC3)
#if defined(NOT_TRIANGLE) && !defined(HAS_NORMAL_VEC3)
//Points or Lines with no NORMAL attribute SHOULD be rendered without lighting and instead use the sum of the base color value and the emissive value.
color = f_emissive + baseColor.rgb;
#else
Expand Down Expand Up @@ -548,5 +561,6 @@ vec3 specularTexture = vec3(1.0);
#endif
#endif

#endif
#endif
}
132 changes: 132 additions & 0 deletions source/Renderer/shaders/tonemapping.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,135 @@ vec3 toneMap(vec3 color)

return linearTosRGB(color);
}

// =============================================================================
// INVERSE TONEMAPPING FUNCTIONS
// =============================================================================

// Inverse ACES tone map (Narkowicz approximation)
// Solves: y = (x*(A*x + B)) / (x*(C*x + D) + E) for x given y
vec3 toneMapACES_NarkowiczInverse(vec3 toneMapped)
{
const float A = 2.51;
const float B = 0.03;
const float C = 2.43;
const float D = 0.59;
const float E = 0.14;

vec3 y = toneMapped;

// Rearrange to: y*(C*x^2 + D*x + E) = x*(A*x + B)
// Which gives: y*C*x^2 + y*D*x + y*E = A*x^2 + B*x
// Rearrange to: (y*C - A)*x^2 + (y*D - B)*x + y*E = 0

vec3 a = y * C - A;
vec3 b = y * D - B;
vec3 c = y * E;

// Solve quadratic equation: ax^2 + bx + c = 0
vec3 discriminant = b * b - 4.0 * a * c;

// Take positive root
vec3 result = (-b - sqrt(discriminant)) / (2.0 * a);

return result;
}

// Inverse RRT and ODT fit
// Solves: y = (x*(x + 0.0245786) - 0.000090537) / (x*(0.983729*x + 0.4329510) + 0.238081) for x
vec3 RRTAndODTFitInverse(vec3 toneMapped)
{
vec3 y = toneMapped;

// Rearrange: y * (x*(0.983729*x + 0.4329510) + 0.238081) = x*(x + 0.0245786) - 0.000090537
// y * (0.983729*x^2 + 0.4329510*x + 0.238081) = x^2 + 0.0245786*x - 0.000090537
// y*0.983729*x^2 + y*0.4329510*x + y*0.238081 = x^2 + 0.0245786*x - 0.000090537
// (y*0.983729 - 1.0)*x^2 + (y*0.4329510 - 0.0245786)*x + (y*0.238081 + 0.000090537) = 0

vec3 a = y * 0.983729 - 1.0;
vec3 b = y * 0.4329510 - 0.0245786;
vec3 c = y * 0.238081 + 0.000090537;

vec3 discriminant = b * b - 4.0 * a * c;

vec3 result = (-b - sqrt(discriminant)) / (2.0 * a);

return result;
}

// Inverse ACES Hill tone mapping
vec3 toneMapACES_HillInverse(vec3 toneMapped)
{
vec3 color = toneMapped;

// Undo ACES output matrix
color = inverse(ACESOutputMat) * color;

// Undo RRT and ODT
color = RRTAndODTFitInverse(color);

// Undo ACES input matrix
color = inverse(ACESInputMat) * color;

return color;
}

// Inverse Khronos PBR neutral tone mapping
// Note: This is a complex inverse that may not be perfectly accurate due to the conditional logic
vec3 toneMap_KhronosPbrNeutralInverse(vec3 toneMapped)
{
const float startCompression = 0.8 - 0.04;
const float desaturation = 0.15;

// This is an approximation - the forward function has complex conditional logic
// that makes perfect inversion difficult
vec3 color = toneMapped;

// Try to undo the desaturation mix
float peak = max(color.r, max(color.g, color.b));

// Approximate inverse of the compression
if (peak >= startCompression) {
const float d = 1.0 - startCompression;
// Approximate inverse of: newPeak = 1. - d * d / (peak + d - startCompression)
// This is a rough approximation
float originalPeak = peak / (1.0 - peak + startCompression);
color *= originalPeak / peak;
}

// Try to undo the offset
float x = min(color.r, min(color.g, color.b));
float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;
color += offset;

return color;
}

// Complete inverse tone mapping function
vec3 toneMapInverse(vec3 toneMapped)
{
vec3 color = toneMapped;

// Then undo the specific tonemapping (this would need to match the forward path)
#ifdef TONEMAP_KHR_PBR_NEUTRAL
color = toneMap_KhronosPbrNeutralInverse(color);
#endif

#ifdef TONEMAP_ACES_HILL_EXPOSURE_BOOST
color = toneMapACES_HillInverse(color);
color *= 0.6; // Undo the exposure boost
#endif

#ifdef TONEMAP_ACES_HILL
color = toneMapACES_HillInverse(color);
#endif

#ifdef TONEMAP_ACES_NARKOWICZ
color = toneMapACES_NarkowiczInverse(color);
#endif

// Finally undo the exposure
color /= u_Exposure;

return color;
}