PNG image colours different when in-game
phkb opened this issue · comments
I suspect this is something to do with the recent updates for rendering to framebuffer and HDR support, but I'm noticing the colours of the background images I have in Xenon UI now have a distinctly different feel to them:
Left side is current trunk, right side is raw image.
Do I need to do something to the image files to get them back to the original shades?
This is almost certainly related to the render to framebuffer changes and not to HDR. Specifically, I believe this is the result of gamma correction and (to a lesser degree) tone mapping happening on the entire scene (it didn't use to occur for gui backgrounds before, just for ship and planets). What I would suggest for getting it back to its previous appearance is to apply a gamma inverse correction of 2.2 on all source background colors so that the source images become darker and when they are gamma corrected by the game they return to their original intended shades.
Before embarking on a task like this, it would probably be a good idea to double-check how it looks if you do not apply gamma correction to the resulting scene. This can be done easily by commenting out the line result = pow(result, vec3(1.0 / 2.2));
in oolite-final.fragment.
I checked it myself to be sure and indeed, the colors are much closer to your original ones if gamma correction is not applied. Still, they get a very slight teal-ish shade, due to scene tone mapping and this gets more obvious once gamma correction is applied.
I am wondering if the inverse gamma correction should be applied in-code for background screens image loading. Need to think about this a bit.
Venturing an opinion here, but I think it would be preferable to turn off gamma correction by default for background screens and overlay images. Trying to describe the process to future OXP devs of what you would need to do to get the colours you actually want shown is going to be tricky at best. OXP devs would probably then need to keep two copies of every image, an original reference version, and one with a number of effects applied (to negate the gamma correction process) that would the release version.
Perhaps changing guiTextureSpecifier to include an option to apply the gamma correction, with the default being not applied.
For example:
setScreenBackground({name:"myimage.png", apply_gamma:yes});
setScreenOverlay({name:"myoverlay.png", apply_gamma:yes});
mission.runScreen({screenID:"myid", title:"My OXP screen", background:{name:"myimage.png", apply_gamma:yes}});
That sort of thing. Not sure how achievable that is. What do you think?
Unfortunately gamma correction cannot be selectively turned off. It is applied to the entire framebuffer texture which contains the entire scene. As such, it includes any background images that may have been put up for render.
We fully agree that artists should not be expected to have anything technical explained in order for them to do their work. They should just be producing their art and expect it to appear on-screen as intended. On the other hand, gamma correction is not optional in rendering. If it is not done then the renders are straight up wrong, plain and simple. So we need a way to apply gamma correction when required, but in a fully transparent way and this should also include background, foreground and overlay images, which do not participate in shader processing (apart of course from the very last part where the final gamma correction is appled for the scene image to be sent off to the display).
Your proposed solution is pretty similar to what I had thought of, but I think it should be applied at a lower texture loading level. It should also not be called apply_gamma (because gamma correction is a must regardless),. A possible solution would be to include an option whenever a general texture is loaded in memory, with an option name like internal_linear_conversion or something, which would be true for background images and such, whereby the engine would silently apply the correction necessary for the back/fore/overlay image to be converted to linear colorspace. Even though no further processing will be applied on these particular images, the final gamma correction step would end up restoring them to their original colors, thus maintaining the artist's intent.
I believe our texture loading infrustructure already allows an options dictionary to be passed to the texture loader, so this might actually be doable. Unfortunately, RL seens to be taking over once more here so I don't know when I'll be able to take a look at this - I do not exactly expect it to be trivial . But I am hopeful that it can be done.
Will do. I should be able to have a run through in the next day or so, time permitting
OK, it's looking heaps better! I'd be prepared to go with this, but in the interests of getting the fine details nutted out, here's a new comparison shot from in game:
Again, left side is the branch from #428, right side is from Oolite version 1.88. The right image is a little brighter than what the new branch produces. As an example, the RGB values for the cyan color on the right are (0, 148, 255). On the left, the same pixel reads as (39, 147, 202) (for reference, I picked a pixel at the centre of the branch in the "h" character, so it should be the brightest version of cyan, rather than any of the antialiased pixels). Note, I can't see any color variance in the original images when run in version 1.88.
This might all be expected, though. As I said, I'd be happy to run with this, particularly if getting exact color parity with the original images is a mountain of work.
Thanks for testing, here is my input on your observations:
The slight difference we see now is exclusively due to the tone mapping algorithm (ACES). No matter which tone mapper we go with, the colors will not be 100% matched to the non-tonemapped image and there will always be some minor differences. You can verify that tone mapping is the actor responsible for the observed difference by changing the relevant line in the oolite-final.fragment shader (replace vec3 result = ACESFilm(hdrColor);
with vec3 result = hdrColor;)
. Doing so will result in an almost perfect reproduction of the original image.
Unfortunately we must tone map for proper color dynamic range handling though and ACES is one of the best tone mappers around (I know this is subjective to a degree, but it is widely used in the film and games industries, which is why it was selected for Oolite). Since you feel happy to go with what we have and since one more user in the forum has reported that they are happy with the way it looks now, I believe that we can proceed with the merge.
Yep, 100% merge! Thanks for sorting this out.