glReadPixels总是在glClearColor中返回相同的值

时间:2017-10-06 17:01:07

标签: c++ opengl

我一直在努力理解glReadPixels的输出,这在理论上似乎很简单,但实际上却产生令人费解的(至少对我而言)结果。

假设我有一个简单的片段着色器,它绘制一个颜色值为vec4(0.2,0,0,0)的单个三角形,而背景颜色设置为(0.3,1.0,1.0,0.0),就像这样:

enter image description here

下面是我用来制作上图的完整代码(着色器构造除外):

subscribe(MessageType, Handler)

注意,我使用默认的帧缓冲区,它会将我的颜色值视为规范化的有符号整数,并将它们转换为[0-255]之间的范围,即我的背景颜色将是(76,255, 255,0),而我的几何颜色将是(51,0,0,0)。

因此,在绘制几何体并交换缓冲区后,我得到了我的图像。现在我想读出颜色值。为了做到这一点,我在交换缓冲区之前插入必要的glReadPixels相关代码:

#include "shader.h" // shader compile/link/use 
#include <GLFW\glfw3.h>
#include <iostream>

const int DISPLAY_WIDTH = 16; 
const int DISPLAY_HEIGHT = 16; 

//============= shader code ==========================

const GLchar *vertexShaderSource = R"glsl(#version 440
in vec2 position;
void main()
{
    gl_Position = vec4(position, 0.0, 1.0);
})glsl";

const GLchar *fragmentShaderSource = R"glsl(#version 440
out vec4 outColor;

void main()
{   
    outColor = vec4(0.2,0.,0.,0.);
})glsl";

//============= c++ entry point ==========================

int main(int argc, char** argv) {

    glfwInit();
    GLFWwindow* window = glfwCreateWindow(DISPLAY_WIDTH, DISPLAY_HEIGHT, "test", NULL, NULL);
    glfwMakeContextCurrent(window);

    GLenum res = glewInit();

    // triangle data (xy-position)
    float vertices[] = {
    0.0f, 0.5f, 
    0.5f, -0.5f, 
    -0.5f, -0.5f  
    };

    GLuint vbo;
    glGenBuffers(1, &vbo); 
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // enable vertex xy-position attribute 
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // compile, link and use shader program
    Shader shader(vertexShaderSource, fragmentShaderSource);
    shader.Use();

    // rendering loop
    while (!glfwWindowShouldClose(window)) {

       glClearColor(0.3f, 1.0f, 1.0f, 0.0f);
       glClear(GL_COLOR_BUFFER_BIT);

       glDrawArrays(GL_TRIANGLES, 0, 3);
       glFlush();

       // read pixels from backbuffer 
       GLubyte data[DISPLAY_WIDTH * DISPLAY_HEIGHT];
       glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, GL_RED, GL_UNSIGNED_BYTE, data);

       for (int i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) {
           int a = data[i]; // implicit conversion of unsigned char to int
           std::cout << a << std::endl;
       }
       std::getchar(); // wait for user input

       glfwSwapBuffers(window);
       glfwPollEvents();
    }

   glfwTerminate(); 
   return 0;
}

为了便于检查我从帧缓冲区读出的像素值的过程,我只提取红色通道,因此容纳像素数据所需的数据大小为GLubyte* data = new GLubyte[DISPLAY_WIDTH * DISPLAY_HEIGHT]; glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, GL_RED, GL_UNSIGNED_BYTE, data); 。此外,这意味着我打印出的值对于背景颜色应为“76”,对于几何体应为“51”。

令人惊讶的是,我打印出的每个红色通道像素数据(所有DISPLAY_WIDTH * DISPLAY_HEIGHT像素都打印出来)恰好是'76',就好像几何被忽略一样。请注意,我在绘制调用之后和交换缓冲区之前读取了像素。

如果你能让我知道我在这里缺少什么,我将不胜感激。

2 个答案:

答案 0 :(得分:2)

日夜不停&#39;在我的Debian Stretch框上很好:

