• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

3D [SOLVED] Multipass w/ Gamma Correction

Binsk

Member
EDIT: Solved, see this post

Howdy!

I'm well along in designing a 3D engine for GameMaker and in doing so have tread on a lot of new ground, for me, anyway. One part of this has been Gamma correction / HDR. GameMaker poses some interesting problems and I am reaching out hoping that someone who has better experience with these concepts can help me out.

Where I'm at is I've designed the beginnings of a deferred shading system. I render textures for ambient, albedo, metallic, roughness, normal, and depth when generating the GBUFFER. I then calculate lighting in chunks before resolving the final values.

The problem comes when I need to apply gamma correction. This is something that needs to be done on the final combined lighting value as I understand it. My problem arises when it comes to storing the color data between passes.

As GameMaker is limited to RGBA8 buffers the precision is way too low and once I pass the data outside the shader I lose a lot of detail, especially since values will be higher than the [0..1] range. This causes extreme banding once I apply the gamma correction at the end.

I have tried to split the colors into two separate buffers and then merge them together when calculating gamma effectively giving me 16bits for each color. This provides a significant improvement but still has very obvious banding. There's also just the imprecision of the packing/unpacking in the first place and the fact that each individual 8-bit channel can now go above the threshold when combined over multiple passes.

I am thinking the fundamental issue is trying to store values larger than 1 since that will always go out of range and get 'clamped' by the datatype as even storing everything in RGBA8 looks fine so long as I calculate all the lights and perform the gamma correction in a single pass. I can't seem to find a proper solution to this multipass problem wherever I look.

Is anyone there that understands how this is generally accomplished? Any ideas, concepts I should research or anything would help me with this one. Is there some other way I can format my MRTs that would help with doing this?
 
Last edited:
A while back, someone was experimenting with extensions that allowed different surface formats. It might be worth looking into for you.

Your question about clamping values between 0 and 1. You're takling about the final colour output of a single colour channel, right?

Could you get away with seperating brightness (as 32 bit), and colour (normalized) into two different textures? Sorry, that is probably a very naive suggestion.
 
Last edited:

Binsk

Member
@flyingsaucerinvasion Thank you for the help.

Hm... I will see if I can find that extension. As I want to release this project when it's done I can't use anyone else's extensions in the project but perhaps I could learn how they did it.

In regards to the clamping, yes. If I calculate some light in my shader that is brighter than white, say <1.4, 1.2, 0.9, 1.>, I know that it will get clamped to <1, 1, 0.9, 1.>. Does this happen when the data is placed into the render target (so multiple-passes would be combining multiple clipped values) or when pushed to the display (multiple passes would be combining correct values)? I am suspecting the former.

Nothing naive about the suggestion. I'm really new to a lot of this so every idea and suggestion helps. The problem I am seeing with the idea, though, is that when multiple passes are combined and the values added we will still get the same issue as before. The RGB values will be normalized until I add another pass where I assume they will be clamped again. I did a quick test just to see. If I have all my lights calculated in one pass and split the channels as you have suggested to my final resolution pass it certainly removes the banding. If I split each light into its own pass then I get the banding again so maybe it is just a matter of values getting clamped?
 
"Does this happen when the data is placed into the render target" Well the surfaces we have in gamemaker are just 1 byte per colour channel. I have to presume that means as soon as something is drawn to a render target, that is the precision it has.
 

Binsk

Member
Man I feel stupid. So I know that the colors get converted to [0..255] ranged 8-bit uints when pushed to the surface but for some reason my brain was stuck thinking range [0..1]. So my question early makes no sense.

So I'm wondering if it is less a data-precision thing (albeit that is still a problem) and more a matter of going above the [0..1] range in the shader. There must be a way of packing data linearly to fix this. I will keep looking but help is still appreciated.

Two potential, and probably really naive, ideas that I thought of:
1)
Have a set of two buffers (so 4 altogether, each set simulating RGBA16) where 2 get swapped every other light batch where the final values have been scaled somehow between [0..1] with a multiplier (maybe via brightness as suggested above?) . At each pass the last pass is read back in, unscaled, added to the final result, then scaled back into the new final value.

This allows me to make sure things don't get 'clamped' when combined together and I the final result just has a single scalar value to modify the colors by before gamma correction. However, this uses a lot of extra texture memory and bandwidth that I'm not a fan of and I'm not super familiar with scaling functions that will keep things close to linear.

2)
Simply render every single light in one go. This would likely be done by passing directional / ambient lights through uniforms (and limiting their number) while passing omni and spot properties via a texture.

This just lets me get everything done before passing into a surface. It does make things a significantly larger hassle for effects / light optimizations down the road, however and I'm not super sure about the efficiency of so much sampling and whatnot.

EDIT:
I tried out option 1 and it works like a charm. Currently I'm using the non-linear scaling value of 1. / (x + 1) between passes which is not ideal but in terms of removing banding and the like this is getting the job done.
 
Last edited:
Top