You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Framebuffer纹理无法传入着色器且无法显示的问题排查

OpenGL FBO离屏渲染纹理不显示问题排查

我正在编写一个通过着色器合成两张图片并保存到本地的程序,直接输出到视口会裁剪屏幕外像素,且希望实现无窗口化,因此尝试使用Framebuffer Object(FBO)进行离屏渲染。编写原型项目测试单张纹理通过FBO显示到屏幕时,结果仅显示背景色;修改片段着色器的颜色值能正常生效,但FBO的纹理完全无法传入着色器。

代码片段

main.cpp

const char* img1_path = "C:/..path../img_00001.jpg";
const char* img2_path = "C:/..path../img_00002.jpg";

GLfloat square_vertices[] =
{ //     COORDINATES     /        COLORS      /   TexCoord  //
    -1.0f, -1.0f, 0.0f,     1.0f, 0.0f, 0.0f,   0.0f, 0.0f, // Lower left corner
    -1.0f,  1.0f, 0.0f,     0.0f, 1.0f, 0.0f,   0.0f, 1.0f, // Upper left corner
     1.0f,  1.0f, 0.0f,     0.0f, 0.0f, 1.0f,   1.0f, 1.0f, // Upper right corner
     1.0f, -1.0f, 0.0f,     1.0f, 1.0f, 1.0f,   1.0f, 0.0f  // Lower right corner
};

GLuint square_indices[] =
{
    0, 2, 1, // Upper triangle
    0, 3, 2 // Lower triangle
};

// for FBO
static const GLfloat g_quad_vertex_buffer_data[] = {
    -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,
};


#ifdef __cplusplus
extern "C" {
#endif
    __declspec(dllexport) DWORD NvOptimusEnablement = 1;
    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;

#ifdef __cplusplus
}
#endif

#define WIDTH 1280
#define HEIGHT 720

int main() {

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // usiamo il core profile

    // create glfwwindow
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "test", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    gladLoadGL();
    glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);

    // Dark blue background
    glClearColor(0.0f, 0.0f, 0.4f, 0.0f);

    // Enable depth test
    glEnable(GL_DEPTH_TEST);
    // Accept fragment if it closer to the camera than the former one
    glDepthFunc(GL_LESS);
    // Cull triangles which normal is not towards the camera
    glEnable(GL_CULL_FACE);


    // VAO init
    GLuint VAO1;
    glGenVertexArrays(1, &VAO1);
    glBindVertexArray(VAO1);

    // my custom texture (1920x1080)
    int widthImg, heightImg, numColCh;
    stbi_set_flip_vertically_on_load(true);
    unsigned char* bytes = stbi_load(img1_path, &widthImg, &heightImg, &numColCh, 0);

    // shader reading, compiling and linking
    Shader shaderProgram(vert_path, frag_path);

    // Texture setup
    GLuint Texture;
    glGenTextures(1, &Texture);
    glBindTexture(GL_TEXTURE_2D, Texture);
    glPixelStorei(GL_UNPACK_ALIGNMENT,1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, WIDTH, HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, bytes);

    // shader uniform linking
    GLuint TextureID = glGetUniformLocation(shaderProgram.ID, "tex0");

    // VBO
    GLuint VBO1;
    glGenBuffers(1, &VBO1);
    glBindBuffer(GL_ARRAY_BUFFER, VBO1);
    glBufferData(GL_ARRAY_BUFFER, sizeof(square_vertices), square_vertices, GL_STATIC_DRAW);

    // EBO
    GLuint EBO1;
    glGenBuffers(1, &EBO1);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO1);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(square_indices), square_indices, GL_STATIC_DRAW);


    // FBO
    // framebuffer
    GLuint FramebufferName = 0;
    glGenFramebuffers(1, &FramebufferName);
    glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

    GLuint renderedTexture;
    glGenTextures(1, &renderedTexture);
    glBindTexture(GL_TEXTURE_2D, renderedTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, WIDTH, HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    // depth buffer
    GLuint depthrenderbuffer;
    glGenRenderbuffers(1, &depthrenderbuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, depthrenderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, WIDTH, HEIGHT);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthrenderbuffer);

    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);

    // Set list of draw buffers.
    GLenum DrawBuffers[1] = { GL_COLOR_ATTACHMENT0 };
    glDrawBuffers(1, DrawBuffers);

    // Error checking framebuffer
    auto fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (fboStatus != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "Framebuffer error: " << fboStatus << std::endl;

    // FBO vertices
    GLuint quad_vertexbuffer;
    glGenBuffers(1, &quad_vertexbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data), g_quad_vertex_buffer_data, GL_STATIC_DRAW);

    // Create and compile our GLSL program from the shaders
    Shader quad_program(FB_vert, FB_frag);
    GLuint texID = glGetUniformLocation(quad_program.ID, "FBtex");


    // LOOP
    while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS && !glfwWindowShouldClose(window)) {

        // Render to the screen
        glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
        glViewport(0, 0, WIDTH, HEIGHT);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        // non-FBO shader
        glUseProgram(shaderProgram.ID);

        // Bind our texture in Texture Unit 0
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, Texture);
        glUniform1i(TextureID, 0);

        // 1rst attribute buffer : vertices
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, VBO1);
        glVertexAttribPointer(
            0,                  // attribute
            3,                  // size
            GL_FLOAT,           // type
            GL_FALSE,           // normalized?
            0,                  // stride
            (void*)0            // array buffer offset
        );

        // bind EBO
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO1);

        // drawelements
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glDisableVertexAttribArray(0);

        // Render to the screen
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glViewport(0, 0, WIDTH, HEIGHT);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // FBO shader
        glUseProgram(quad_program.ID);

        // Bind our texture in Texture Unit 0
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, renderedTexture);
        glGenerateMipmap(GL_TEXTURE_2D);
        glUniform1i(texID, 0);

        // 1rst attribute buffer : vertices
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
        glVertexAttribPointer(
            0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
            3,                  // size
            GL_FLOAT,           // type
            GL_FALSE,           // normalized?
            0,                  // stride
            (void*)0            // array buffer offset
        );

        // Draw the triangles
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glDisableVertexAttribArray(0);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteBuffers(1, &VAO1);
    glDeleteBuffers(1, &VBO1);
    glDeleteBuffers(1, &EBO1);
    shaderProgram.Delete();
    quad_program.Delete();
    glDeleteFramebuffers(1, &FramebufferName);
    glDeleteTextures(1, &renderedTexture);
    glDeleteRenderbuffers(1, &depthrenderbuffer);
    glDeleteBuffers(1, &quad_vertexbuffer);

    glfwDestroyWindow(window);
    glfwTerminate();

    return 0;
}

