I have seen (several) (semi)-(related) (posts) (about) this, but none answers the following question directly:
When exactly does glTexImage.D clamp input values to [0,1]?
The documentation seems to imply that it always does (with the possible exception of GL_DEPTH_STENCIL, which I seem to remember being disallowed, although the documentation doesn't support this). This is how I'm currently handling it in my code.
Other of the linked sources seem to suggest that data is clamped exactly when it is converted to a normalized color format, or that it is only clamped when the image format is integral. To confirm this, I tested a GL_RGBAF32 internal format: negative float data are clamped to 0, while positive data aren't clamped at all.
Then there's (all combinations of) unsigned/signed normalized/unnormalized integer internal formats.
So, when does pixel unpack, specifically glTexImage.D, clamp data? I'm specifically interested in GL_((R|RG|RGB|RGBA)((8|16|32)(|I|UI|_SNORM)|(16F|32F))|DEPTH_COMPONENT(16|24|32)) (i.e., all the color floating-point and integer textures, plus all depth textures), but probably all internal formats should be addressed.
It does not have to clamp anything, technically.
One place this behavior is defined is by the pixel transfer operation. In other words, if the internal format and the format of the data you are transfering into the texture differ, then lots of interesting things can happen including scaling/clamping to a certain range. If you properly match internal format and pixel transfer format you can avoid implicit normalization and clamping of the image data since no conversion will be necessary.
Of course, pixel transfer is only one source of clamping. In GL4.3 you can share the underlying data store between multiple texture objects and change the internal format for compatible image classes (e.g. any 32-bit texture format) using texture views. That can be used to reinterpret UNORM image data as SNORM when sampled, for example.
I should probably explain that something like GL_RGB8 is fixed-point... more specifically, it is Unsigned Normalized (UNORM). If you were to transfer image data into a GL_RGBA32F image using GL_RGBA as the pixel transfer format and GL_UNSIGNED_BYTE as the data type GL will interpret that as conversion from GL_RGBA8 (source format) to GL_RGBA32F (internal format).
In GLES you must always match these formats because pixel transfer conversion is undefined. In desktop GL, however, this example is going to produce values only in the range [0.0, 1.0]. If you wanted the range to be [-1.0, 1.0] you would need to use an SNORM format.
It works the other way around too, if you try to read from a texture with a different internal format and destination format. If you want to get the proper values into / out of your texture, you need to avoid pixel transfer conversion.
OpenGL 4.4 Core Profile Specification - 8.5 Texture Image Specification - pp. 183
8.4.4.2 Conversion to floating-point
This step applies only to groups of floating-point components. It is not performed on indices or integer components. For groups containing both components and indices, such as GL_DEPTH_STENCIL, the indices are not converted.
Each element in a group is converted to a floating-point value. For unsigned or signed integer elements, equations 2.1 or 2.2, respectively, are used.
The specification goes on later to further clarify this behavior for all classes of image data:
OpenGL 4.4 Core Profile Specification - 8.5 Texture Image Specification - pp. 184
The selected groups are transferred to the GL as described in section 8.4.4 and then clamped to the representable range of the internal format. If the internalformat of the texture is signed or unsigned integer, components are clamped to [−2n−1, 2n−1 − 1] or [0, 2n − 1], respectively, where n is the number of bits per component. For color component groups, if the internalformat of the texture is signed or unsigned normalized fixed-point, components are clamped to [−1, 1] or [0, 1], respectively. For depth component groups, the depth value is clamped to [0, 1]. Otherwise, values are not modified. Stencil index values are masked by 2n − 1, where n is the number of stencil bits in the internal format resolution (see below). If the base internal format is GL_DEPTH_STENCIL and format is not GL_DEPTH_STENCIL, then the values of the stencil index texture components are undefined.
Related
I would like to ask a clarification about the sRGB color space and its 8bit per channel representation in OpenGL. After digging in the subject, I found out that storing images in sRGB is just made to compensate the opposite gamma operation done by the monitor which "imitates" what the old CTR did by nature for compatibility reasons. The fact that the human eye has a similiar non linear response is just a coincidence and has nothing to do with gamma correction (as many confusing articles claim) as the output will be linear again anyway.
Assuming that this understanding is right, in OpenGL we have the GL_SRGB8_ALPHA8 format for textures, which has 8bit per channel. However since the range of 0~255 is the same as a linear RGB texture, does this mean that to convert a linear 8bit per channel texture to sRGB the color values remain unchanged and a simple "flag" tells OpenGL: Look this 0~255 range is not linear so interpret them as a curve?
What about sRGB 16bit per channel images (ex. 16bit PNGs) ?
The range is the same. The values are not.
Your linear values before storing are floating-point. So they have greater precision than an 8-bit-per-channel format.
If you store them in a linearRGB image, you're taking the input range [0, 1] and evenly mapping them to [0, 255].
But if you do sRGB conversion, then you're taking the [0, 1] range and mapping them to [0, 255] via a non-linear gamma mapping of approximately 2.2. While this non-linear mapping does not magically create more values, what it does do is effectively give you more precision in the higher parts of the range than the lower parts.
In sRGB conversion, values in the input range [0.5, 1] are mapped to [56, 255]. That's over 75% of the output range that's covered by 50% of the input range. This gives you a better representation of the larger values in your input.
A linear mapping loses precision evenly. The sRGB mapping loses more precision in the darker areas than the lighter. Or to put it another way, it preserves more precision in the lighter areas than the linear mapping.
For a memory vs. visual quality tradeoff, sRGB comes out better overall than linearRGB 8-bit-per-channel.
However since the range of 0~255 is the same as a linear RGB texture, does this mean that to convert a linear 8bit per channel texture to sRGB the color values remain unchanged and a simple "flag" tells OpenGL: Look this 0~255 range is not linear so interpret them as a curve?
It depends on what operation you're talking about.
An sRGB texture is a texture that stores its RGB information in the sRGB colorspace. However, shader operations are assumed to want data in the linearRGB colorspace, not sRGB. So using an sRGB format means that texture fetches will convert the pixels they read from sRGB to linearRGB.
Writes from a fragment shader to an FBO-attached image using an sRGB format may or may not perform conversion. Here, conversion has to be explicitly enabled with GL_FRAMEBUFFER_SRGB. The idea being that some operations will generate values in the sRGB colorspace (GUIs, for example. Most images were created in the sRGB colorspace), while others will generate values in linearRGB (normal rendering). So you have an option to turn on or off conversion.
The conversion also allows blending to read sRGB destination pixels, convert them to linear, blend with the incoming linearRGB values, and then convert them back to sRGB for writing.
Uploads to and downloads from an sRGB image will write and read the pixel values in the sRGB colorspace directly.
What about sRGB 16bit per channel images (ex. 16bit PNGs) ?
What about them? OpenGL has no 16-bit-per-channel sRGB formats.
sRGB conversion is typically done via a 256-entry table lookup. For every sRGB value, there is a pre-computed linear one.
So, just like any other case where an image format offers something that OpenGL doesn't match, you'll have to manually convert them.
Gamma is Good
I found out that storing images in sRGB is just made to compensate the opposite gamma operation done by the monitor...the fact that the human eye has a similiar non linear response is just a coincidence and has nothing to do with gamma correction ... Assuming that this understanding is right,
That understanding is not correct. Storing images with a gamma curve is necessary to increase the data density in dark areas for perceptual reasons. Without gamma, you would need 12 bits for linear for the same image fidelity you get with 8 bits and a gamma curve.
Old school TVs could have been designed to use a linearized signal, but in fact the design techniques of the day increased gamma from the theoretical 1.5 to the commonly used 2.4. As a function of system design the use of gamma reduced the perception of noise in the broadcast signal.
A gamma-type of transfer curve makes the most of a limited bandwidth by weighting the available bandwidth per human non-linear visual perception.
OPENGL
As for your question, If the internal format parameter is GL_SRGB, GL_SRGB8, GL_SRGB_ALPHA, or GL_SRGB8_ALPHA8, the texture is treated as if the red, green, or blue components are encoded in the sRGB color space.
This means that the values in the image are assumed to be gamma-encoded values as opposed to GL_RGB8 which are assumed to be linear.
However since the range of 0~255 is the same as a linear RGB texture, does this mean that to convert a linear 8bit per channel texture to sRGB the color values remain unchanged and a simple "flag" tells OpenGL: Look this 0~255 range is not linear so interpret them as a curve? What about sRGB 16bit per channel images (ex. 16bit PNGs) ?
The range or bit depth has nothing at all to do with a gamma curve being used or not.
An image needs gamma for perceptual reasons. A linear texture map or bump map does not. If you use the GL_SRGB8 tag on a linear bump map, then GL will use sRGB gamma on that linear data which you do NOT want to do - that is it will apply a power curve of approximately 2.2 to linear 1.0 values, and this is not want you want, unless your bump map IS an image with a gamma curve.
The sRGB tag is there so that when you have an sRGB image which has the color values encoded with a ~1/2.2 curve, those values become linearized.
I want to:
create a read and writable 1-channel texture that contains integers.
using a shader, write integer "I" to the texture.
use the texture as a source, sample it and compare if the sample is equal to the integer I.
All this with core profile 3.3.
This is what I've got so far:
I create the texture like so:
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_INT, (java.nio.ByteBuffer) null);
I've Also tried GL_R8I and GL_RED_INTEGER, but that won't work.
I bind this texture as my FBO, set blendfunc to (GL_ONE, GL_ONE) (additive) and render 1 quad using a shader that simply does:
layout(location = 2) out float value;
main{ value = 1.0;}
Again, Integers won't work here!
Next, I bind another FBO and use the above as a source. I use a shader that samples the above with:
"if (texture2D(Tshadow, v_sampler_coo).r == 1.0){discard;}" + "\n"
The problem is that only way I've managed to get it to somehow work is by outputting:
out_diffuse = 1.0;
The compared:
if (texture2D(Tshadow, v_sampler_coo).r == 1.0){discard;}
Any other value, even a power of two (2^-2, 2^-3 etc) will not generate a true statement, nothing except 1.0.
Now what I'd ideally like is to be able to write an integer and sample that integer. But I can't manage. And regardless if I succeeded to write and integer, the sampler2D thing only returns normalized floats, right? And that's the only way to sample a color attachment?
UPDATE:
I found that if I write 0.5, then this work:
if (texture2D(Tshadow, v_sampler_coo).r == 0.4980392307){discard;}
The interal format GL_R8I is actually the correct format for your use case, and the spec explicitely lists that format as color-renderable.
layout(location = 2) out float value;
Why are you using a float output type if you intend to store integers? The OpenGL 3.3 core profile specification explicitely states the following:
Color values written by a fragment shader may be floating-point,
signed integer, or unsigned integer. If the color buffer has an signed
or unsigned normalized fixed-point format, color values are assumed to
be floating-point and are converted to fixed-point as described in
equations 2.6 or 2.4, respectively; otherwise no type conversion is
applied.
However, as you put it:
Now what I'd ideally like is to be able to write an integer and sample that integer.
You can do that, assuming you use the correct integer sampler type in the shader. What you can't do is use blending with integers. To quote the spec:
Blending applies only if the color buffer has a fixed-point or
floating-point format. If the color buffer has an integer format,
proceed to the next operation.
So if you need the blending, the best option is to work with GL_R8 normalized integer format, and with floating point outputs in the shader.
With more modern GL, you could simply emulate the additive blending by directly sampling the previous value in the shader which is updating the texture. Such feedback loops where a shader reads exactly the texel location it is later going to write to are well-defined and allowed in recent GL versions, but unfortunately not in GL3.x.
UPDATE
I found that if I write 0.5, then this work:
if (texture2D(Tshadow, v_sampler_coo).r == 0.4980392307){discard;}
Well, 0.5 is not representable by normalized integer formats (and actually, the bit depth does not even matter, it will never work). In the 8 bit case, the GL will convert
0.5 to 0.5*255 = 127.5. Now it will be implementation specific if it will round to 127 or 128, so you will end up with either 127.0/255.0 (which you got), or 128.0/255.0.
Note on the rounding rules: In the GL 3.3 spec, it is stated that the value after the multiplication
is then cast to an unsigned binary integer value with exactly b bits
With no rounding at all, so that 127 should always be the result. However, in the latest version, GL 4.5, it is stated as
returns one of the two unsigned binary integer values with exactly b
bits which are closest to the floating-point value r (where rounding
to nearest is preferred).
so actually, any rounding behavior is allowed...
As a final note: you should never compare floats for equality.
I am currently faced with a problem closely related to the OpenGL pipeline, and the use of shaders.
Indeed, I work on a project whose one of the steps consists of reading pixels from an image that we generate using OpenGL, with as much accuracy as possible : I mean that instead of reading integers, I would like to read float numbers. (So, instead of reading the value (134, 208, 108) for a pixel, I would like to obtain something like (134.180, 207.686, 108.413), for example.)
For this project, I used both vertex and fragment shaders to render my scene. I assume that the color computed and returned by the fragment shader, is a vector of 4 floats (one per RGBA component) belonging to the "continuous" [0, 1] internal. But, how can I get it in my C++ file ? Is there a way of doing it ?
I thought of calling the glReadPixels() function just after having rendered my scene in a buffer, by setting the format argument to GL_RGBA, and the data type of the pixel data to GL_FLOAT. But I have the feeling that the values associated to the pixels that we read, have already been casted to a integer in the meanwhile, because the float numbers that I finally get, correspond to the interval [0, 255] clamped to [0, 1], without any gain in precision. A closer look on the OpenGL spectifications strengthens this idea : I think there is indeed a cast somewhere between rendering my scene, and callingglReadPixels().
Do you have any idea about the way I can reach my objective ?
The GL_RGBA format returned by the fragment shader stores pixels components in 8-bit integers. You should use a floating point format, such as GL_RGBA16F or GL_RGBA32F, where 16 and 32 are the depths for each component.
I'm using my alpha channel as an 8 bit integer index for something unrelated to blending so I want to carefully control the bit values. In particular, I need for all of the pixels from one FBO-rendered texture with a particular alpha value to match all of the pixels with the same alpha value in the shader. Experience has taught me to be careful when comparing floating point values for equality...
While setting the color values using the floating point vec4 might not cause me issues, and my understanding is that even a half precision 16bit float will be able to differentiate all 8 bit integer (0-255) values. But I would prefer to perform integer operations in the fragment shader so I am certain of the values.
Am I likely to incur a performance hit by performing integer ops in the fragment shader?
How is the output scaled? I read somewhere that it is valid to send integer vectors as color output for a fragment. But how is it scaled? If I send a uvec4 with integers 0-255 will it scale that appropriately? I'd like for it to directly write the integer value into the pixel format, for integer formats I don't want it to do any scaling. Perhaps for RGBA8 sending in an int value above 255 would clamp it to 255, and clamp negative ints to zero, and so on.
This issue is made difficult by the fact that I cannot debug by printing out the color values unless I grab the rendered images and examine them carefully. Perhaps I can draw a bright color if something fails to match.
Here is a relevant thread I found on this topic. It has confused me even more than before.
I suggest not using the color attachment's alpha channel, but an additional render target with an explicit integer format. This is available since at least OpenGL-3.1 (the oldest spec I looked at, for this answer). See the OpenGL function glBindFragDataLocation, which binds a fragments shader out variable. In your case a int out $VARIABLENAME. For input into the next state use a integer sampler. I refer you to the specification of OpenGL-3.1 and GLSL-1.30 for the details.
I tried reading the OpenGL ARB_texture_float spec, but I still cannot get it in my head..
And how is floating point data related to just normal 8-bit per channel RGBA or RGB data from an image that I am loading into a texture?
Here is a read a little bit here about it.
Basically floating point texture is a texture in which data is of floating point type :)
That is it is not clamped. So if you have 3.14f in your texture you will read the same value in the shader.
You may create them with different numbers of channels. Also you may crate 16 or 32 bit textures depending on the format. e.g.
// create 32bit 4 component texture, each component has type float
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, 16, 16, 0, GL_RGBA, GL_FLOAT, data);
where data could be like this:
float data[16][16];
for(int i=0;i<16*16;++i) data[i] = sin(i*M_PI/180.0f); // whatever
then in shader you can get exactly same (if you use FLOAT32 texture) value.
e.g.
uniform sampler2D myFloatTex;
float value = texture2D(myFloatTex, texcoord.xy);
If you were using 16bit format, say GL_RGBA16F, then whenever you read in shader you will have a convertion. So, to avoid this you may use half4 type:
half4 value = texture2D(my16BitTex, texcoord.xy);
So, basically, difference between the normalized 8bit and floating point texture is that in the first case your values will be brought to [0..1] range and clamped, whereas in latter you will receive your values as is ( except for 16<->32 conversion, see my example above).
Not that you'd probably want to use them with FBO as a render target, in this case you need to know that not all of the formats may be attached as a render target. E.g. you cannot attach Luminance and intensity formats.
Also not all hardware supports filtering of floating point textures, so you need to check it first for your case if you need it.
Hope this helps.
FP textures have a special designated range of internal formats (RGBA_16F,RGBA_32F,etc).
Regular textures store fixed-point data, so reading from them gives you [0,1] range values. Contrary, FP textures give you [-inf,+inf] range as a result (not necessarily with a higher precision).
In many cases (like HDR rendering) you can easily proceed without FP textures, just by transforming the values to fit in [0,1] range. But there are cases like deferred rendering when you may want to store, for example, world-space coordinate without caring about their range.