Skip to content

Conversation

allenwp
Copy link
Contributor

@allenwp allenwp commented Feb 4, 2025

Superseded by #106940

Not cherry-pickable to 4.3, as AgX is only in 4.4.

Tonemapping Curve

This PR changes the tonemapping curve of AgX to use an approximate in linear encoding. Timothy Lottes' tonemapping curve equation is used to approximate the AgX curve. This new approximation introduces a higher contrast than the original Blender AgX by EaryChow, but greatly improves performance and flexibility.

Visual curve comparison (top: this PR, bottom: Godot 4.4 beta 4)
AgX-Approximate-this-PR
AgX-Approximate-4 4beta4

Performance

Using the visual profiler, Calinou's tonemapping test scene, ~4K window, and an NVIDIA 980 Ti on Windows 11, I recorded the following performance stats:

AgX (exact curve with white parameter): 1.35 ms
Tony McMapface: 1.29 ms
AgX (exact curve): 1.22 ms
AgX (Godot 4.4 beta 4): 1.19 ms
ACES: 1.07 ms
AgX (this PR): 1.05 ms
Filmic: 1.0 ms
Rienhard: 0.93 ms
Linear: 0.82 ms

Comparison

Exact Curve (Reference) Approximate (this PR) Approximate (Godot 4.4 beta 4)
tps ref tps newapprox tps -beta4
red dot refernce red dot new red dot beat 4
square reference square new square beta4
calinou reference calinou new calinou reference
Godot_AgX-Exact-Reference_B002C016_220405_B09C.00796 Godot_AgX-Approx-Timothy-Curve_B002C016_220405_B09C.00796 Godot_AgX-Approx-4.4-Beta4_B002C016_220405_B09C.00796
Godot_AgX-Exact-Reference_Matas_Alexa_Mini_sample_BT709 Godot_AgX-Approx-Timothy-Curve_Matas_Alexa_Mini_sample_BT709 Godot_AgX-Approx-4.4-Beta4_Matas_Alexa_Mini_sample_BT709
Godot_AgX-Exact-Reference_red_xmas_rec709 Godot_AgX-Approx-Timothy-Curve_red_xmas_rec709 Godot_AgX-Approx-4.4-Beta4_red_xmas_rec709
Godot_AgX-Exact-Reference_HDR-dark-corner-photo Godot_AgX-Approx-Timothy-Curve_HDR-dark-corner-photo Godot_AgX-Approx-4.4-Beta4_HDR-dark-corner-photo
Godot_AgX-Exact-Reference_Max1Saturation100 Godot_AgX-Approx-Timothy-Curve_Max1Saturation100 Godot_AgX-Approx-4.4-Beta4_Max1Saturation100
Godot_AgX-Exact-Reference_Max1Saturation50 Godot_AgX-Approx-Timothy-Curve_Max1Saturation50 Godot_AgX-Approx-4.4-Beta4_Max1Saturation50
Godot_AgX-Exact-Reference_Max1Saturation0 Godot_AgX-Approx-Timothy-Curve_Max1Saturation0 Godot_AgX-Approx-4.4-Beta4_Max1Saturation0
Godot_AgX-Exact-Reference_Max18Saturation100 Godot_AgX-Approx-Timothy-Curve_Max18Saturation100 Godot_AgX-Approx-4.4-Beta4_Max18Saturation100
Godot_AgX-Exact-Reference_Max18Saturation50 Godot_AgX-Approx-Timothy-Curve_Max18Saturation50 Godot_AgX-Approx-4.4-Beta4_Max18Saturation50
Godot_AgX-Exact-Reference_Max18Saturation0 Godot_AgX-Approx-Timothy-Curve_Max18Saturation0 Godot_AgX-Approx-4.4-Beta4_Max18Saturation0
Godot_AgX-Exact-Reference_HDR-dark-bands Godot_AgX-Approx-Timothy-Curve_HDR-dark-bands Godot_AgX-Approx-4.4-Beta4_HDR-dark-bands

White parameter and HDR output

Because this AgX tonemapping curve approximation uses Timothy Lottes' equation, it will be possible to control the maximum value (white parameter) and also adjust brightness and exposure in the future. A separate PR will add support for the white parameter and further features can be added relating to controlling the middle grey input and output mappings for HDR output. More information about this tonemapping equation can be found in the GDC slides or video.

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good to me.

@akien-mga akien-mga requested a review from clayjohn February 5, 2025 17:11
@allenwp
Copy link
Contributor Author

allenwp commented Feb 8, 2025

Previously I had not updated the tonemap_exposure docs. I just pushed a change to improve the docs for this member.

@akien-mga akien-mga modified the milestones: 4.4, 4.5 Feb 13, 2025
@allenwp allenwp changed the title Improve AgX tonemapping curve and update tonemapping docs. Improve AgX tonemapping curve. Feb 18, 2025
@allenwp
Copy link
Contributor Author

allenwp commented Feb 18, 2025

I've updated this PR to remove the docs changes, as those were merged separately in #102820.

