An sRGB Conversion Experience

Started by
6 comments, last by SBD 1 year, 2 months ago

In the course of preparing my (D3D11) renderer to move towards HDR-capable rendering, I've taken the long-overdue step of moving from sRGB-ignorant rendering to sRGB-compliant. I thought it may be illuminating to share my experience, and see if anyone had thoughts and/or similar issues.

One handy migration feature I worked to provide myself was the ability to run the game in either sRGB mode or sRGB-ignorant mode (via a command-line switch). This was ultra-handy for doing A/B comparisons. Mainly for the purposes of tracking down issues and experimenting, I implemented sRGB<->Linear conversion routines for both C++ and HLSL, along with handy utility accessors to determine what mode we're running in and doing "the right thing"/conversion automatically. It's expected that sRGB->Linear conversion will generally be the default when loading attribute color data in sRGB mode.

Of course, setting the appropriate display render target formats was the starting point, to allow for automatic sRGB output conversion. In my case, for the first phase, this is simply moving from R8G8B8A8_UNORM to R8G8B8A8_UNORM_SRGB (eventually, this will shift to a float format later down the line). In addition to the normal monitor output target, it was important to make sure I specified/changed the necessary VR render targets accordingly as well.

Next, I converted all textures from old DXT* formats to BC* formats, and specifically color textures to BC SRGB formats, which will automatically set up sampling to do automatic SRGB->Linear conversion.

I collaborated with ChuckW to update DirectXTK texture loading to allow for ignoring/discarding _SRGB BC format modifier, which allowed for easy switching between sRGB and sRGB-ignorant modes using the same textures.

Generally speaking, I treated all attribute/material color values as sRGB (meaning they are converted to linear on load for calculations, etc.). The overall thought being these values would be selected via a color wheel or similar mechanism by content creators (read; a color on a monitor, thus sRGB). Existing data was either generated via model export (in which case this is likely the proper way to interpret the existing data), or values manually entered by me via a text editor, which meant they are now off. The basic methodology was to take this hit on existing manual data. Via my attribute system I added a way to selectively take a value based on if it has an SRGB tag specified (true/false), which allowed me to hand-tweak values (have both an sRGB one and a non-sRGB one) and be able to run in either sRGB or sRGB-ignorant modes to compare. This allowed me to be able to hand-adjust problematic values in specific instances.

One of the most noticeable effects of being sRGB-aware is that alpha-blending results are different. Light text blending onto dark backgrounds looks better (appropriately "thick"), while dark text on light backgrounds looks worse ("thinner", as it were). When you consider that alpha-blended values in the mid-range will be brighter, this result makes sense. Similarly, additive alpha-blended effects are generally "brighter"...post-process glow is much more noticeable (a desirable result). Things like SSAO, however, are generally more subdued. Again, this makes sense when you consider the curve. SSAO is interesting, as this means the effect is lessened ("shrinks").

Another expected result of sRGB-aware processing is that several techniques that produced linear gradients now exhibited banding artifacts. In particular, in my crepuscular rays processing I had to shift from using an R8G8B8_UNORM output render target to R11G11B10_FLOAT. Further, I also had to add a dither effect to completely eliminate banding (most egregious in VR, which generally is more sensitive to banding issues).

Overall, the process wasn't too difficult. However, I remain a little non-plussed that I could not replicate my sRGB-ignorant output more closely in all cases. SSAO, and my crepescular rays color in particular (which I had "mathematically" specified manually) are still not as close as I want. SSAO I think will just take some adjusting to it being a little more subtle. The color values are a little more vexing. Simply trying to convert them to/from sRGB values doesn't really do the trick (nor do I really expect it to), and given that the results can be technique-dependent, I think I'll probably have to adjust to the fact that this shift and data expectation means using programmer brain to make a nice blue hue via typing in (0.33, 0.33, 1.0) is no longer the paradigm I'm operating under. And, of course, this is a bit of the "worst-case" scenario of having a lot of existing data where you're trying to match a particular look (on your own), versus being able to simply have the artists go back and properly revisit assets themselves.

