在 OpenGL(C++) 中进行两次高斯模糊时出现意外结果(第一遍消失)

Unexpected result (first pass disappeared) when doing two-pass Gaussian blur in OpenGL(C++)

我正在尝试使用 OpenGL 实现背景模糊效果。

这是我的想法:

  1. 将所有背景元素绘制到 colorFBO
  2. colorFBO画入pingpongFBO[0]和pingpongFBO1
  3. 使用pingpongFBO[0]作为贴图,绘制hori-blur矩形到pingpongFBO1
  4. 使用 pingpongFBO1 作为纹理,绘制垂直模糊矩形到 pingpongFBO[0]

这是模糊半径为 200 的结果:

Result

如你所见,水平模糊效果几乎消失了。

蓝色矩形的边缘仍然很锋利。

如果我只画水平模糊部分,它看起来是正确的,现在边缘模糊了。

Hori Only

这是我的模糊片段着色器代码

#version 330 core
#define pow2(x)(x*x)
#define PI 3.14159265
uniform sampler2D screenTexture;
uniform bool horizontal;
uniform float radius;

out vec4 FragColor;
float gaussian(float x){
    float sigma2=2.*pow2(radius/3.);
    return(1./(sqrt(PI*sigma2)))*exp(-pow2(x)/sigma2);
}
void main(){
    vec2 resolution=vec2(600,600);
    vec2 uv=vec2(gl_FragCoord.xy/resolution);
    vec4 color=vec4(0.);
    float weight=gaussian(0);
    color+=texture2D(screenTexture,uv)*weight;
    float accum=weight;
    if(horizontal){
        for(int i=1;i<radius+1;i++){
            vec2 off=vec2(i,0)/resolution;
            weight=gaussian(i);
            color+=texture2D(screenTexture,uv+off)*weight;

            color+=texture2D(screenTexture,uv-off)*weight;
            accum+=weight*2;
        }
    }else{
        for(int i=1;i<radius+1;i++){
            vec2 off=vec2(0,i)/resolution;
            weight=gaussian(i);
            color+=texture2D(screenTexture,uv+off)*weight;

            color+=texture2D(screenTexture,uv-off)*weight;
            accum+=weight*2;
        }
    }
    FragColor=vec4((color/accum).xyz,1.);
}

这是主要的 CPP :

#include "Common.hh"

