使用帧缓冲区(纹理空白)向纹理发出绘图

Posted

技术标签:

【中文标题】使用帧缓冲区(纹理空白)向纹理发出绘图【英文标题】:Issue drawing to textures with framebuffer (texture blank) 【发布时间】:2016-04-10 02:39:22 【问题描述】:

首先我会说我对这些主题相当陌生,并且最近几天一直在给自己上一个学校项目的速成课程。很抱歉,这很长,但由于我不确定问题出在哪里,我想我会尽量做到彻底。

我正在尝试使用帧缓冲区来绘制一些 RGBA 纹理,这些纹理稍后将用于其他绘制(我使用纹理中的值作为矢量来绘制粒子——纹理存储诸如它们的位置和速度)。

但是,据我所知,绘制后的纹理是空白的。纹理中的采样值返回 (0, 0, 0, 1)。我后来的绘图程序似乎也证实了这一点,因为它似乎所有粒子都在原点重叠绘制(这个观察是基于我的混合函数和一些硬编码的测试颜色值) .

我正在使用带有 SDL2 和 glew 的 OpenGL 4。

我会尝试有条不紊地发布所有相关内容:

帧缓冲区类创建帧缓冲区并附加 (3) 个纹理。 它有:

GLuint buffer_id_ 是帧缓冲区的 id,初始化为 0,unsigned int width, height,这是纹理的宽度和高度,以像素为单位,numTextures,这是为帧缓冲区创建的纹理数量(同样,在这种情况 3) 和 std::vector<GLuint> textures_ 用于与帧缓冲区关联的所有纹理。

bool Framebuffer::init() 
    assert( numTextures <= GL_MAX_COLOR_ATTACHMENTS );
    // Get the buffer id.
    glGenFramebuffers( 1, &buffer_id_ );
    glBindFramebuffer( GL_FRAMEBUFFER, buffer_id_ );
    // Generate the textures.
    for (unsigned int i = 0; i < numTextures; ++i) 
        GLuint tex;
        glGenTextures( 1, &tex );
        glBindTexture(GL_TEXTURE_2D, tex);
        // Give empty image to OpenGL.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_FLOAT, 0);
        // Use GL_NEAREST, since we don't want any kind of averaging across values:
        // we just want one pixel to represent a particle's data.
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        // This probably isn't necessary, but we don't want to have UV coords past the image edges.
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        textures_.push_back(tex);
    
    glBindTexture(GL_TEXTURE_2D, 0);
    // Bind our textures to the framebuffer.
    GLenum drawBuffers[GL_MAX_COLOR_ATTACHMENTS];
    for (unsigned int i = 0; i < numTextures; ++i) 
        GLenum attach = GL_COLOR_ATTACHMENT0 + i;
        glFramebufferTexture(GL_FRAMEBUFFER, attach, textures_[i], 0);
        drawBuffers[i] = attach;
    
    glDrawBuffers( numTextures, drawBuffers );

    if ( glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE ) 
        std::cerr << "Error: Failed to create framebuffer." << std::endl;
        return false;
    
    glBindFramebuffer( GL_FRAMEBUFFER, 0 );
    return true;

粒子系统制作了两个匹配的帧缓冲区,因此一个可以用作着色器程序的输入,另一个用作输出。

bool AmbientParticleSystem::initFramebuffers() 
    framebuffers_[0] = new Framebuffer(particle_texture_width_, particle_texture_height_, NUM_TEXTURES_PER_FRAMEBUFFER);
    framebuffers_[1] = new Framebuffer(particle_texture_width_, particle_texture_height_, NUM_TEXTURES_PER_FRAMEBUFFER);

    return framebuffers_[0]->init() && framebuffers_[1]->init();

然后我尝试将初始状态绘制到第一个帧缓冲区:

void AmbientParticleSystem::initParticleDrawing() 
    glBindFramebuffer( GL_FRAMEBUFFER, framebuffers_[0]->getBufferID() );
    // Store the previous viewport.
    GLint prevViewport[4];
    glGetIntegerv( GL_VIEWPORT, prevViewport );
    glViewport( 0, 0, particle_texture_width_, particle_texture_height_ );
    glClear( GL_COLOR_BUFFER_BIT );
    glDisable( GL_DEPTH_TEST );
    glBlendFunc( GL_ONE, GL_ZERO);

    init_shader_->use(); // This sets glUseProgram to the init shader.
    GLint attrib = init_variable_ids_[constants::init::Variables::IN_VERTEX_POS]->id;
    glEnableVertexAttribArray( attrib );
    glBindBuffer( GL_ARRAY_BUFFER, full_size_quad_->getBufferID() );
    glVertexAttribPointer( attrib, full_size_quad_->vertexSize,
                GL_FLOAT,   // type
                GL_FALSE,   // normalized?
                0,          // stride
                (void*)0 ); // Array buffer offset
    glDrawArrays( GL_TRIANGLES, 0, full_size_quad_->numVertices );

// Here I'm just printing some sample values to confirm it is blank/black.
    glBindTexture( GL_TEXTURE_2D, framebuffers_[0]->getTexture(0) );
    GLfloat *pixels = new GLfloat[particle_texture_width_ * particle_texture_height_ * 4];
    glGetTexImage( GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, pixels);

    std::cout << "some pixel entries:\n";
    std::cout << "pixel0:     " << pixels[0] << "  " << pixels[1] << "  " << pixels[2] << "  " << pixels[3] << std::endl;
    std::cout << "pixel10:    " << pixels[40] << "  " << pixels[41] << "  " << pixels[42] << "  " << pixels[43] << std::endl;
    std::cout << "pixel100:   " << pixels[400] << "  " << pixels[401] << "  " << pixels[402] << "  " << pixels[403] << std::endl;
    std::cout << "pixel10000: " << pixels[40000] << "  " << pixels[40001] << "  " << pixels[40002] << "  " << pixels[40003] << std::endl;
