OpenGL的帧缓冲对象和浮点纹理



接下来准备实现光照贴图的打包和预计算了。因为想实现HDR,光照贴图准备存储为 RGBE或浮点格式。为了渲染浮点格式的光照贴图,就需要解决两个问题,一是如何让OpenGL能够真正地处理浮点格式的纹理,而不是把他们截断到 [0,1]区间内;二是如何将场景渲染到浮点格式的纹理中,以便对这个纹理进行Tone mapping 和Bloom等操作。

今天花了一晚上的时间在网上搜索资料,学习了Frame Buffer Object的用法。FBO是目前实现RTT和GPGPU算法最好的解决方案,因为它的接口设计相对合理,避免了显存到内存之间的数据交换。

FBO相当于各类帧缓冲,是一个渲染对象。在传统的渲染管线中,渲染后的数据输出到各种帧缓冲中,如颜色缓冲,深度缓冲等。有了FBO之后,就可以把数据渲染到已粘附到FBO的纹理中。稍后将会给出具体的代码,结合代码和分析你将很容易掌握FBO的用法。

现在先讨论一下OpenGL的浮点纹理。我们先来看一下OpenGL的glTexImage2D函数原型。

void glTexImage2D(

       GLenum target,
      GLint level,
      GLint internalformat,
      GLsizei width,
      GLsizei height,
      GLint border,
      GLenum format,
      GLenum type,
      const GLvoid *pixels
    );

 

以前,我们向纹理对象提供数据时是这样做的:

glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,pixels);

我们为Type参数提供的值是GL_UNSIGNED_BYTE表示我们的数据在内存中的存储格式为无符号字节型整数。现在我们的纹理格式为浮点型,我们会很自然地想到,能不能把上述函数调用改为:

glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_FLOAT,pixels);

来让OpenGL接收浮点纹理呢?答案是不可以。使用GL_FLOAT调用该函数后,glTexImage确实能够接收浮点数据,但type参数表示的仅仅是象素数据在主内存中的存储格式,一个象素在显存里如何储存仅由 internalformat参数决定。这里internalformat如果设置为GL_RGBA,则表示象素存储为8位整数的RGBA格式。这样一来,当函数在接收数据时,会自动把浮点类型的象素数据截断到[0,255]的整数区间内,然后再放到显存中去,这就丢失了浮点数据。

GL_ARB_texture_float扩展解决了这个问题。这个扩展为我们提供了一些新的内部数据类型,包括GL_RGB16F_ARB, GL_RGBA16F_ARB, GL_RGB32F_ARB, GL_RGBA32F_ARB等。其中16F表示半精度浮点数。半精度浮点数占用两个字节,包括1位的符号位,5位的指数位(Exponent)和10位的尾数位(Mantissa)。要注意一些老的显卡可能不支持对GL_RGBA32F_ARB和GL_RGB32F_ARB等32位浮点纹理进行双线性纹理过滤(如我以前使用的GeForce 6200)。同时考虑到空间因素,目前主要还是使用16F的纹理。当我们把纹理内部数据类型指定为这些浮点类型时,OpenGL就不会再把我们提供的纹理数据截断到[0.0,1.0]区间上了。如果我们指定的是16F格式的纹理,OpenGL会自动将我们提供的32bit浮点数据转换为16Bit的数据格式。因此我们唯一要做的事情就是把函数调用改成这样以支持Floating Point纹理:

glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA16F_ARB,width,height,0,GL_RGBA,GL_FLOAT,pixels);

