JadenJin
JadenJin

Reputation: 83

glReadPixels() returns background color?

I am trying to get the colour which was clicked on in OpenGL via glReadPixels():

#include <iostream>

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

const char *vertexShaderSource = "#version 330 core\n"
        "layout (location = 0) in vec4 aPos;\n"

        "uniform mat4 projection;\n"
        "uniform mat4 model;\n"

        "void main()\n"
        "{\n"
        "   gl_Position = projection * model * vec4(aPos.xy, 0.0, 1.0);\n"
        "}\0";
const char *fragmentShaderSource = "#version 330 core\n"
        "out vec4 FragColor;\n"
        "void main()\n"
        "{\n"
        "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
        "}\n\0";

int main() {
    if (glfwInit()) {
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    } else {
        std::cout << "glfw failed to initialise" << std::endl;
    }

    GLFWwindow *window = glfwCreateWindow(500, 500,
                                          "Opengl Selection", NULL, NULL);

    glfwMakeContextCurrent(window);

    if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
        std::cout << "Failed to initialise glad" << std::endl;
    }

    glClearColor(0.3f, 0.2f, 0.1f, 1.0f);
    glViewport(0, 0, 500, 500);

    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success) {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    float vertices[] = {
        // pos      // tex
        0.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 0.0f,

        0.0f, 1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 1.0f, 1.0f,
        1.0f, 0.0f, 1.0f, 0.0f
    };

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindVertexArray(VAO);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    glUseProgram(shaderProgram);

    glm::mat4 projection = glm::ortho(0.0f, 1000.0f, 1000.0f, 0.0f, -1.0f, 1.0f);

    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE,
                       glm::value_ptr(projection));

    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();

        glClear(GL_COLOR_BUFFER_BIT);


        glUseProgram(shaderProgram);

        // glm::mat4 projection = glm::ortho(0.0f, 500.0f, 500.0f, 0.0f, -1.0f, 1.0f);
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
        model = glm::scale(model, glm::vec3(100.0f, 100.0f, 0.0f));

        glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE,
                           glm::value_ptr(model));

        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);

        glfwSetMouseButtonCallback(window, [](GLFWwindow *window, int button, int action, int mods) {
            if (action == GLFW_PRESS) {
                double xPos, yPos;

                glfwGetCursorPos(window, &xPos, &yPos);

                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);


                unsigned char data[4];


                glReadPixels(xPos, yPos, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);

                std::cout << (float) data[0] << " " << (float) data[1] << " " << (float) data[2] << " " << (float) data[
                            3]
                        << std::endl;
            }
        });


        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    return 0;
}

However, where ever I click, OpenGl simply returns the colour of the background. I believe I am doing something wrong regarding how I am rendering the square on the screen.

Upvotes: 0

Views: 145

Answers (1)

Yun
Yun

Reputation: 3812

The main problem* is that the position passed to glfwGetCursorPos is "relative to the upper-left corner of the content area of the specified window", whereas the position passed to glReadPixels is relative to the lower-left corner of the framebuffer.
Moreover, the coordinates used by many OpenGL functions (such as glReadPixels) are in pixels, whereas the coordinates obtained from glfwGetCursorPos are in GLFW's (virtual) screen coordinates which don't necessary correspond to pixels (e.g. when the display is scaled).

First, the call to glViewport should be corrected: the window size is in screen coordinates instead of pixels (the GLFW documentation specifically warns for this), and the dimensions passed to glfwCreateWindow as merely a hint - the actual window may have a different size. The implementation should first retrieve the framebuffer size using glfwGetFramebufferSize and pass this on to glViewport. As per GLFW's documentation:

int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);

The main problem is solved by converting the screen coordinates to pixel coordinates as used by OpenGL, and by inverting the y-position for the call to glReadPixels:

int windowWidth, windowHeight;
glfwGetWindowSize(window, &windowWidth, &windowHeight);

int framebufferWidth, framebufferHeight;
glfwGetFramebufferSize(window, &framebufferWidth, &framebufferHeight);

// Convert GLFW's screen coordinates to pixel coordinates using the ratio between the window size and the framebuffer size
double xPosInPixels = xPos * static_cast<double>(framebufferWidth) / windowWidth;
double yPosInPixels = yPos * static_cast<double>(framebufferHeight) / windowHeight;

// Shift to use OpenGL's convention of pixel centers being at half-integers
xPosInPixels += 0.5;
yPosInPixels += 0.5;

// Invert along the y-axis
yPosInPixels = framebufferHeight - yPosInPixels;

glReadPixels(xPosInPixels, yPosInPixels, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);

For a more detailed description of this process, please see the first part of this answer by derhass.

Now, the color of the square is returned when both xPos and yPos < 50. Note that the implementation now also supports scaled and resized windows, although it is advisable to also change the viewport dimensions when the window is resized. In this case, you may want to use callback functions for the window and framebuffer sizes.

*As first pointed out by Rabbid76 in a comment.

Upvotes: -1

Related Questions