Add white, contrast, and future HDR support to the AgX tonemapper.#106940
Conversation
529f105 to
867ca9f
Compare
There was a problem hiding this comment.
Tested locally, it works as expected. Code and documentation look good to me.
Testing project: tonemap_color_correction.zip
Also in a branch: https://github.com/Calinou/godot-demo-projects/tree/tonemap-improve-agx
| Before | After |
|---|---|
![]() |
![]() |
Whitepoint 16.0
Whitepoint 0.1
There seems to be a slight visual difference between 0.1 and 2.0. Should the value be clamped to avoid this? Either way, the whitepoint setting seems to make little difference in AgX compared to other tonemappers. Here, it only affects the bright glowing cube/sphere on the left.
Yes, that might be it or just an optical illusion. |
|
I tested this on a Google Pixel 1 with Android version 10 (Oct 6, 2019) and everything behaved as expected. (No crash issues or unexpected behaviour with the extra bytes in the shader push constants/binary objects.)
I did notice that this phone did not support Forward+, so even when I exported with Forward+ it would still use the Mobile rendering method. This behaviour was not related to this PR because it happened with b89c47b as well. |
e5431d7 to
0f51251
Compare
Check the rendering method in the project settings (by default, the |
Ah, thanks for sharing this! And this default is for good reason: this phone doesn't support Forward+ independent of this PR. (Forward+ always fails on this phone with |
6654de0 to
be0312b
Compare
|
tldr; non-AgX tonemappers are behaving the same with this PR as they are before this PR. I did another sanity check across all tonemappers and all three rendering methods on my NVIDIA 980 Ti desktop with a
So although this is measurable for some specific colour input, it's entirely invisible to a human. As shown in the original PR comment, visible differences are seen in the AgX tonemapper. |
|
tldr; This PR may introduce a change in behaviour for effects that depend on values above One thing that I did not discuss in this PR is that the tonemapper output with this new curve is clipped to a maximum of Without this clipping, the "allenwp tonemapping curve" has a linear behaviour above the output max value. This is because the shoulder is a Reinhard-like shoulder, and this is the normal and expected behaviour for the Reinhard tonemapper. While this is fine in some cases, it is at odds with the intentional mismatch between inset and outset chromaticity coordinate rotation of the Blender AgX configuration: This cyan strip on very large input values appears because of the mismatch between the rotation in the inset and rotation in the outset matrices (the last three arguments of the Interestingly, if I use a tonemapping curve that has non-linear curve above the maximum output value, this cyan strip does not occur: So I tried for weeks to construct a curve that would maintain the curved shoulder above the output max value and eventually decided that it was not feasible. The main issues that occurred were:
So my solution was to simply clip the shoulder values to the maximum output value before applying the mismatching outset matrix. This seemed quite reasonable, because conceptually the purpose of a tonemapper is to place values into the |
438ca5d to
e89f41b
Compare
clayjohn
left a comment
There was a problem hiding this comment.
To be honest, I'm a little uncomfortable with the huge amount of complexity this adds for little to no noticeable impact. I understand that this is theoretically better than the current state. But I struggle to believe that any user will actually care about the difference.
The comparison photos all look either the same, or extremely similar. And even with the ones that are different, its impossible to say which version is preferable. So I worry that this PR may just be chasing some theoretical ideal without having a strong user-focused reason to be merged.
I suspect the only difference users will notice is the fact that suddenly AgX looks bad because the white point is now 1.0 instead of 16.29.
|
The original proposal for AgX had a lot of people excited for the "AGX Punchy" version, and I am one of them. This higher quality contrast adjustment would be extremely valuable for cartoony artstyle games. For example |
e89f41b to
f4f023d
Compare
|
We had a long discussion about the addition of the Contrast parameterI have changed the contrast parameter for AgX to be more clearly only for this tonemapper by renaming it to Here are other notes about this decision:
White parameterWe figured it might be best to introduce an entirely new
|
f4f023d to
72e0ffa
Compare
I agree with that. From what I know a simple but effective way to add contrast control is to just take it to the power of something. For example there could be a simple |
|
The AgX contrast parameter does control the power function exponent, similar to what you described -- but importantly, it is anchored on middle grey (18% linear) instead of By applying a power function with the anchor at But in the end, a tonemapping curve like the ones in Godot is a contrast curve, so applying another contrast curve before or after the tonemapper is a little weird anyway when performance is a concern. |
72e0ffa to
1cb3d81
Compare
|
Rebased to work with new behaviour in Mobile HDR 2D. |
1cb3d81 to
c286bdb
Compare
white and contrast parameters to the AgX tonemapper.white, contrast, and HDR output support to the AgX tonemapper.
white, contrast, and HDR output support to the AgX tonemapper.white, contrast, and future HDR support to the AgX tonemapper.
c286bdb to
9eb473a
Compare
|
Rebased to include new mobile/forward+ tonemap shader split. Also improved some comments and the documentation text that describes how |
Calinou
left a comment
There was a problem hiding this comment.
Tested locally, it works as expected on all renderers (including HDR 2D enabled). Code and documentation look good to me.
9eb473a to
1f3dd61
Compare
clayjohn
left a comment
There was a problem hiding this comment.
Seems fine to me.
I agree that adding an agx_white property is awkward and makes the interface more bloated. But we can't really avoid it if we want to have a configurable white parameter for AgX. This sort of bloat is the reason I was against merging AgX in the first place. But now that we have AgX merged our choices are limited and we need this for proper HDR support in the future.
Similarly, exposing a new agx_contrast is unfortunate bloat to the interface and causes an awkward naming conflict with the existing contrast parameter. Again, there is no way around it if it really is so important to have, so the best we can do is prefix it with agx_ to make it clear that it is a different form of contrast and hide it from users who aren't using AgX.
Overall, I think this PR strikes the best balance we can achieve given that our goals ultimately require adding some amount of bloat to the interface.
|
Needs rebase, and this should be good to merge before feature freeze. |
Also optimize all tonemappers to perform less calculations per-pixel. Note: unlike `white`, `agx_white` is limited to a minimum of `2.0` and defaults to `16.29`. When using a RGB10A2 render buffer, `agx_white` will be ignored and a value of `2.0` will be used instead to ensure good behavior on the Mobile renderer.
1f3dd61 to
628df32
Compare
|
Thanks! |
















Summary
This PR adds
agx_whiteandagx_contrastparameters and HDR output support to the AgX tonemapper. It also optimizes existing tonemappers to reduce unnecessary per-pixel math operations.SDR output is currently hardcoded; minimal changes are required to support future HDR output. Try out HDR output with this PR here: #110701.
agx_whiteparameter that can be set as low as2.0.log2space when applying the curve. This PR improves performance by applying the curve directly to linear values instead.agx_contrastparameter that has no performance cost and produces higher quality results than the existingcontrastadjustment.Note: unlike
white,agx_whiteis limited to a minimum of2.0and defaults to16.29to match Blender's configuration and the existing behaviour in Godot. When using a RGB10A2 render buffer,agx_whitewill be ignored and a value of2.0will be used instead to ensure good behavior on the Mobile renderer.This PR uses a new bespoke tonemapping curve in place of the AgX curve because the AgX curve is not suitable for real time use and is also not suitable for variable dynamic range output (EDR). The new tonemapping curve very closely matches the existing Blender AgX curve for dark-to-mid values with the default
agx_contrastandagx_white. There is deviation from Blender and Godot 4.4 in the brightest input values, but this is required to ensure stable behaviour across all variable dynamic ranges.I have also re-done the math on the combined colour space and Blender AgX matrices to correct an issue where colourless values were having colour added to them by the matrices. I traced this back to the Rec. 709 to/from Rec. 2020 matrices that I had calculated based on the Blender OCIO files. The new matrices were taken from RGB Colourspace Transformation Matrix app of Colour Science, which is clearly where I should have sourced them to begin with.
New AgX contrast parameter
The new AgX contrast is applied during the tonemapping process, so it produces higher quality results than Adjustments contrast. The AgX contrast is specifically better at maintaining the hue of bright colours than Adjustments contrast which is prone to hue shifts towards yellow, cyan, and magenta from channels hard-clipping.
1.53The following compares AgX contrast with Adjustments contrast in Forward+ with HDR 2D disabled:
Review considerations
agx_white; it would be much nicer to have this be the samewhiteas all of the other tonemappers, but I don't know any other way to get the AgX white to default to16.29to match existing behaviour in Godot 4.4 and Blender.EnvironmentStorage, which doesn't seem like the best place for this, but logically is the nicest spot of the current classes. With future HDR output, the parameters will change every frame based on max output value, so they are calculated on demand instead of when tonemap parameters changed by the user.agx_whitecould go as low as1.0instead of2.0, but as pointed out by the primary Blender AgX author, it really doesn't perform well whenwhiteis set to1.0.Performance
Using the visual profiler, Calinou's tonemapping test scene, ~4K window, and an NVIDIA 980 Ti on Windows 11 with Forward+, I recorded the following performance stats:
Using the visual profiler, Calinou's tonemapping test scene, 1/2 of a ~4K window, and Intel Graphics 770 on Windows 11 with Forward+, I recorded the following performance stats:
(Note: these were very noisy, so I recorded the smallest number that kept occurring in the profiler.)
Compatibility with Godot 4.4
One of the goals of this PR is to maintain compatibility with existing Godot 4.4 and 4.5 projects. For this reason, it is important that the new curve produces similar visuals to the old AgX approximation. The following comparison demonstrates that visual appearance with the new curve is similar to the old appearance in Godot 4.4:
white = 16.0,contrast = 1.25Other notes
Here’s the Mathematica for the matrix maths and verification of their stability:
Related PRs
Further explanation
Thanks
Thanks to Troy Sobotka for the AgX generator, EaryChow for the AgX configuration, and Stephen Hill for helping me through the process of using Mathematica to do the fitting to the AgX Blender tone curve! Also thanks to Timothy Lottes, Erik Reinhard, and John Hable for sharing their work, which was foundational for the new variable dynamic range tonemapping curve.