GLEW version: 2.0.0
GLFW version: 3.2.1 X11 GLX EGL clock_gettime /dev/js Xf86vm shared
GL_VERSION  : 3.0 Mesa 13.0.6
GL_VENDOR   : Intel Open Source Technology Center
GL_RENDERER : Mesa DRI Intel(R) Kabylake GT2 

76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 
76 76 76 76 51 51 51 51 51 51 51 51 76 76 76 76 
76 76 76 76 76 51 51 51 51 51 51 76 76 76 76 76 
76 76 76 76 76 51 51 51 51 51 51 76 76 76 76 76 
76 76 76 76 76 76 51 51 51 51 76 76 76 76 76 76 
76 76 76 76 76 76 51 51 51 51 76 76 76 76 76 76 
76 76 76 76 76 76 76 51 51 76 76 76 76 76 76 76 
76 76 76 76 76 76 76 51 51 76 76 76 76 76 76 76 
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76

全部一起(将着色器#version删除到130,因为Mesa在非核心上下文中不支持GL 3.0以外的任何内容):

// g++ main.cpp -lGLEW -lGL -lglfw
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cstdarg>

struct Program
{
    static GLuint Load( const char* shader, ... )
    {
        GLuint prog = glCreateProgram();
        va_list args;
        va_start( args, shader );
        while( shader )
        {
            const GLenum type = va_arg( args, GLenum );
            AttachShader( prog, type, shader );
            shader = va_arg( args, const char* );
        }
        va_end( args );
        glLinkProgram( prog );
        CheckStatus( prog );
        return prog;
    }

private:
    static void CheckStatus( GLuint obj )
    {
        GLint status = GL_FALSE;
        if( glIsShader(obj) ) glGetShaderiv( obj, GL_COMPILE_STATUS, &status );
        if( glIsProgram(obj) ) glGetProgramiv( obj, GL_LINK_STATUS, &status );
        if( status == GL_TRUE ) return;
        GLchar log[ 1 << 15 ] = { 0 };
        if( glIsShader(obj) ) glGetShaderInfoLog( obj, sizeof(log), NULL, log );
        if( glIsProgram(obj) ) glGetProgramInfoLog( obj, sizeof(log), NULL, log );
        std::cerr << log << std::endl;
        exit( EXIT_FAILURE );
    }

    static void AttachShader( GLuint program, GLenum type, const char* src )
    {
        GLuint shader = glCreateShader( type );
        glShaderSource( shader, 1, &src, NULL );
        glCompileShader( shader );
        CheckStatus( shader );
        glAttachShader( program, shader );
        glDeleteShader( shader );
    }
};

const GLchar *vertexShaderSource = R"glsl(#version 130
in vec2 position;
void main()
{
    gl_Position = vec4(position, 0.0, 1.0);
})glsl";

const GLchar *fragmentShaderSource = R"glsl(#version 130
out vec4 outColor;

void main()
{   
    outColor = vec4(0.2,0.,0.,0.);
})glsl";


const int DISPLAY_WIDTH = 16; 
const int DISPLAY_HEIGHT = 16; 


