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

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

问题原因

  1. 过渡函数错误清除帧缓冲:transition函数中调用了glClear(GL_COLOR_BUFFER_BIT),会把主循环中已经渲染的rect2内容清除,导致普通矩形消失。
  2. 纹理单元绑定不匹配:
    • render函数激活了GL_TEXTURE2,但normal.fs中的texture1默认绑定GL_TEXTURE0,且没有设置uniform指定纹理单元,导致着色器无法找到正确的纹理。
    • 构造函数中错误地把texture(实际对应着色器的texture1)的uniform设为1,与后续绑定的GL_TEXTURE0不匹配。
  3. 重复生成纹理ID:每次调用render和transition都生成新的纹理ID,既造成资源泄漏,又导致后续渲染可能绑定到错误的纹理对象。
  4. 纹理格式不匹配:过渡函数中创建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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 22:17:02