【问题标题】:glReadPixels always returns the same value in glClearColorglReadPixels 总是在 glClearColor 中返回相同的值
【发布时间】:2018-03-18 13:09:15
【问题描述】:

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

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

以下是我用来生成上图的完整代码(着色器构造除外):

#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;
}

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

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

GLubyte* data = new GLubyte[DISPLAY_WIDTH * DISPLAY_HEIGHT];
glReadPixels(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, GL_RED, GL_UNSIGNED_BYTE, data);

为了便于检查从帧缓冲区中读出的像素值,我只提取红色通道,因此容纳像素数据所需的数据大小为DISPLAY_WIDTH * DISPLAY_HEIGHT。此外,这意味着我打印出来的值应该是背景颜色的“76”和几何图形的“51”。

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

如果你能告诉我我在这里缺少什么,我将不胜感激。

【问题讨论】:

  • 如果您获得了背景颜色而不是对象的颜色,似乎glReadPixels 正在“清除颜色”和“交换缓冲区”调用之间读取。或者它在第一次交换后从front buffer 读取而不是默认的back buffer,您以某种方式丢失了。
  • 您所做的看起来不错。您确定您只看到背景像素吗?从你的图片来看,在第一个几何图形之前会有很多背景像素......
  • 在minimal reproducible example 中编辑。随意使用this 作为基础。
  • @Rabbid76:glFlush 不是必需的。 glReadPixels 引入了一个同步点,在进行像素读出之前,它隐式地完成了导致调用 glReadPixels 的所有操作。很可能 OP 没有指定从哪个缓冲区读取,并且意外地从错误的缓冲区中读取。
  • @Ripi2 是的,我在清除颜色和交换缓冲区之间进行读取,但在我绘制几何图形之后。所以我假设在我读取我的后备缓冲区时,它已经包含几何生成的像素。

标签: c++ opengl


【解决方案1】:

在我的 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;
}

【讨论】:

  • 我终于知道发生了什么。我用glfwCreateWindow 指定的窗口大小是16x16。显然,我附加的图像具有更大的宽度。 GLFW documentation(参见 glfwSetWindowSize)声明如下:“窗口管理器可能会限制允许的大小。GLFW 不能也不应该覆盖这些限制”。好吧,事实证明我不能将宽度设置为小于 120 像素。话虽如此,检查data 数组发现“51”的值确实被正确记录了。
【解决方案2】:

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

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

GLFW documentation(参见glfwSetWindowSize)声明如下:“窗口管理器可能会限制允许的大小。GLFW 不能也不应该覆盖这些限制”。事实证明,我不能在我的 Windows 10 机器上设置小于 120 像素的宽度。随后,检查data 120x16 像素而不是 16x16 像素的数组,发现“51”的值确实被正确记录了。

然后,为了确保将几何图形准确地绘制在我想要绘制的位置,我需要在绘制调用之前调用gViewport,以便指定从标准化设备坐标 (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 的功能上传图片,所以这里有一个 imgur 链接供感兴趣的人使用:https://imgur.com/6bfLhfW。

如果有人可以让我知道这是否可以被接受为答案,我会继续点击按钮。或者也许其他人可以给出更有意义的答案。谢谢你们的意见!

【讨论】:

    猜你喜欢
    • 2019-07-16
    • 1970-01-01
    • 1970-01-01
    • 2019-10-22
    • 2019-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多