int main(int argc, char** argv)
{
    glfwInit();
    GLFWwindow* window = glfwCreateWindow(DISPLAY_WIDTH, DISPLAY_HEIGHT, "test", NULL, NULL);
    glfwMakeContextCurrent(window);

    GLenum res = glewInit();

    std::cout << "GLEW version: " << glewGetString(GLEW_VERSION) << std::endl;
    std::cout << "GLFW version: " << glfwGetVersionString() << std::endl;
    std::cout << "GL_VERSION  : " << glGetString( GL_VERSION ) << std::endl;
    std::cout << "GL_VENDOR   : " << glGetString( GL_VENDOR ) << std::endl;
    std::cout << "GL_RENDERER : " << glGetString( GL_RENDERER ) << std::endl;

    // triangle data (xy-position)
    float vertices[] =
    {
        0.0f, 0.5f, 
        0.5f, -0.5f, 
        -0.5f, -0.5f  
    };

    GLuint vbo;
    glGenBuffers(1, &vbo); 
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // enable vertex xy-position attribute 
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // compile, link and use shader program
    GLuint program = Program::Load
        (
        vertexShaderSource, GL_VERTEX_SHADER,
        fragmentShaderSource, GL_FRAGMENT_SHADER,
        NULL
        );
    glUseProgram( program );

    // rendering loop
    while (!glfwWindowShouldClose(window)) {

    glClearColor(0.3f, 1.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glDrawArrays(GL_TRIANGLES, 0, 3);
    glFlush();

    // read pixels from backbuffer 
    GLubyte data[DISPLAY_WIDTH * DISPLAY_HEIGHT];
    glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, GL_RED, GL_UNSIGNED_BYTE, data);

    int i = 0;
    for (int y = 0; y < DISPLAY_HEIGHT; y++)
    {
        for (int x = 0; x < DISPLAY_WIDTH; x++)
        {
            int a = data[i]; // implicit conversion of unsigned char to int
            std::cout << a << " ";;
            i++;
        }
        std::cout << std::endl;
    }
    std::cout << std::endl;

    glfwSwapBuffers(window);
    glfwPollEvents();
    }

    glfwTerminate(); 
    return 0;
}

答案 1 :(得分:1)

所以这是完整的故事。值得一提的是,正如Bahbar所说,我正在正确读出像素。但是,我对默认帧缓冲区的大小做了错误的假设,其尺寸设置为我创建的窗口的尺寸。

我在glfwCreateWindow的代码中指定的窗口大小为16x16。显然,我在原始问题中附加的图像具有更大的宽度。

GLFW documentation(请参阅glfwSetWindowSize)说明以下内容:&#34;窗口管理器可能会限制允许的大小。 GLFW不能也不应该超越这些限制&#34; 。事实证明,我的Windows 10机器上的宽度不能小于120像素。随后,检查data 120x16像素的阵列,而不是16x16像素,显示了&#39; 51&#39;的值。确实,记录正确。

然后,为了确保几何图形准确地绘制在我想要绘制的位置,我需要在绘制调用之前调用gViewport,以指定从标准化设备坐标(NDC)到标准化设备坐标(NDC)的所需转换我的窗口中的像素(或渲染缓冲区中的像素或纹理,如果我使用用户定义的帧缓冲对象在屏幕外渲染)。下面是genpfault提供的输出和略微修改的代码(他的代码是完全自包含的,包含我的示例所缺少的着色器构造位):

GLEW version: 2.0.0
GLFW version: 3.2.1 Win32 WGL EGL VisualC
GL_VERSION  : 4.5.0 NVIDIA 376.53
GL_VENDOR   : NVIDIA Corporation
GL_RENDERER : GeForce GTX 970/PCIe/SSE2

76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76
76 76 76 76 76 76 76 51 51 76 76 76 76 76 76 76
76 76 76 76 76 76 76 51 51 76 76 76 76 76 76 76
76 76 76 76 76 76 51 51 51 51 76 76 76 76 76 76
76 76 76 76 76 76 51 51 51 51 76 76 76 76 76 76
76 76 76 76 76 51 51 51 51 51 51 76 76 76 76 76
76 76 76 76 76 51 51 51 51 51 51 76 76 76 76 76
76 76 76 76 51 51 51 51 51 51 51 51 76 76 76 76
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76
76 76 76 76 76 76 76 76 76 76 76 76 76 76 76 76

代码本身包含glViewport调用(在渲染循环之前)和稍微修改的打印循环,以便将输出与实际图像匹配:

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cstdarg>