@allenwp allenwp marked this pull request as ready for review February 21, 2025 20:31
@allenwp allenwp requested a review from a team as a code owner February 21, 2025 20:31
@allenwp
Copy link
Contributor Author

allenwp commented Feb 21, 2025

After around 2 months of R&D, I've got AgX into a good state where it is both high performance and high quality, matching the reference curve much closer than before. This is now ready to merge with addition of white parameter in a future PR.

@allenwp allenwp requested a review from a team as a code owner February 21, 2025 20:40
Copy link
Contributor

@Ansraer Ansraer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me, thanks for taking the time to dive deeper into AGX.

@clayjohn clayjohn added the cherrypick:4.4 Considered for cherry-picking into a future 4.4.x release label Feb 22, 2025
@allenwp
Copy link
Contributor Author

allenwp commented Feb 24, 2025

Stephen Hill, author of the ACES tonemapping curve approximation that Godot uses, took the time to review this as well and suggested a clever optimization:

color = log2(color);
color = exp2(color * a) / (exp2(color * d') * b + c);

Where d' = a * d.

Stephen explained that pow(x, e) is typically implemented natively as exp2(log2(x) * e), which matches what I've seen elsewhere. This means that a log2() operation can be saved.

The full breakdown starts by replacing pow(x, e) with exp2(log2(x) * e):

x = exp2(log2(x) * a) / (exp2(log2(exp2(log2(x) * a)) * d) * b + c);

...is equivalent to:

x = log2(x);
x = exp2(x * a) / (exp2(log2(exp2(x * a)) * d) * b + c);

log2(exp2( cancel out, so this is equivalent to:

x = log2(x);
x = exp2(x * a) / (exp2(x * a * d) * b + c);

In my test, I found this improved performance of the tonemapper from 0.26 ms to 0.23 ms. I've updated the PR to include this optimization.

@allenwp allenwp marked this pull request as draft March 8, 2025 14:17
@allenwp
Copy link
Contributor Author

allenwp commented Mar 8, 2025

I’m temporarily putting this PR back in a draft state, as I’ve discovered a piecewise polynomial equation that may be better for approximating AgX in terms of performance, future white parameter, and a future HDR adaptation. It should only take me a week or two (excluding GDC) to try out it out and see if it’s a better fit.

@allenwp
Copy link
Contributor Author

allenwp commented Apr 3, 2025

Update on my progress:

After playing around with it more, I've found that there are actually four goals for the new AgX tone curve:

  1. Match the Blender AgX curve without oscillations found in the Godot 4.4 curve
  2. Improve performance compared the Godot 4.4 curve
  3. Allow addition of a configurable white parameter
  4. Work well with highly variable dynamic range for future support with HDR output

The first two goals are well achieved by the current state of this PR.

The third goal of the white parameter is somewhat achieved by this PR, but the contrast changes a fair bit as the white parameter changes. If this was the only issue, I'd say it's good enough, but...

The fourth goal is simply not achieved with the current state of this PR. The contrast (toe strength) is greatly influenced by manipulation of the dynamic range. I thought it would work well enough, but the contrast (toe strength) increases as dynamic range increases. In practice, artists would typically want the opposite behaviour, because an increase in dynamic range is naturally an increase in contrast already.

The reason this is such an issue is that the sort of HDR that Godot is aiming to support is actually described by Apple as "Extended Dynamic Range". That is, values can simply extend beyond 100% (1.0). If the user turns their brightness to maximum, it's possible that there will be no extra dynamic range left to work with, so the maximum value will be 1.0. Or, the user may have their brightness low, so the maximum value would be something like 5.0 or 10.0. This behaviour is effectively the same on Windows for Godot's purposes, but Apple does a better job of explaining it.

When users increase the brightness, they typically expect midtones to increase and highlights to be compressed to fit into the range of the display.

All that to say, there is more maths to be done to achieve all of the goals for the new AgX tone curve. All of this should be very possible, but it's not something that's well documented (most HDR tonemapping is fixed range instead of variable/extended range).

This PR could be merged, but since the curve will need to be adjusted again in the future to support variable dynamic range, it might be better to only merge the curve after all goals have been met.

@allenwp
Copy link
Contributor Author

allenwp commented May 30, 2025

I've created a new PR that doesn't have as good of performance as this PR, but is extremely stable and predicable across all variable dynamic ranges, making it suitable for SDR, HDR, and variable Extended Dynamic Range (EDR): #106940

I'll leave this in draft for now, but will close it if and when the new PR is merged.

@Repiteo Repiteo modified the milestones: 4.5, 4.6 Jun 23, 2025
@AThousandShips AThousandShips added the cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release label Jul 29, 2025
@clayjohn clayjohn removed the cherrypick:4.4 Considered for cherry-picking into a future 4.4.x release label Sep 16, 2025
@allenwp allenwp closed this Sep 19, 2025
@AThousandShips AThousandShips added archived and removed cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release labels Sep 19, 2025
@AThousandShips AThousandShips removed this from the 4.6 milestone Sep 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

AgX: Oscillations in sigmoid approximation
7 participants