Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comments for post 2010-08-19-why-reinhard-desaturates-my-blacks #2

Closed
tommadams opened this issue Apr 4, 2020 · 9 comments
Closed

Comments

@tommadams
Copy link
Owner

Comments for blog post 2010-08-19-why-reinhard-desaturates-my-blacks

@tommadams
Copy link
Owner Author

comment imported from WordPress
Ben Diamand said on 2010-08-26 21:47:59:

Very good article! In fact, God of War III shipped with almost the exact same tone mapping operator as what you have presented here. The only real interesting extension came about because we found that Reinhard did not allow us to drop the low end of the curve enough. So in a similar way to what you suggest, we had what amounted to a madd to drop the low end contrast (well, really, two madds in order to smoothly ramp on a low end contrast shift without affecting values closer to the white point).

I do have a couple of nits to pick though!

You said, "if you want to crisp the blacks ...you can do that in your final colour grade. If you’re colour grading via a volume texture lookup, this is for free!"

My major nit is that while you can certainly apply color correction via a texture lookup at the end (and indeed, GOW did just this to get many cool effects), I found it very difficult to get the kind of complex function required to do decent quality color grading (the kind that emulates the 'filmic' look) without a very large 3D map. And to get, for example, the same effect as John got by doing tone mapping per channel requires quite a bit of precision, at least for us, due to how the various operators modify intensities in such non-linear ways.

It was even worse on GOW, since we actually weren't able to maintain a full precision buffer throughout the pipeline, meaning that all we had was a 32-bit RGBA buffer to color correct. This wass fine for simple stuff like desaturation or brightness/contrast changes, but was not nearly enough precision for high precision color grading.

And then there's the problem of map creation. If all you are doing is mapping a 32-bit value to another 32-bit value, using artist driven tools like Photoshop is great, and the artists get to come up with all kinds of nice mapping textures! But as soon as you want to allow either larger range or larger precision, we've found a woeful lack of support out there (Photoshop's plain old 16-bit support is better than it used to be, but still crappy). So even if we had a post tone mapped FP16 buffer to work with, at the end of the day, getting film-style color correction in there (for which we didn't find any natural mapping with any of the built in Photoshop knobs) would have been both painful, and I think would have required lots of texels.

Interestingly, GOW did not do per channel Reinhard (I simply didn't think to do it for one thing - John had much better theoretical handle on the problem than I did!), but post shipping, as we've ramped up to using a larger dynamic range (we added HDR to GOW alarmingly late in the dev cycle), the particular look of over saturated darks has grown on many of our artists, and we've experimented with per channel Reinhard (as well as a full implementation of John operator).

What we've found is that while the per channel idea has a couple of downsides (it's more expensive for one, and as you rightly pointed out it is completely fixed in nature and so not easily amenable to artist modification), the look it gives is unique, and often quite nice. And it's also a look that we've found very difficult to replicate with post effects (though it is theoretically possible). From a theoretical standpoint, I've become convinced that John was on to the right idea - film really does respond to different colors independently, and so in effect, the tone mapping 'exposure' really should be done per color, at least if you want to emulate film. So incorporating that idea within tone mapping instead of after does have a good basis in (film) reality.

As for John's specific operator, it obviously worked very well for Naughty Dog, but we have (at least for now) chosen to stick with Reinhard. As it turns out, Reinhard is a sub-function of John 'filmic' operator. In other words, a little algebra shows that you can implement all of Reinhard with proper constants in John's operator. But we've found the added functionality to be a lot harder to control, and with not too many benefits over Reinhard for the added complexity, so while there are some curve shapes that we cannot get, the trade-off in ease of use will likely keep us in the Reinhard camp (albeit with 'filmic' style per channel mapping).

Wow - that was a larger reply than I was intending - I hope not too large! :)

Ben Diamand

@tommadams
Copy link
Owner Author

comment imported from WordPress
Naty Hoffman said on 2010-08-30 12:48:46:

Reinhard was on the wrong track in applying his tone mapping curve to luminance; his curve was inspired by film, but film curves are applied to each color channel separately. And that is a good thing - here is a salient quote from Georgianni & Madden's "Digital Color Management, 2nd ed." (a book I highly recommend to anyone who wants to understand color reproduction): "...adjusting RGB, rather than luminance... results in higher overall reproduced color saturation, which is desirable because it helps compensate for color-saturation decreases associated with viewing flare and with the relatively low absolute luminance levels of the... (reproduced) images.".

