Why does OpenGL act this way when I use color values greater than 1.0f?
Asked Answered
T

1

7

I was working on learning OpenGL and I was tasked with creating the figure below: Correct image

This is what my intention was, but the first time I wrote it I buffered the colors as floats from 0 - 255 instead of 0.0 - 1.0. Clearly that was wrong, but this is what was displayed: Incorrect image

Only the center triangle is displayed, only with outlines and the colors are the first three vertex colors. Why did this happen? What does OpenGL do when I buffer colors that aren't in the range [0.0, 1.0]? I couldn't find documentation on this.

My shaders are as follows:

vertex:

layout (location = 0) in vec3 Position;
layout (location = 2) in vec4 vertexColor;

out vec4 vertexColor0;

void main() {
    gl_Position = vec4(Position, 1.0f);
    vertexColor0 = vertexColor;
}

fragment:

in vec4 vertexColor0;

void main() {
    gl_FragColor = vertexColor0;
}

And here's the code I use for buffering data and drawing data:

static const int npoints = 9;
static const glm::vec3 points[npoints] = {
                                glm::vec3(-0.5, 0.5, 0.0),
                                glm::vec3(-0.7, 0.0, 0.0),
                                glm::vec3(-0.3, 0.0, 0.0),

                                glm::vec3(0.2, 0.0, 0.0),
                                glm::vec3(-0.2, 0.0, 0.0),
                                glm::vec3(0.0, -0.5, 0.0),

                                glm::vec3(0.5, 0.5, 0.0),
                                glm::vec3(0.3, 0.0, 0.0),
                                glm::vec3(0.7, 0.0, 0.0)
                            };

//the incorrect version, in the correct version 255 is replaced with 1.0f and 127 with 0.5f
static const glm::vec4 colors[npoints] = {
                                glm::vec4(0, 255, 255, 255),
                                glm::vec4(255, 0, 255, 255),
                                glm::vec4(255, 255, 0, 255),

                                glm::vec4(255, 0, 0, 255),
                                glm::vec4(0, 255, 0, 255),
                                glm::vec4(0, 0, 255, 255),

                                glm::vec4(0, 0, 0, 255),
                                glm::vec4(255, 255, 255, 255),
                                glm::vec4(127, 127, 127, 255)
                            };

//Create the VAO and the buffer data
void Figure::initialize() {
    glUseProgram(shaderProgram); //shaderProgram is a member set to the built shader above

    glGenVertexArrays(1, &vertexArrayObject); //vertexArrayObject is also a member
    glBindVertexArray(vertexArrayObject);

    GLuint VBO;
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER,
                 npoints * sizeof(glm::vec3),
                 points,
                 GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    GLuint CBO;
    glGenBuffers(1, &CBO);
    glBindBuffer(GL_ARRAY_BUFFER, CBO);
    glBufferData(GL_ARRAY_BUFFER,
                 npoints * sizeof(glm::vec4),
                 colors,
                 GL_STATIC_DRAW);

    glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(2);
}

//draw the figure
void Figure::draw() {
    glUseProgram(shaderProgram);
    glBindVertexArray(vertexArrayObject);
    glDrawArrays(GL_TRIANGLES, 0, npoints);
}
Toweling answered 19/9, 2014 at 4:4 Comment(6)
Possibly related questionToweling
You might use glColor3ub and actually use unsigned bytes.Spalato
Most likely it's overflowing. Since you're using whole numbers, it's overflowing to the same value every timeRayon
@Rayon how could it overflow? 255 is representable as a float. They have to cast it at some point to an unsigned byte after multiplying by 255. I don't really see how that cast could cause overflow, usually casts just mod by the max size of the destination. Some documentation saying floats > 1.0f cause undefined behavior or something would be nice.Toweling
@Toweling I was thinking in ints and it taking the least significant 8 bits. You're quite right that doesn't apply with floats.Rayon
It's being clamped. In the fragment shader when you assign a color value that is outside the range of 0 to 1 to gl_FragColor then it gets clamped to 0 to 1 range unless you are rendering to a buffer that supports writing arbitrary floating point values.Hearthside
M
6

Since your values are so high, any points inside the triangle will have a little bit of each color corner. Each corner gives one of the RGB components. Let's evaluate a pixel a small distance away from the red corner. This pixel might get the unclipped values vec3(253.0f, 1.2f, 1.9f). If we clip this value to the range 0.0f - 1.0f, you will see that this becomes vec3(1.0f,1.0f,1.0f) or white.

The reason the edges are not white is that this is the only place where the interpolation distance is low enough that 0.0f - 255.0f does not overflow 1.0f for one of the components. Anywhere on the line between the red and blue point there is so little green that it does not overflow 1.0, therefore we get a purple line. If you look closely at the corners, you also see that this is the only place where there is only the color of the corner (or at least a small amount from the other corners).

Anywhere else on the triangle will be clipped to vec3(1.0f,1.0f,1.0f) and you get a white triangle.

EDIT: The reason why the triangle on the left does not have these edges is because the corners have full intensity (255.0f) on two of the RGB components (vec3(255.0f,255.0f,0.0f),vec3(0.0f,255.0f,255,f) and vec3(255.0f,0.0f,255.0f)). On one of the edges it interpolates between vec3(255.0f,255.0f,0.0f) and vec3(255.0f,0.0f,255.0f). Going only slightly away from one of the corners, the only component being 0.0f will interpolate towards 255.0f since the other corner will always have fully intensity on that particular RGB component. So as soon as you move slightly away from the corner you will get a value like for instance vec3(255.0f,253.7f,1.3f). This will clip to white there for in this case the edges will also be white. If you increase the resolution you might see that exactly on the corner there might be one pixel that is not fully white, but I am not sure about that.

The triangle on the right have full intensity on all RGB components in all corners except the one that is black. As soon as you move slightly away from the black corner, the values will be something like vec3(1.3f, 1.3f, 1.3f) which will clip towards white and the entire triangle will appear white. Again, if you increase the resolution you might see a black dot at the black corner.

Molotov answered 19/9, 2014 at 16:53 Comment(4)
But this does not answer why the left triangle and the middle one behave differently (i.e: the one in the center get's colored edges, while the other not). Why is that? I bet is some kind of overflow, but cannot figure it out.Osugi
Thanks @bofjas! That makes sense, basically all color components in the right triangle are always > 1.0, therefore white everywhere. The triangle in the middle has the edge colors with at least one color component = 0, therefore you get to "see something" because it is not white. Great!Osugi
This is great, its not even undefined behavor like I thought. So basically the colors get clamped after interpolating?Toweling
Yes. The rasterization have no idea of what the vertex shader output are going to be used for in the fragment shader. I just sees a vec4 which could be anything (some form of position or normal). If the colors were to be clamped before interpolating, then the GLSL compiler would need to somehow deduce what the vertex shader output are going to be used for.Molotov

© 2022 - 2024 — McMap. All rights reserved.