1. 程式人生 > >處理opengl使用fbo貼圖時產生的鋸齒

處理opengl使用fbo貼圖時產生的鋸齒

背景描述:

在使用artools的矩陣進行貼圖的時候,發現在我自己的濾鏡裡貼圖會有明顯鋸齒。

學習了一下,這種鋸齒被稱作aliasing,反鋸齒被稱作anti-aliasing。

比較常用的方法是MSAA(Multisample Anti- Aliasing),該方法已經在opengl裡實現了,對於使用視窗的opengl程式來說,僅僅需要設定sample的數目,然後glEnable(GL_MULTISAMPLE)。而對於我使用離屏渲染fbo的情況來說,必須自己處理。

網上常見的方法大多使用的是renderbuffer來處理。我目前呼叫的流程不需要renderbuffer,我的流程僅僅需要opengl把圖draw到一個fbo裡,然後我通過glReadPixels讀取內容到cpu memory。最後我僅僅增加了一個multisample fbo和一個multisample texture來處理。粗略流程就是:讓draw的目的fbo繫結使用multi sample的texture,然後再把裡面的內容拷貝到普通的fbo用於輸出。

下面簡單解釋一下流程:

首先,初始化的時候標明使用multi sample,以及samples的個數。我這裡是通過離屏渲染,所以對應的是glXChooseFBConfig初始化的引數:

    static int visualAttribs[] = { GLX_SAMPLE_BUFFERS, 1, GLX_SAMPLES , 4 };
    /* GLX Framebuffer Config */
    fbConfigs = glXChooseFBConfig(s->display, DefaultScreen(s->display), visualAttribs, &numberOfFramebufferConfigurations);
在 glewInit() 之後,我們可以通過下面的方法檢查是否設定生效了:
    GLint bufs, samplesaa;
    glGetIntegerv(GL_SAMPLE_BUFFERS, &bufs);
    glGetIntegerv(GL_SAMPLES, &samplesaa);
之後,設定使用multi sample
    glEnable(GL_MULTISAMPLE);
接下來,我們需要建立multi sample的texture,用於多采樣。注意GL_TEXTURE_2D_MULTISAMPLE後面的引數4對應的是samples的個數,越多效果越好,速度越慢
    glGenTextures(3, p_fbo->tex_serial);
    for (int i=0; i<s->planes; i++)
    {
        int k = (!!i);

        glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, p_fbo->tex_serial[i]);
        glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RED, p_fbo->width[k], p_fbo->height[k], GL_TRUE);
        glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
    }
然後建立fbo,繫結這個multi sample的texture。這裡和普通fbo繫結的區別是顯示宣告GL_TEXTURE_2D_MULTISAMPLE
    glGenFramebuffers(3, s->fbo_render);
    for (int i=0; i<s->planes; i++)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, s->fbo_render[i]);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, p_fbo->tex_serial[i], 0);
        status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if (status != GL_FRAMEBUFFER_COMPLETE)
        {
          av_log(NULL, AV_LOG_ERROR, "Error(in %s): Couldn't create frame buffer", __FUNCTION__);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

接下來建立普通的texture和fbo,用於輸出(readpixel)資料
    glGenTextures(3, p_fbo->tex_second);
    for (int i=0; i<s->planes; i++)
    {
        int k = (!!i);

        glBindTexture(GL_TEXTURE_2D, p_fbo->tex_second[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, p_fbo->width[k], p_fbo->height[k], 0, GL_RED, GL_UNSIGNED_BYTE, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_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);
    }

    glGenFramebuffers(3, s->fbo_second);
    for (int i=0; i<s->planes; i++)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, s->fbo_second[i]);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_fbo->tex_second[i], 0);
        status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
        if (status != GL_FRAMEBUFFER_COMPLETE)
        {
          av_log(NULL, AV_LOG_ERROR, "Error(in %s): Couldn't create frame buffer", __FUNCTION__);
        }
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

在畫圖的時候,我們首先繫結multi sample的fbo:fbo_render
            glBindFramebuffer(GL_FRAMEBUFFER, s->fbo_render[i]);
            glUseProgram(p_fbo->prg[k]->po);
        
            /* source */
            glActiveTexture(GL_TEXTURE0+i);
            glBindTexture(GL_TEXTURE_2D, s->sub_texture[i]);
            glUniform1i(p_fbo->prg[k]->texUniform, i);

            glUniformMatrix4fv(p_fbo->prg[k]->posMVPMatrix, 1, GL_FALSE, (const GLfloat *)m2);
            
            /* destination */
            glDrawBuffer(GL_COLOR_ATTACHMENT0);
            glViewport(0, 0, p_fbo->width[k], p_fbo->height[k]);

            /* draw to FBO */
            glBindVertexArray(p_fbo->vao[k]);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, p_fbo->index_buffer[k]);
            glDrawElements(GL_TRIANGLES, p_fbo->indices_count[k], GL_UNSIGNED_INT, NULL);
在draw完成之後,我們把裡面的內容考到普通的fbo:
            // copy framebuffer from multi sample fbo to normal fbo
            glBindFramebuffer(GL_READ_FRAMEBUFFER, s->fbo_render[i]);
            glBindFramebuffer(GL_DRAW_FRAMEBUFFER, s->fbo_second[i]);
            glBlitFramebuffer(0, 0, p_fbo->width[k], p_fbo->height[k], 0, 0, p_fbo->width[k], p_fbo->height[k], GL_COLOR_BUFFER_BIT, GL_NEAREST);
最後,我們從第二個fbo裡讀取資料給cpu的memory
    for (int i=0; i<s->planes; i++)
    {
        GLubyte* src;
        int k = (!!i);
        glBindFramebuffer(GL_FRAMEBUFFER, s->fbo_second[i]);
        glReadBuffer(GL_COLOR_ATTACHMENT0);
        glPixelStorei(GL_PACK_ALIGNMENT, 4); // TODO
        glPixelStorei(GL_PACK_IMAGE_HEIGHT, p_fbo->height[k]); // image height in client memory
        glPixelStorei(GL_PACK_ROW_LENGTH, out->linesize[i]);   // pitch in client memory
        glBindBuffer(GL_PIXEL_PACK_BUFFER, s->pbo_down[i]);
        glReadPixels(0, 0, p_fbo->width[k], p_fbo->height[k], GL_RED, GL_UNSIGNED_BYTE, 0);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        //glBindBuffer(GL_PIXEL_PACK_BUFFER, s->pbo_down[i]);
        src = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
        av_assert0(src); // TODO
        av_assert0(out->data[i]);
        memcpy(out->data[i], src, out->linesize[i]*p_fbo->height[k]);
        glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
    }
下面是對比圖,第一張是沒有使用MSAA的貼圖,第二張是使用sample=4的貼圖。很明顯邊緣效果好很多。



更多更詳細的原理內容以及使用renderbuffer的解決方法請參考這些帖子:

http://www.tuicool.com/articles/I3Qfmmb

http://www.cnblogs.com/waytofall/p/3830527.html