SIMPLE QUESTION:
Does OpenGL clip texture values to the range 0-1 by default? And if it does is there a way to disable this?
DETAILED QUESTION:
I have a texture which, when created, I put some default values in.
I create the texture of RGBA values size 16x16 like this:
//Generate the texture and get its ID
GLuint texID;
glGenTextures(1, &texID);
//Bind the texture
glBindTexture(GL_TEXTURE_2D, texID);
//Define the size of the texture
int width = 16;
int height = 16;
int numberOfElementsPerTexel = 4;
int size = width * height * numberOfElementsPerTexel;
//Create an empty array of texel data
float data[size];
std::fill_n(data, size, 0.0f);
//Add the empty data to the texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, GL_RGBA, GL_FLOAT, data);
To test this works correctly after I create and fill the texture I inspect the contents like this:
//Bind the texture
glBindTexture(GL_TEXTURE_2D, texID);
//Get the data from the texture
float texData[size];
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, texData);
{
for(int i = 0; i < size; i++)
{
printf("Value at %d: %f\n", i, texData[i]);
}
}
So this gives a load of 0's as I would expect, then as another test before I call glTexImage2D I modify one of the values in the array of data:
data[0] = 1.0f;
And as expected this adds a 1.0 in the first number to be printed out. Any number between 0 and 1 prints out as expected however any number greater than 1 or less than 0 clips to that range?
This sounds like it could be because the texture stores colour values and therefore clips to the range of RGBA values. Is this the case or am I doing something wrong?
And if this is the case is there a way to disable it? I'm looking to store arbitrary data in the texture not colour values its just I'm looking to improve performance because the texture/texture lookup structure is highly optimised in the GPU
Does OpenGL clip texture values to the range 0-1 by default?
Sometimes - depending on the way you are accessing the texture and the texture's internal format.
And if it does is there a way to disable this?
Not really, but you can change the internal format to get what you want.
GL Spec 4.6, Section 2.3.5 Fixed-Point Data Conversions:
When generic vertex attributes and pixel color or depth components are repre- sented as integers, they are often (but not always) considered to be normalized. Normalized integer values are treated specially when being converted to and from floating-point values, and are usually referred to as normalized fixed-point. Such values are always either signed or unsigned.
...
The conversion from a floating-point value f to the corresponding unsigned nor- malized fixed-point value c is defined by first clamping f to the range [0, 1], then
The following call is deceiving as GL_FLOAT
is given for the texture format (which just says the data you give it is 32 bit floating point). The internal format is GL_RGBA
. I believe most implementations end up using essentially GL_RGBA8 to store this, i.e. 8 bits per channel like unsigned char r, g, b, a
(although this is not required by the spec). From the above section of the GL spec, the input data values values will be clamped and then converted and to fixed point. 0.0f and below becomes 0 and 1.0f and above becomes 255.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, GL_RGBA, GL_FLOAT, data);
The same happens when you read and write with such textures in a shader. The integer values get transparently scaled to the [0, 1] range. In your case, reaing with glGetTexImage
+ GL_FLOAT
also scales the integer values to back down to [0, 1] but the data you passed in has already been clamped. If instead you read with GL_UNSIGNED_BYTE
and an unsigned char
/GLubyte
array, you should see 0 to 255. When accessing textures it's probably faster to access it with the same format as the internal format. Then there's no need for a conversion.
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, texData);
In short, yes, anything outside the range 0 to 1 gets clamped (eg. using an FBO to write to a typical RGB/A texture). If you don't need the precision but still want numbers bigger than 1 you could scale the values beforehand.
If you need more precision you could use GL_RGBA32F
, as below. In this case no conversion is required - your input is the same as the texture storage and clamping does not happen. The downside is it would take up 4 times as much data, which means 4 times as much to transfer when accessing. Of course this isn't just more regular precision because you get the exponent representation in the floating point format.
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, GL_RGBA, GL_FLOAT, data);