Game Development Community

Linear tonemapping

by Alex Scarborough · 10/23/2005 (4:54 pm) · 15 comments

Download Code File

The options dialog has had a couple of sliders added to it so you can easily mess around with the values and get instant feedback. To get fullscreen, open the console and type LTMFS(true); To disable fullscreen, open the console and type LTMFS(false); Sorry that's not part of the GUI, I'm not familiar enough with the TGE gui system to make that work.

Console functions added

LTMmin(minValue, correspondingMin);
LTMmax(maxValue, correspondinMax);
LTMFS(bool);

What's up with correspondingMin and correspondingMax? In most cases you'll want those to be 0.0 and 1.0 respectively. If you're adventurous though, those are the values that minValue and maxValue are decreased/increased to. I left that in there simply because it gives you a bit more control over the system.

Oh, and if you've changed SceneGraph.cc, SceneGraph.h, or SceneState.cc, then the LTM changes are noted. They're all in big blocks anyways, so it works out pretty well. Just search for Begin LTM and End LTM in those files to see what has been added.

Here is a writeup on the process. Enjoy.

How It Works
What actually happens is that the contrast of the entire scene is increased; the dark areas get darker, the lighter areas get brighter, and everything inbetween shifts to fill in the gap. This makes the scene look more vibrant and pretty. While it does produce some washing out of colors, this is unavoidable on our target range of computers. Doing spiffy effects without adjusting system requirements isn't easy.

In Depth Explanation
We use a series of quads blended in over the scene to run all the colors through a calculated linear regression. A typical regression would be :

NewColor = 2.5(OldColor) - 0.6.

This regression is applied equally on the entire RGB spectrum. However, we are dealing with a clamped range. If, after a render operation is performed, the value falls out of the 0-1 range, it is clamped to 0 or 1. This requires a very interesting little hack. We take our bias (-0.6) and divide it by our scalar (2.5) to produce a far smaller bias (-0.24). If you take a look at the graph of the equation :

y = 2.5x - 0.6

Here you will notice that all x values below 0.24 create a y value below 0. By applying our scaled bias first, we ensure that the operation runs smoothly, even though we are clamped to the 0-1 range after every calculation. So, we turn NewColor = 2.5(OldColor) - 0.6 into :

NewColor = 2.5(OldColor - 0.24)

Okay, this is great, so we can keep our spiffy calculations within the handy 0-1 range so we don't kill colors. But how exactly are we carrying this out? The first step is a subtractive blend. It is worth noting that many older cards do not support subtractive blending, and there appears to be an issue under Windows that prevents programs from setting the subtractive blending mode. The solution to this is very... interesting. We invert the colors of the entire scene (black is now white, etc.) and then add to that. Then we invert it back. For example, say our color is 0.3 (only one value for simplicity). When we invert, our value is (1-0.3) 0.7. Now we add 0.24 to that to get 0.94. When we invert again, we have 0.06 which is 0.3 - 0.24. Spiffy, no?

From there our scalar is easy. You can find some articles on how to do that with a simple google search, and I highly advise that you do, and read the articles. Older articles on graphics programming are true works of art.

Some notes to be made: You can pass in any value for either LTMmin or LTMmax. I've tested this with LTMmin being 0.9 and LTMmax being 0.9000001, which generates a linear regression of 100,000,000(color) - 90,000,000 and it's worked just fine. Furthermore, LTMmin does not have to be above 0, and LTMmax does not have to be below 1. Feel free to experiment to produce some interesting whiteout and desaturation effects.

Why didn't you just use a different blend equation?
Simple: I don't own a graphics card that supports a different blend equation.

Why not just use GL_COPY_INVERTED and save ourselves a quad?
There is a conflict with cards that support EXT_blend_ and GL_COLOR_LOGIC_OP. Basically, if we have enabled GL_COLOR_LOGIC_OP, and are blending, then it's the equivalent of having called glBlendEquation(GL_LOGIC_OP);. This doesn't work. Therefore, extra quad.

Can I use this with the TLK?
Absolutely! In fact, keep the TLK overbrighting on. It looks amazing running on top of the TLK. Thank you John for your amazing product.

Where did you get the idea for this?
I was talking to Jeff "Reno" Raab while trying to learn some stuff about rendering, and he wondered if we could do HDR in fixed function, or at least fake it. Long story short: not really. But we did get this though.

This is from an article?
Yep. That article actually contains not only this current code (though I modified it a bit, to be fair), but even contains my earlier attempts to do this. Those attempts ran at less than 1FPS. Also, look at the publication date. 1997.

Do you want feedback?
YES! Of course! My favorite kind of feedback is the type that starts with "I liked it, but...". Second favorite would be some variant of "It sucked, because..."

A couple of the screenshots have a totally different looking sky. How did you do that?
I used GL_COLOR_LOGIC_OP to invert the skybox. Give it a try. If anyone wants it, I can post the code for that too.

