OpenGL纹理类混合渲染异常问题排查与修复请求
OpenGL纹理过渡与渲染异常问题排查与修复
问题描述
我在OpenGL中实现了一个Texture类,可接收缓冲区渲染2D纹理,单独使用时能正常渲染任意数量纹理。但添加transition函数实现双纹理混合后,同时渲染过渡纹理和普通纹理时出现异常:
- 调用
render的普通矩形消失 - 调用
transition的过渡矩形错误使用了普通矩形的纹理
预期效果:
- 调用
render的矩形显示data3 - 调用
transition的矩形混合data1和data2进行过渡
相关代码
main.cpp
class Texture{ private: typedef struct transtex_s { uint8_t* buff; int w, h; } transtex; Shader ourShader; unsigned int texture; unsigned int texture2; unsigned int VBO, VAO, EBO; public: transtex tex2; Texture(float pos, bool is_trans, std::string shader): ourShader("camera.vs", shader.c_str()) { float vertices[] = { // positions // colors // texture coords pos, pos, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right pos, -pos, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right -pos, -pos, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left -pos, pos, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left }; unsigned int indices[] = { 0, 1, 3, // first triangle 1, 2, 3 // second triangle }; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glGenBuffers(1, &EBO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // position attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // color attribute glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // texture coord attribute glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glEnableVertexAttribArray(2); ourShader.use(); glUniform1i(glGetUniformLocation(ourShader.ID, "texture"), 1); if(is_trans) { ourShader.setInt("texture2", 1); } } void transition(int width1,int height1, uint8_t* data1, float progress) { ourShader.use(); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width1, height1, 0, GL_RGB, GL_UNSIGNED_BYTE, data1); glGenerateMipmap(GL_TEXTURE_2D); glGenTextures(1, &texture2); glBindTexture(GL_TEXTURE_2D, texture2); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex2.w, tex2.h, 0, GL_RGB, GL_UNSIGNED_BYTE, tex2.buff); glGenerateMipmap(GL_TEXTURE_2D); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2); glUniform1f(glGetUniformLocation(ourShader.ID, "progress"), progress); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); } void render(int w, int h, uint8_t* buffer) { ourShader.use(); glEnable(GL_BLEND); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); glGenerateMipmap(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE2); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); } ~Texture() { glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO); } }; int main() { ... // init glfw, opengl and glad. // Read images as buffers using stbi image. int width1, height1, nrChannels1; stbi_set_flip_vertically_on_load(true); unsigned char *data1 = stbi_load("/home/tb/Desktop/Work/CameraTest/bin/ex2.jpeg", &width1, &height1, &nrChannels1, 0); int width2, height2, nrChannels2; unsigned char *data2 = stbi_load("/home/tb/Desktop/Work/CameraTest/bin/ex1.jpeg", &width2, &height2, &nrChannels2, 0); int width3, height3, nrChannels3; unsigned char *data3 = stbi_load("/home/tb/Desktop/Work/CameraTest/bin/WATERMARK.png", &width3, &height3, &nrChannels3, 0); Texture rect1 = Texture(0.5f, true, "camera.fs"); Texture rect2 = Texture(1.0f, false, "normal.fs"); float progress = 0.0f; while (!glfwWindowShouldClose(window)) { rect1.tex2.buff = data2; rect1.tex2.w = width2; rect1.tex2.h = height2; glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); rect2.render(width3, height3, data3); rect1.transition(width1, height1, data1, progress); progress += 0.01f; if(progress >= 1.0f) progress = 0.0f; glfwSwapBuffers(window); } stbi_image_free(data1); stbi_image_free(data2); return 0; }
片段着色器
camera.fs
#version 330 core out vec4 FragColor; in vec3 ourColor; in vec2 TexCoord; uniform sampler2D texture1; uniform sampler2D texture2; uniform float progress; void main() { FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), progress); }
normal.fs
#version 330 core in vec2 TexCoord; out vec4 FragColor; uniform sampler2D texture1; void main() { vec4 texColor = texture(texture1, TexCoord); FragColor = texColor; }
问题原因
- 过渡函数错误清除帧缓冲:
transition函数中调用了glClear(GL_COLOR_BUFFER_BIT),会把主循环中已经渲染的rect2内容清除,导致普通矩形消失。 - 纹理单元绑定不匹配:
render函数激活了GL_TEXTURE2,但normal.fs中的texture1默认绑定GL_TEXTURE0,且没有设置uniform指定纹理单元,导致着色器无法找到正确的纹理。- 构造函数中错误地把
texture(实际对应着色器的texture1)的uniform设为1,与后续绑定的GL_TEXTURE0不匹配。
- 重复生成纹理ID:每次调用
render和transition都生成新的纹理ID,既造成资源泄漏,又导致后续渲染可能绑定到错误的纹理对象。 - 纹理格式不匹配:过渡函数中创建texture2时,内部格式设为GL_RGBA,但外部格式是GL_RGB,导致纹理数据解析错误。
修复方案
1. 移除过渡函数中的glClear调用
主循环中已经统一执行了帧缓冲清除,删除transition函数中的以下代码:
glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT);
2. 修正纹理单元绑定逻辑
- 修改构造函数中的uniform设置,确保
texture1对应GL_TEXTURE0:
ourShader.use(); // 修正变量名:着色器中是texture1,不是texture glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); if(is_trans) { ourShader.setInt("texture2", 1); }
- 修改
render函数中的纹理单元激活逻辑:
void render(int w, int h, uint8_t* buffer) { ourShader.use(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 激活GL_TEXTURE0,对应着色器的texture1 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); glGenerateMipmap(GL_TEXTURE_2D); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); }
3. 避免重复生成纹理ID
在构造函数中生成纹理ID,而不是每次渲染时生成:
Texture(float pos, bool is_trans, std::string shader): ourShader("camera.vs", shader.c_str()) { // ... 原有VAO/VBO/EBO初始化代码 ... // 生成纹理ID glGenTextures(1, &texture); if(is_trans) { glGenTextures(1, &texture2); } ourShader.use(); glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); if(is_trans) { ourShader.setInt("texture2", 1); } }
同时修改transition函数,直接使用已生成的纹理ID更新内容:
void transition(int width1,int height1, uint8_t* data1, float progress) { ourShader.use(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width1, height1, 0, GL_RGB, GL_UNSIGNED_BYTE, data1); glGenerateMipmap(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2); // 修正纹理格式:内部格式与外部格式保持一致(假设tex2.buff是RGB格式) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex2.w, tex2.h, 0, GL_RGB, GL_UNSIGNED_BYTE, tex2.buff); glGenerateMipmap(GL_TEXTURE_2D); glUniform1f(glGetUniformLocation(ourShader.ID, "progress"), progress); glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); }
4. 优化渲染状态设置
将glEnable(GL_BLEND)移到主循环初始化或构造函数中,避免每次渲染重复调用:
// 在Texture构造函数中添加 glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
然后删除render函数中的glEnable(GL_BLEND)和glBlendFunc调用。
5. 析构函数中添加纹理资源清理
避免资源泄漏,在析构函数中删除生成的纹理:
~Texture() { glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO); glDeleteTextures(1, &texture); if(texture2 != 0) { glDeleteTextures(1, &texture2); } }
最终效果
修复后,主循环中先渲染rect2(显示data3),再渲染rect1(混合data1和data2进行过渡),两者都能正常显示,过渡效果符合预期。
内容的提问来源于stack exchange,提问作者Turgut
相关产品推荐
相关产品推荐