好了,现在通过一段代码解释FBO的用法。下面的这段代码是我探索FBO时写的。这里贴出来解释一下。前面的初始化代码就省略了。

    这个代码所作的事情就是,创建一个浮点纹理,然后使用这个浮点纹理向FBO绘制一个矩形,

    再把FBO中的数据读回内存。

    glEnable(GL_TEXTURE_2D);
    // 首先我们创建一个纹理,然后用这个纹理渲染一个QUAD。
    glGenTextures(1,&texid);
    
    glBindTexture(GL_TEXTURE_2D,texid);
    
    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);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    随便创建一个浮点型的数组,代表我们的浮点纹理。

    float *tex = new float[1024*4];
    for (int i=0;i<32;i++)
        for (int j=0; j<32;j++)
        {
            tex[i*32*4+j*4] = 1000.3f;
            tex[i*32*4+j*4+1] = 1900.3f;
            tex[i*32*4+j*4+2] = 1099.3f;
            tex[i*32*4+j*4+3] = 1.0f;
        }


    //把纹理格式设置为半精度浮点,然后传入一个事先生成的浮点型数组。

    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA16F_ARB,32,32,0,GL_RGBA,GL_FLOAT,tex);
    if (glGetError()!=GL_NO_ERROR )
    {
        ui->Console->PrintString(HString("Error Occured."));
    }
    
    // 注意,HFBO是我自己封装的OpenGL函数库,其中fbo.xxx相当于glxxxEXT函数。
    // 如fbo.GenFramebuffers就相当于glGenFramebuffersEXT。
    HFBO fbo;

    首先,创建一个帧缓冲对象:


    GLuint fboid;
    fbo.GenFramebuffers(1,&fboid);

    然后绑定这个对象:

    fbo.BindFramebuffer(GL_FRAMEBUFFER_EXT,fboid);

    接着创建一个纹理,然后把这个纹理粘附到FBO中,这样渲染后的颜色缓冲就会被写入这个纹理:

    GLuint texColorBuf;
    glViewport(0,0,32,32);
    glGenTextures(1,&texColorBuf);
    glBindTexture(GL_TEXTURE_2D,texColorBuf);
    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);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    现在创建的是32Bit浮点纹理,指定相应的格式:

    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA32F_ARB,32,32,0,GL_RGBA,GL_FLOAT,0);

    接下来是重点。glFramebufferTexture2DEXT函数将一个纹理对象粘附到当前绑定的FBO中。
    注意第二参数指定了帮定点。一个FBO可以有多个帮定点,这样数据可以同时写入到多个纹理对象中。
    因为我们目前只有一个纹理对象,所以将绑定点设置为GL_COLOR_ATTACHMENT0_EXT。
    fbo.FramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,

        GL_TEXTURE_2D, texColorBuf, 0);

    接下来glCheckFramebufferStatus函数检查当前的帧缓冲对象是否准备就绪。如果就绪,

    函数返回GL_FRAMEBUFFER_COMPLETE_EXT。

    if (fbo.CheckFramebufferStatus(GL_FRAMEBUFFER_EXT)!=GL_FRAMEBUFFER_COMPLETE_EXT)
        PrintString("Frame buffer not ready.");
    
    现在我们渲染场景。渲染后的颜色数据将写入到当前绑定的FBO对象中。如果不需要向FBO中写入数据,

    而直接绘制到屏幕缓冲区,则调用glBindFramebuffer(GL_FRAMEBUFFER_EXT,0);以取消帧缓冲对象。

    BeginUIDrawing();
        glBindTexture(GL_TEXTURE_2D,texid);
        glColor4ub(255,255,255,255);
        glBegin(GL_QUADS);
            glTexCoord2i(0,0);
            glVertex2i(0,0);
            glTexCoord2i(0,1);
            glVertex2i(0,32);
            glTexCoord2i(1,1);
            glVertex2i(32,32);
            glTexCoord2i(1,0);
            glVertex2i(32,0);
        glEnd();
    EndUIDrawing();

    现在我们创建一个浮点数组,然后试着从刚才已粘附到FBO中的纹理对象读取数据。

    看看这些数据是否和我们的预期相符。    

    float *datar = new float[32*32*4];
    glBindTexture(GL_TEXTURE_2D,texColorBuf);
    
    glReadPixels(0,0,32,32,GL_RGBA,GL_FLOAT,datar);
    glGetTexImage(GL_TEXTURE_2D,0,GL_RGBA,GL_FLOAT,datar);
    
    如果你愿意,可以在此设置一个断点,观察一下datar中的值。    

    程序到此结束,执行清理。

    glDeleteTextures(1,&texColorBuf);
    glDeleteTextures(1,&texid);
    fbo.DeleteFramebuffers(1,&fboid);
    delete [] datar;
    delete [] tex;

如果你还想了解更多关于FBO以及GPGPU的知识,可以参考这篇文章:GPU深度发掘, 作者:Rob 'phantom' Jones 译者:华文广。

你可能感兴趣的:(算法,UI,ext,J#)