Framebuffer纹理无法传入着色器且无法显示的问题排查
我正在编写一个通过着色器合成两张图片并保存到本地的程序,直接输出到视口会裁剪屏幕外像素,且希望实现无窗口化,因此尝试使用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);
附加问题解答
- 仅使用纹理时,是否需要深度缓冲区才能使用Framebuffer?若需要,是否还必须实现相机和3D模型?
不需要深度缓冲区。深度缓冲区用于处理3D场景的遮挡关系,纯2D渲染(如绘制纹理四边形)不需要深度测试,FBO仅绑定颜色附件即可。相机和3D模型也不是必须的,纯2D渲染直接使用NDC坐标(或简单的正交投影)就能完成。