While this topic has been explored in several previous threads, I'd be interested to hear your own experiences (recent or otherwise) with doing similar sRGB-aware conversions. In particular, your thoughts on attribute color data specification (materials, shader parameters), and if you had similar issues with things like SSAO, blending, etc., and how you handled it would be enlightening.

Advertisement

SBD said:
Light text blending onto dark backgrounds looks better (appropriately "thick"), while dark text on light backgrounds looks worse ("thinner", as it were).

Yes. For this reason I disable sRGB completely on my font shaders.

Aressera said:

SBD said:
Light text blending onto dark backgrounds looks better (appropriately "thick"), while dark text on light backgrounds looks worse ("thinner", as it were).

Yes. For this reason I disable sRGB completely on my font shaders.

In particular, the issue here (for me at least) is that I'm using a bitmap font system, and the fonts are alpha-specified. Alpha, of course, is excluded from sRGB conversion(s), so changing the texture format (normal/sRGB) has no functional effect. I am taking the approach of just having thicker fonts for dark-on-light, rather than having a divergent (non-sRGB) render path for them. We'll see how that works out.

My few cents; as a baseline I believe everything should be/ take sRGB into account, to make it ‘correct’. And when there's an exception, handle that specifically (like perhaps bitmap font with blending)

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

I'm curious to hear if anyone had issues with achieving (or, more specifically, replicating) high-contrast effects when moving to sRGB? As previously mentioned, my SSAO is more subdued, and in the case of my crepuscular rays, I'm having trouble making the shadowed/occluded regions pop (darken) as well as they did previously.

I managed to replicate my sRGB-ignorant SSAO output by applying a gamma curve to my SSAO occlusion factor. Interestingly enough, not a fully sRGB gamma curve (2.4) but rather around 1.75, but I wouldn't necessarily expect a one-to-one result given that the occlusion factor is just a sub-component of the final ambient lighting term. Still not convinced this is the “right”/correct thing to do, but SSAO already falls outside of real/physically-based calculations, so it's not really too unsavory.

Crepuscular rays are proving more difficult, as aside from the color accumulation value (already sRGB→linear converted) the calculations are a mix of various decays where a gamma ramp has no obvious application. I'm also wondering if the final application of the output as an additive blend onto the backbuffer is causing me some sort of subtle interaction I'm missing.

Don't mean to necro this thread, but wanted to update with another subtle gotcha that may be of interest to those doing a similar upgrade. I've been working on migrating to HDR rendering, and have thusly implemented such (float render target, color grading, tonemapping, etc.). This all proceeded without issue, but I did come across a problem with my crepuscular rays (again); namely banding artifacts reappeared. This was somewhat puzzling, as I had previously migrated that post-effect to do float render targets, dithering, etc. as part of my sRGB conversion.

As it turns out, this was not related specifically to my crepuscular rays processing (although its output is susceptible to banding due to its gradual linear color changes), but rather to a specific case in my HDR tonemapping pipeline. Namely, when I had multi-sampling enabled, I output my tonemapping to a non-MSAA intermediary render target, due to also needing to bind a non-MSAA bloom output render target at the same time. However, I reasoned that since tonemapping would be giving me range-mapped 0.0 - 1.0 color values, I could simply use an R8G8B8A8 output render target and save some space. This, along with bloom, is then composited to the final MSAA sRGB8 output render target. However, this was in fact introducing quantization/precision reduction before conversion to sRGB, which was (re)introducing the banding. FLOAT16→RGB8→sRGB8. Of course, the answer here was to use a FLOAT16 render target for tonemapping output as well, thus ensuring that we are getting max precision color when feeding into the (automatic) sRGB conversion.

This was particularly sneaky and really only narrowed down by inspecting the output at every step of the way (and also happily noticing the banding disappeared when MSAA was off). Hope that might save someone else a similar headache.

This topic is closed to new replies.

Advertisement