// They all print 0, 0, 0, 1
    delete pixels;

    glBindBuffer( GL_ARRAY_BUFFER, 0 );
    glDisableVertexAttribArray( attrib );
    init_shader_->stopUsing();
    glBindFramebuffer( GL_FRAMEBUFFER, 0 );
    // Return the viewport to its previous state.
    glViewport(prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3] );

您也可以在这里看到我尝试获取一些像素值的地方,它们都返回 (0, 0, 0, 1)。

此处使用的 full_size_quad_ 定义为:

// Create quad for textures to draw onto.
static const GLfloat quad_array[] = 
    -1.0f,  -1.0f,  0.0f,   
     1.0f,   1.0f,  0.0f,
    -1.0f,   1.0f,  0.0f,
    -1.0f,  -1.0f,  0.0f,
     1.0f,  -1.0f,  0.0f,
     1.0f,   1.0f,  0.0f,
;
std::vector<GLfloat> quad(quad_array, quad_array + 18);
full_size_quad_ = new VertexBuffer(3, 6, quad);

VertexBuffer 是我自己的类,我不认为需要在这里展示。它只是 glGens 和 glBinds 顶点数组和缓冲区。

这里使用的着色器对于顶点着色器有这个:

#version 400 core

in vec3 inVertexPos;

void main() 
    gl_Position = vec4(inVertexPos, 1.0);

片段着色器:

#version 400 core

layout(location = 0) out vec4 position;
layout(location = 1) out vec4 velocity;
layout(location = 2) out vec4 other;

uniform vec2 uResolution;

// http://***.com/questions/4200224/random-noise-functions-for-glsl
float rand(vec2 seed) 
  return fract(sin(dot(seed.xy,vec2(12.9898,78.233))) * 43758.5453);


void main() 
  vec2 uv = gl_FragCoord.xy/uResolution.xy;

  vec3 pos = vec3(uv.x, uv.y, rand(uv));
  vec3 vel = vec3(-2.0, 0.0, 0.0);
  vec4 col = vec4(1.0, 0.3, 0.1, 0.5);

  position = vec4(pos, 1.0);
  velocity = vec4(vel, 1.0);
  other = col;

uResolution 是纹理的宽度和高度,以像素为单位,设置:

init_shader_->use();
glUniform2f( init_uniform_ids_[constants::init::Uniforms::U_RESOLUTION]->id, particle_texture_width_, particle_texture_height_ );

出于好奇,我尝试将 position = vec4(pos, 1.0); 更改为不同的值,但我的像素打印仍然给出 0 0 0 1。

我已经调试了大约 20 到 30 个小时,并在这里查找了十几个不同的教程和其他问题,但在过去的几个小时里,我觉得我没有取得任何进展。

这里有什么突出的原因是为什么纹理看起来是空白/黑色,或者还有什么需要解决的问题?我正在使用这个项目来了解着色器,所以我对这一切都很陌生。任何帮助将不胜感激。

【问题讨论】:

使用常量GL_MAX_COLOR_ATTACHMENTS来控制缓冲区的分配和循环的迭代次数是错误。这是您查询以找出实际数字的 ID,它的基本形式不是任何类型的限制,它将是一个非常大的数字,您可能会在尝试分配和运行循环时遇到运行时问题多次 :) 先查询值。 @AndonM.Coleman 感谢您的收获。现在我用glGetIntegerv( GL_MAX_COLOR_ATTACHMENTS, &amp;maxAttach); 查询它。我想我很幸运,我只得到了 3 个纹理,所以到目前为止我从来没有遇到过任何问题。 我认为没有变化?想到的第二个问题是您已经绑定了一个用于读取的纹理,该纹理已经附加到您的活动帧缓冲区以进行写入。在纹理当前作为帧缓冲区的图像附件处于活动状态时绑定纹理不太可能有任何好处,您可能会得到不连贯的值。要从当前绑定的 FBO 读回像素数据,您需要使用 glReadPixels (...) 并将 readbuffer 设置为适当的颜色附件。 @AndonM.Coleman 不,没有变化。如果我先取消绑定纹理,glGetTexImage 是否有效?就像现在一样,读取像素纯粹是为了了解我的程序为什么不能正常工作,所以它并不是非常重要。我会尝试一些不同的事情,看看我是否会得到任何不同的结果。 :) 编辑:如果我取消绑定 framebuffer,对不起。 更新:在解绑帧缓冲区后使用 glGetTexImage 和使用 glReadPixels 都返回相同的结果 0、0、0、1,所以看起来这些可能是实际值。 【参考方案1】:

通过完全重写代码解决了这个问题。

我仍然不能 100% 确定问题出在哪里,但我认为我可能有一些不匹配的启用/禁用调用。

【讨论】:

以上是关于使用帧缓冲区(纹理空白)向纹理发出绘图的主要内容,如果未能解决你的问题,请参考以下文章

OpenGLES 2.0 帧缓冲区可以同时绑定到纹理和渲染缓冲区吗?

JOGL 和帧缓冲区渲染到纹理的问题:无效帧缓冲区操作错误

尝试使用帧缓冲区渲染到纹理总是会导致白色纹理

使用帧缓冲区缩放纹理

OpenGL 帧缓冲区 - 渲染到纹理创建带有主帧缓冲区内容的纹理

readPixels 函数返回未修改的纹理