const unsigned int SCR_WIDTH = 600;
const unsigned int SCR_HEIGHT = 600;
float left = 150;
float top = 200;
float radius = 200;
void processInput(GLFWwindow *window);
int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow *window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);

    glfwMakeContextCurrent(window);

    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    float *quad1Vertices = rectWithSize(SCR_WIDTH, 200);
    float *quad2Vertices = rectWithSize(200, SCR_WIDTH);
    float *blurQuadVertices = rectWithSize(200.0, 200.0);
    float backgroundVertices[] = {
        -1.0f, 1.0f, 0.0f, 1.0f,
        -1.0f, -1.0f, 0.0f, 0.0f,
        1.0f, -1.0f, 1.0f, 0.0f,

        -1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, -1.0f, 1.0f, 0.0f,
        1.0f, 1.0f, 1.0f, 1.0f};

    unsigned int quad1VAO, quad1VBO;
    glGenVertexArrays(1, &quad1VAO);
    glGenBuffers(1, &quad1VBO);
    glBindVertexArray(quad1VAO);
    glBindBuffer(GL_ARRAY_BUFFER, quad1VBO);
    glBufferData(GL_ARRAY_BUFFER, RECT_SIZE, quad1Vertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);

    unsigned int quad2VAO, quad2VBO;
    glGenVertexArrays(1, &quad2VAO);
    glGenBuffers(1, &quad2VBO);
    glBindVertexArray(quad2VAO);
    glBindBuffer(GL_ARRAY_BUFFER, quad2VBO);
    glBufferData(GL_ARRAY_BUFFER, RECT_SIZE, quad2Vertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);

    unsigned int quad3VAO, quad3VBO;
    glGenVertexArrays(1, &quad3VAO);
    glGenBuffers(1, &quad3VBO);
    glBindVertexArray(quad3VAO);
    glBindBuffer(GL_ARRAY_BUFFER, quad3VBO);
    glBufferData(GL_ARRAY_BUFFER, RECT_SIZE, blurQuadVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);

    unsigned int backgroundVAO, backgroundVBO;
    glGenVertexArrays(1, &backgroundVAO);
    glGenBuffers(1, &backgroundVBO);
    glBindVertexArray(backgroundVAO);
    glBindBuffer(GL_ARRAY_BUFFER, backgroundVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(backgroundVertices), &backgroundVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)(2 * sizeof(float)));

    Shader shader("simple_rect.vs", "simple_rect.fs");
    Shader screenShader("screen.vs", "screen.fs");
    Shader blurShader("blur_rect.vs", "blur_rect.fs");
    glm::mat4 projection = glm::ortho(0.0f, (float)SCR_WIDTH, 0.0f, (float)SCR_HEIGHT, -1.0f, 1.0f);
    glm::mat4 model = glm::mat4(1.0);
    shader.use();
    shader.setMat4("projection", projection);

    blurShader.use();
    blurShader.setMat4("projection", projection);
    blurShader.setInt("screenTexture", 0);

    screenShader.use();
    screenShader.setMat4("projection", glm::mat4(1.0));
    screenShader.setMat4("model", glm::mat4(1.0));
    screenShader.setInt("screenTexture", 0);

    GLuint colorFBO;
    GLuint colorBuffer;

    glGenFramebuffers(1, &colorFBO);
    glGenTextures(1, &colorBuffer);

    glBindFramebuffer(GL_FRAMEBUFFER, colorFBO);

    glBindTexture(GL_TEXTURE_2D, colorBuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorBuffer, 0);

    GLuint pingPongFBO[2];
    GLuint pingPongColorBuffer[2];
    glGenFramebuffers(2, pingPongFBO);
    glGenTextures(2, pingPongColorBuffer);
    for (GLuint i = 0; i < 2; i++)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, pingPongFBO[i]);

        glBindTexture(GL_TEXTURE_2D, pingPongColorBuffer[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingPongColorBuffer[i], 0);
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    while (!glfwWindowShouldClose(window))
    {
        processInput(window);
        glBindFramebuffer(GL_FRAMEBUFFER, colorFBO);
        glClearColor(229.0 / 255.0, 229.0 / 255.0, 229.0 / 255.0, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        shader.use();
        shader.setMat4("model", glm::translate(model, glm::vec3(0.0f, 100.0f, 0.0f)));
        shader.setVec4("uColor", glm::vec4(0.3451, 0.7333, 0.2, 1.0));
        glBindVertexArray(quad1VAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        shader.setVec4("uColor", glm::vec4(0, 178.0 / 255.0, 1, 1.0));
        shader.setMat4("model", glm::translate(model, glm::vec3(50.0f, 0.0f, 0.0f)));
        glBindVertexArray(quad2VAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glBindFramebuffer(GL_FRAMEBUFFER, pingPongFBO[0]);
        glBindTexture(GL_TEXTURE_2D, colorBuffer);
        glClearColor(0.0f, 1.0f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        screenShader.use();
        glBindVertexArray(backgroundVAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glBindFramebuffer(GL_FRAMEBUFFER, pingPongFBO[1]);
        glBindTexture(GL_TEXTURE_2D, colorBuffer);
        glClearColor(0.0f, 1.0f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        screenShader.use();
        glBindVertexArray(backgroundVAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glBindFramebuffer(GL_FRAMEBUFFER, pingPongFBO[1]);
        glBindTexture(GL_TEXTURE_2D, pingPongColorBuffer[0]);
        blurShader.use();
        blurShader.setMat4("model", glm::translate(model, glm::vec3(left, top, 0)));
        blurShader.setInt("screenTexture", 0);
        blurShader.setBool("horizontal", true);
        blurShader.setFloat("radius", radius);
        glBindVertexArray(quad3VAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glBindFramebuffer(GL_FRAMEBUFFER, pingPongFBO[0]);
        glBindTexture(GL_TEXTURE_2D, pingPongColorBuffer[1]);
        blurShader.setBool("horizontal", false);
        glBindVertexArray(quad3VAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glClearColor(0.0f, 1.0f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        screenShader.use();
        glBindVertexArray(backgroundVAO);
        glBindTexture(GL_TEXTURE_2D, pingPongColorBuffer[0]);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glDeleteVertexArrays(1, &quad1VAO);
    glDeleteBuffers(1, &quad1VBO);

    glDeleteVertexArrays(1, &quad2VAO);
    glDeleteBuffers(1, &quad2VBO);

    glDeleteVertexArrays(1, &quad3VAO);
    glDeleteBuffers(1, &quad3VBO);

    glDeleteVertexArrays(1, &backgroundVAO);
    glDeleteBuffers(1, &backgroundVBO);
    glfwTerminate();
    return 0;
}


完整的源代码是here

The vertical edge is blurry. But the effect is reduced and covered by the vertical blur in the 2nd pass. Note, the vertical blur reinforced the vertical edge, because it blurs along this edge.
After the 1st pass (horizontal blur), the main color on the left side is still blue and the main color on the right side is still green and white. The vertical blur, mix the colors along the columns of the image. That causes that the transition along the columns between the left (blue) and right (green/white) becomes a noticeable edge.

If you change the order of the passed (1st vertical blur, 2nd horizontal blur), then the horizontal edge becomes visible again:

In general the algorithm works. Compare the result, when a diagonal blur is used:

void main(){
    // [...]

    if(horizontal){
        for(int i=1;i<radius+1;i++){
            vec2 off=vec2(i,i)/resolution;

            // [...]
        }
    }else{
        for(int i=1;i<radius+1;i++){
            vec2 off=vec2(-i,i)/resolution;

            // [...]
        }
    }

    // [...]
}