struct Program
{
    static GLuint Load(const char* shader, ...)
    {
        GLuint prog = glCreateProgram();
        va_list args;
        va_start(args, shader);
        while (shader)
        {
            const GLenum type = va_arg(args, GLenum);
            AttachShader(prog, type, shader);
            shader = va_arg(args, const char*);
        }
        va_end(args);
        glLinkProgram(prog);
        CheckStatus(prog);
        return prog;
    }

private:
    static void CheckStatus(GLuint obj)
    {
        GLint status = GL_FALSE;
        if (glIsShader(obj)) glGetShaderiv(obj, GL_COMPILE_STATUS, &status);
        if (glIsProgram(obj)) glGetProgramiv(obj, GL_LINK_STATUS, &status);
        if (status == GL_TRUE) return;
        GLchar log[1 << 15] = { 0 };
        if (glIsShader(obj)) glGetShaderInfoLog(obj, sizeof(log), NULL, log);
        if (glIsProgram(obj)) glGetProgramInfoLog(obj, sizeof(log), NULL, log);
        std::cerr << log << std::endl;
        exit(EXIT_FAILURE);
    }

    static void AttachShader(GLuint program, GLenum type, const char* src)
    {
        GLuint shader = glCreateShader(type);
        glShaderSource(shader, 1, &src, NULL);
        glCompileShader(shader);
        CheckStatus(shader);
        glAttachShader(program, shader);
        glDeleteShader(shader);
    }
};

const GLchar *vertexShaderSource = R"glsl(#version 130
    in vec2 position;
    void main()
    {
        gl_Position = vec4(position, 0.0, 1.0);
    })glsl";

const GLchar *fragmentShaderSource = R"glsl(#version 130
    out vec4 outColor;

    void main()
    {   
        outColor = vec4(0.2,0.,0.,0.);
    })glsl";


const int DISPLAY_WIDTH = 16;
const int DISPLAY_HEIGHT = 16;


int main(int argc, char** argv)
{
    glfwInit();
    GLFWwindow* window = glfwCreateWindow(DISPLAY_WIDTH, DISPLAY_HEIGHT, "test", NULL, NULL);
    glfwMakeContextCurrent(window);

    GLenum res = glewInit();

    std::cout << "GLEW version: " << glewGetString(GLEW_VERSION) << std::endl;
    std::cout << "GLFW version: " << glfwGetVersionString() << std::endl;
    std::cout << "GL_VERSION  : " << glGetString(GL_VERSION) << std::endl;
    std::cout << "GL_VENDOR   : " << glGetString(GL_VENDOR) << std::endl;
    std::cout << "GL_RENDERER : " << glGetString(GL_RENDERER) << std::endl << std::endl;

    // triangle data (xy-position)
    float vertices[] =
    {
        0.0f, 0.5f,
        0.5f, -0.5f,
        -0.5f, -0.5f
    };

    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // enable vertex xy-position attribute 
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // compile, link and use shader program
    GLuint program = Program::Load
        (
        vertexShaderSource, GL_VERTEX_SHADER,
        fragmentShaderSource, GL_FRAGMENT_SHADER,
        NULL
        );
    glUseProgram(program);

    glViewport(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
    // rendering loop
    while (!glfwWindowShouldClose(window)) {

        glClearColor(0.3f, 1.0f, 1.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glDrawArrays(GL_TRIANGLES, 0, 3);
        glFlush();

        // read pixels from backbuffer 
        GLubyte data[DISPLAY_WIDTH * DISPLAY_HEIGHT];
        glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, GL_RED, GL_UNSIGNED_BYTE, data);

        for (int y = DISPLAY_HEIGHT-1; y >= 0; y--)
        {
            for (int x = 0; x < DISPLAY_WIDTH; x++)
            {
                std::cout << (int)data[y*DISPLAY_HEIGHT + x] << " "; // implicit conversion of unsigned char to int
            }
            std::cout << std::endl;
        }
        std::getchar();
        std::cout << std::endl;

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

最后,我无法使用stackoverflow的功能上传图片,因此这里有一个链接指向感兴趣的人:https://imgur.com/6bfLhfW

如果有人能告诉我这是否可以接受作为答案,我只需按下按钮即可。或者也许其他人可以提供更有意义的答案。谢谢输入人员!