FBO.vert (passthrough)

#version 460 core

layout (location = 0) in vec2 inPos;
layout (location = 1) in vec2 inTexCoords;

out vec2 texCoords;

void main()
{
    gl_Position = vec4(inPos.x, inPos.y, 0.0, 1.0); 
    texCoords = inTexCoords;
}

FBO.frag

#version 460 core

in vec2 texCoords;
out vec3 color;

uniform sampler2D FBtex;

void main()
{
    vec3 temp = texture(FBtex, texCoords).xyz;
    color = temp;

    // this works and shades the blue background color
    //color = vec3(temp.x + 0.3f, temp.y + 0.3f, temp.z + 0.3f);
}

问题排查与修复

1. FBO渲染阶段的顶点属性配置错误

square_vertices数组每个顶点包含3个坐标+3个颜色+2个纹理坐标(共8个float),但你设置glVertexAttribPointer时stride设为0,OpenGL会默认每个属性占3个float,导致纹理坐标完全无法正确传递到着色器,渲染到FBO的内容本身就是错误的。

修复方案:
绑定VBO1后,分别配置所有顶点属性并设置正确的步长:

// 坐标属性(对应shader的location 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

// 颜色属性(对应shader的location 1,若shader用到则开启)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

// 纹理坐标属性(对应shader的location 2)
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

同时确保shaderProgram的顶点着色器正确声明输入属性:

#version 460 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec2 texCoord;

void main() {
    gl_Position = vec4(aPos, 1.0);
    texCoord = aTexCoord;
}

2. FBO显示阶段的顶点数据与着色器不匹配

FBO.vert声明了位置(vec2 inPos)和纹理坐标(vec2 inTexCoords)两个输入,但你传入的g_quad_vertex_buffer_data只有顶点位置,完全没有纹理坐标数据,导致片段着色器的texCoords未定义,采样纹理时无法获取正确像素。

修复方案:
修改顶点数据,加入纹理坐标:

static const GLfloat g_quad_vertex_buffer_data[] = {
    // 位置          // 纹理坐标
    -1.0f, -1.0f, 0.0f,  0.0f, 0.0f,
     1.0f, -1.0f, 0.0f,  1.0f, 0.0f,
    -1.0f,  1.0f, 0.0f,  0.0f, 1.0f,
    -1.0f,  1.0f, 0.0f,  0.0f, 1.0f,
     1.0f, -1.0f, 0.0f,  1.0f, 0.0f,
     1.0f,  1.0f, 0.0f,  1.0f, 1.0f,
};

然后配置FBO四边形的顶点属性:

// 位置属性(location 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

// 纹理坐标属性(location 1)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

3. 纹理加载尺寸不匹配

你加载的图片是1920x1080,但创建纹理时硬编码为WIDTH和HEIGHT(1280x720),glTexImage2D的目标尺寸与输入数据尺寸不一致,导致纹理数据加载错误。

修复方案:
使用实际加载的图片尺寸创建纹理:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, widthImg, heightImg, 0, GL_RGB, GL_UNSIGNED_BYTE, bytes);

若需要缩放到FBO尺寸,可通过设置纹理过滤参数或渲染时的投影矩阵适配。

4. 不必要的渲染状态干扰

当前是纯2D渲染,开启GL_DEPTH_TEST和GL_CULL_FACE可能导致渲染被意外剔除(比如四边形顶点顺序导致背面被剔除),可先关闭这两个选项:

// 注释掉以下两行
// glEnable(GL_DEPTH_TEST);
// glEnable(GL_CULL_FACE);

附加问题解答

  1. 仅使用纹理时,是否需要深度缓冲区才能使用Framebuffer?若需要,是否还必须实现相机和3D模型?
    不需要深度缓冲区。深度缓冲区用于处理3D场景的遮挡关系,纯2D渲染(如绘制纹理四边形)不需要深度测试,FBO仅绑定颜色附件即可。相机和3D模型也不是必须的,纯2D渲染直接使用NDC坐标(或简单的正交投影)就能完成。
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 23:18:21