Hello!
I’m working on a game for Android, I’m using OpenGL ES 3.0, and I’m getting some inconsistent colors probably due to not handling gamma correction well.
My understanding is that, for standard OpenGL, the cleanest way is to enable GL_FRAMEBUFFER_SRGB which means that the framebuffer has sRGB values internally but accepts linear input. So you can work with linear colorspace in the fragment shader, and the gamma correction happens automatically.
But this appears not to be possible with OpenGL ES. Correct me if I’m wrong there, but it seems like OpenGL ES does not implement GL_FRAMEBUFFER_SRGB or equivalent. I guess it’s just designed for working with sRGB values throughout. If I use linear space in the fragment shader and do a gamma correction to my final color, apart from the performance overhead, semi-opaque blending is still going to come out wrong.
Am I missing something? Does anyone know of a (preferably simple and generally compatible) way to use linear color space in ES?
ES 3.0 “New Features” includes: “8-bit sRGB textures and framebuffers (without mixed RGB/sRGB rendering)”
(emphasis added)
In the section about framebuffer blending, GL 3.2 says: “If FRAMEBUFFER_SRGB is enabled and the value of FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING…”
While ES 3.0 says: “If the value of FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING…”
So it works exactly like desktop OpenGL where the encoding is determined by the internal format of each attachment, except that the enable doesn’t exist.
Thank you for that… I think I understand. So, if the attachment’s internal format is sRGB, it would expect linear values from the fragment shader and the main difference is we can’t turn off that behavior. And conversely, if I were just passing through sRGB values throughout my rendering, I’d want the attachment’s internal format to be linear?
Correct, you can’t turn off the sRGB conversion when using an sRGB-encoded attachment (mixed rendering isn’t supported.)
When using an FBO, you can control the internal format of each attachment. The default framebuffer #0 encoding is implementation-defined, so you should take care with the platform-specific surface creation.
No. 1. Stop thinking about gamma correction. Nothing is being corrected. sRGB is a bandwidth-saving encoding. Once you understand that it is an encoding a lot of things become clearer (not necessarily the following things).
If the attachment’s internal format is sRGB, fragment shader outputs are encoded to sRGB by the hardware so yes your fragment shader outputs must be linear. But you should do all your color calculations in linear, i.e decode first, to get correct results including for filtering, lighting and blending so this behaviour is what you need. I hope it is now clear that “passing through sRGB values throughout (your) rendering” is a very bad idea.
Many implementations allow you to create an sRGB default framebuffer.
Unfortunately many implementations “linear” default framebuffers are actually sRGB because the data is simply passed to the sRGB display. Once such is WebGL which does not offer choice of an sRGB default FB. (This should change once the web colorspace stuff becomes a reality.) In such cases applications need to encode their fragment shader outputs to sRGB for correct results and should avoid hardware blending operations with the framebuffer contents.
No. 1. Stop thinking about gamma correction. Nothing is being corrected
Amen to that. I would have said “gamma f**kery” but I don’t think it’s standard terminology.
Many implementations allow you to create an sRGB default framebuffer.
Unfortunately many implementations “linear” default framebuffers are actually sRGB because the data is simply passed to the sRGB display.
This right here is what I’m having a problem with. Setting up an sRGB default framebuffer doesn’t seem to be as simple as I’d hoped. I tried requesting it by setting the EGL_COLORSPACE attribute to EGL_COLORSPACE_sRGB when creating the EGL configuration. But I’m getting no matches. Otherwise (not requesting that), I tried using eglGetConfigAttrib() to query that attribute on the configs I found. It returns false, so maybe EGL_COLORSPACE isn’t even a valid attribute? Am I doing this wrong?
Failing that, it seems like the only viable option is to use an FBO with an sRGB texture, render everything to that, and then render that texture to a quad in the default framebuffer. It seems quite inefficient, if I’m not wanting to add postprocessing effects. Also I’ve given it a try and it seems to make no difference to the color space. Haven’t looked deep into that but I suspect the sampler converts the texture values back to linear so if I want sRGB output (or don’t want it but unfortunately need it) I’d have to do the conversion calculation myself in the fragment shader. I feel like I’m taking crazy pills!
Is that the only way? Since I have no direct access to the textures in the default framebuffer, I’m fairly sure direct copying using something like glCopyImageSubData() isn’t a possibility.
Please let me know if I’m barking up the wrong tree. I feel like it shouldn’t be this complicated and I’m still missing something.
The attribute name is EGL_GL_COLORSPACE and the relevant value is EGL_GL_COLORSPACE_SRGB. I don’t know if what you wrote is a typo or if that is what you are trying to use. If the latter, that is your problem. These are attributes to pass to eglCreatePlatformWindowSurface not to eglChooseConfig or eglCreateContext. The native window you pass to eglCreatePlatformWindowSurfacewill likely also need to be an sRGB window. Probably that is what you will get when creating a window with default settings on pretty much any platform.
I’ve been using SDL for years because I make portable apps so my knowledge of EGL has rotted. I hope I haven’t given you any bum steers.
That sure brightened things up, I think you found what I was missing!
Thank you so much for your help, both /u/arekkusu and /u/markc. Sorry I can’t mark either of your replies as the solution since it’s a bit of both. I’ll summarize what I learned for the sake of the next person:
To use linear RGB in OpenGL ES fragment shader, you can render to a texture/framebuffer with sRGB encoding. Linear RGB output will be converted automatically, blending will be handled correctly, and the end result will be sRGB encoded. This behavior happens automatically when you have sRGB encoding and unlike standard OpenGL, you cannot enable/disable GL_FRAMEBUFFER_SRGB to switch this on and off.
To set up the default framebuffer like that you need to pass the attribute EGL_GL_COLORSPACE with value EGL_GL_COLORSPACE_SRGB when calling eglCreateWindowSurface/eglCreatePlatformWindowSurface. You also need your native window to be sRGB encoded but that’s usually the default.