This kills fog horribly. What's up with that?
Fog is actually pretty bright to begin with, and almost always gets brighter with the LTM stuff. The best way around it might be to adjust the fog color by running it through a reversed set of LTM calcs. This is another area where I was lazy. Just dropping the values in the mission editor works too.

MY RETINA'S BURN! SO MUCH BRIGHT!
You'll want to increase the LTMmax value.

So dark, can't see anything, bad DooM 3 memories...
Decrease your LTMmin value.

Give me screenies!

Screen 1
Screen 2
Screen 3

This one is my personal favorite.
Shiny!

If you want more information, or want to give feedback, or wish to verbally rip my head off, then you can email me at golantrevize AT comcast DOT net.

#1
10/23/2005 (11:14 pm)
Just an FYI, on 1.4 I get a bit of errors. I hope this isn't too long, but i figuared i'd put it there to help ya. If someone complains its too big i'll edit! Promise. Edit: WAS WAYYYYY TOO BIG, 136 Errors..
#2
10/24/2005 (6:33 am)
Pretty interesting Alex, I'll have to give it a try!
#3
10/24/2005 (7:26 am)
@Chris: I would advise only copying over the added blocks of code if you're getting errors like that. Also, I've found that precompiled headers can cause issues when replacing header files. It looks like VC couldn't find scenegraph.h.
#4
10/24/2005 (11:27 pm)
I got this working in the SG lighting pack 1.3.5, but it shows the same thing your screenshots show, left half before, right half after.
#5
10/24/2005 (11:28 pm)
@Midhir: If you open the console and type LTMFS(true); it should give you a full screen effect.
#6
10/25/2005 (1:42 am)
The source files in this zip are in some strange encoding so whenever I try to do a copy and paste, things get all jumbled. Might it be possible to re-upload this with a proper text encoding?

Thanks in advance!

Stephane
#7
10/25/2005 (6:39 am)
I'm seeing a huge reduction in fps. Anyone else experinceing this?
#8
10/25/2005 (7:11 am)
@Stephane: Strange text encoding? I downloaded a clean copy of TGE, added this in, tested it, then uploaded the files I had changed. They should have the same text encoding as every other file in TGE. However, I'll make the code that needs to be added part of the resource later today.

@Jody: Huge reduction in FPS? What kind of graphics card do you have? All this does is render a few quads after rendering all non-gui stuff.
#9
10/25/2005 (10:36 am)
@Alex: Sorry I can't be more specific, but the files looked fine in wordpad, VS.NET and notpad. Trying to open the files in Microsoft Word 2003 was another story. All the Torque Game Engine files open in MS Word 2003 without a problem, but all of your files bring up the "Select encoding that makes your document readable" window. Was this developed on a Mac? Might that be the problem?

Anyway, I had to just type everything manually and it works like a charm... copy and pasting just didn't cut it (pardon the pun...)

Thanks for the prompt reply! Good work by the way too!

Stephane
#10
10/25/2005 (11:19 am)
@Alex: From around 100 fps to around 20 fps. I'm using an ATI Mobility Fire GL T2 on a Pentium M 2Ghz with 2GB of ram.

EDIT:

Alright, evidently it was something I was doing. I suspect it was the full screen motion blur. Anyway, well done!
#11
10/25/2005 (3:08 pm)
@Stephane: Yes, this was done on a mac. I really really like XCode, and find it almost impossible to work with Eclipse. I'll keep the text encoding issues in mind for future resources.

@Jody: Glad to hear you got your problems worked out! Ya, motion blur can be painful. That whole copying from framebuffer to texture memory thingy. I've been trying to work true RtT into Torque, but my OpenGL knowledge just isn't adequate for the job, and I've got a lot going on.
#12
07/09/2007 (9:45 pm)
Great resource! I wonder if there's any way to do a nonlinear regression here, and get a kind of nonlinear tone mapping. That would mimic HDR lighting much better. I know very little open gl code. I just understand lighting conceptually. So forgive me if I'm way of base for even suggesting that.

Here's a nice shot to show off the effects.
#13
07/10/2007 (3:14 am)
Hey I want to improve this resource, but I'm not entirely sure how to use the functions captureAverageItensity() and captureScreen(). I want some way to get the screens average intensity as a float value. Then I can easily automate the LTM min and max to choose the ideal values for whatever kind of picture is on screen.
#14
01/11/2008 (6:18 am)
WOW, cant believe it!

One of the *MUST SEE* stuff among many useful resources on the site.
#15
07/10/2009 (12:30 am)
I'm trying to use this resource in TGE 1.5.2 with no luck.. Don't seem to be getting any errors during compile, but none the less not getting any results. Has anyone successfully implimented this resource in TGE 1.5.2?