The "hue and saturation shifts" (really mostly saturation shifts) resulting from applying nonlinear curves per channel are an important feature, not a bug. When the nonlinear curve has the proper filmic S-shape these shifts are highly desirable. For more information on why an S-curve is optimal (beyond the fact that it is the result of 200 years of careful engineering), please see Josh Pine's slides in my recent color course (http://renderwonk.com/publications/s2010-color-course/) - Josh's course notes will go into more detail than the slides, but they are not ready yet.

I agree with Ben that while in principle all effects can be achieved with a LUT post pass, in practice there are precision and size issues which mean that tone mapping curves like this are best applied using either math or dense 1D textures, onto the original HDR data (inside the pixel shader, or on an HDR buffer). The color LUT should be reserved for color enhancement effects rather than basic color rendering.

The main problem with the Reinhard curve is that it lacks a "toe". From Ben Diamand's description ("two madds in order to smoothly ramp on a low end contrast shift without affecting values closer to the white point") it sounds like they have added a toe to Reinhard, giving it similar visual properties to the Hable curve. This "toe" when applied per channel, gives an increase in shadow saturation that has very good perceptual effects.

@tommadams
Copy link
Owner Author

comment imported from WordPress
Michael said on 2010-09-25 10:17:24:

Very useful post, thanks a bunch!

@tommadams
Copy link
Owner Author

comment imported from WordPress
ZACK said on 2011-12-26 09:23:30:

Great post!!!
It's what I am looking for!

@tommadams
Copy link
Owner Author

comment imported from WordPress
Ruud van Gaal said on 2012-10-19 07:45:03:

Modified Reinhard is a lot softer than John Hable's UC2 curve; that one sometimes gives a bit too filmic contrast IMHO. I'll give this a go for some time, thanks for the article!

@tommadams
Copy link
Owner Author

comment imported from WordPress
Constantine said on 2017-02-20 07:36:02:

Hi!

I was experimenting with tonemapping for my Unity game and came up with formula below. It`s damn simple but I found the result very satisfying. Could you please give it a look?

vec3 logToneMap(vec3 c, float limit){
    float luma = dot(c, vec3(0.2126, 0.7152, 0.0722));
    float luma2 = log(luma + 1.0) / log(limit + 1.0);
    c *= luma2 / luma;
    return c;
}

In my scene it lacked contrast in middle range so i added this dirty hack. though i suppose it should not be needed.

float hackyContrast(float x, float coef){
    float s_curve = x * x * (3 - 2 * x);
    return mix(x, s_curve, coef);
}

I also included my tonemapping formula into your tonemapping demo on shadertoy.com. its number three from the botton

https://www.shadertoy.com/view/4sXcWn

@tommadams
Copy link
Owner Author

comment imported from WordPress
Constantine said on 2017-02-20 09:30:44:

I made a mistake above - should have applied logToneMap separately to every channel instead of luma.
The code in shadertoy is the one i actually use.

@tommadams
Copy link
Owner Author

comment imported from WordPress
netocg said on 2018-12-11 15:16:42:

Hi,

Interesting discussion.
I've been recently reading more and more about tone mapping.
I work with VFX, and I was playing recently with some Nuke script to tone map some hdr images that I've done with my camera. I've also tested photometric and other software.

Going back to the formula I remember there's values you should use for the white point, as well toe, shoulder etc...

My idea is get LDR images that have a high perceptual fidelity of the light I was seeing it when I captured the images. And I wonder if you have any tips in regards to what values should I use for each "input"?

I am currently capturing hdr doing 7 backetings exposures with 1ev internal between each. My camera is capable of register 11.6stops of dynamic range in it's raw file.

Best,
Antonio.

@tommadams
Copy link
Owner Author

comment imported from WordPress
Tom Madams said on 2018-12-16 12:52:19:

Not really, I'm afraid. I'm just a humble programmer and the only experience I have with tone mapping is from video games, where configuring those options was left up to much more talented artists. Plus, I haven't worked on this stuff for more than seven years ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant