zoukankan      html  css  js  c++  java
  • OpenGL帧缓存对象(FBO:Frame Buffer Object) 【转】

    http://blog.csdn.net/dreamcs/article/details/7691690

    原文地址http://www.songho.ca/opengl/gl_fbo.html

    但有改动。

    OpenGL Frame BufferObject(FBO)

    Overview:

           在OpenGL渲染管线中,几何数据和纹理经过多次转化和多次测试,最后以二维像素的形式显示在屏幕上。OpenGL管线的最终渲染目的地被称作帧缓存(framebuffer)。帧缓冲是一些二维数组和OpenG所使用的存储区的集合:颜色缓存、深度缓存、模板缓存和累计缓存。默认情况下,OpenGL将帧缓冲区作为渲染最终目的地。此帧缓冲区完全由window系统生成和管理。这个默认的帧缓存被称作“window系统生成”(window-system-provided)的帧缓冲区。

           在OpenGL扩展中,GL_ARB_framebuffer_object提供了一种创建额外的非显示的帧缓冲区对象的接口。此种帧缓冲区被称为"由程序创建的帧缓冲区(application-created framebuffer)"。这是为了区分window系统默认创建的帧缓冲区对象。通过使用帧缓存对象(FBO),OpenGL可以将显示输出重定向到程序帧缓存对象,而不是传统的“window系统生成”帧缓存。而且,它完全受OpenGL控制。

    相似于window系统提供的帧缓存,一个FBO也包含一些存储颜色、深度和模板数据的区域。(注意:没有累积缓存)我们把FBO中这些逻辑缓存称之为“帧缓存关联图像”,它们是一些能够和一个帧缓存对象关联起来的二维像素数组。

    有两种类型的“帧缓存关联图像”:纹理图像(texture images)和渲染缓存图像(renderbuffer images)。如果纹理对象的图像数据关联到帧缓存,OpenGL执行的是“渲染到纹理”(render to texture)操作。如果渲染缓存的图像数据关联到帧缓存,OpenGL执行的是离线渲染(offscreen rendering)。

           这里要提到的是,渲染缓存对象是在GL_ARB_framebuffer_object 扩展中定义的一种新的存储类型。在渲染过程中它被用作存储单幅二维图像。下面这幅图显示了帧缓存对象、纹理对象和渲染缓存对象之间的联系。多多个纹理对象或者渲染缓存对象能够通过关联点关联到一个帧缓存对象上。

          下面这幅图显示了帧缓冲区对象与纹理对象、渲染缓冲区对象的关系。多个纹理对象或多个渲染缓冲区对象,可通过连接点(attachment points)连接到帧缓冲区对象上。


            在一个帧缓存对象中有多个颜色关联点(GL_COLOR_ATTACHMENT0_EXT,...,GL_COLOR_ATTACHMENTn_EXT),一个深度关联点(GL_DEPTH_ATTACHMENT_EXT),和一个模板关联点(GL_STENCIL_ATTACHMENT_EXT)。每个FBO中至少有一个颜色关联点,其数目与实体显卡相关。可以通过GL_MAX_COLOR_ATTACHMENTS_EXT来查询颜色关联点的最大数目。FBO有多个颜色关联点的原因是可以将颜色对象同时渲染到多个目标上。这种“多渲染目标”(multiple rendertargets,MRT)可以通过GL_ARB_draw_buffers扩展实现。需要注意的是:FBO本身并没有任何图像存储区,只有多个关联点。

         帧缓冲区对象提供了高效的切换机制:从帧缓冲区对象中断开先前的帧缓冲区关联图像,连接一个新的帧缓冲区关联图像到帧缓冲区对象上。切换帧缓冲区关联图像比切换帧缓冲区对象快多了。帧缓冲区对象提供了glFramebufferTexture2D()来切换2D纹理对象,glFramebufferRenderbuffer()来切换渲染缓冲区对象。

     

    创建FBO

    创建FBO和产生VBO类似。

    glGenFramebuffers()
    void glGenFramebuffers(GLsizei n, GLuint* ids)
    void glDeleteFramebuffers(GLsizei n, const GLuint* ids)

     

    glGenFramebuffers()需要两个参数:第一个是要创建的帧缓存的数目,第二个是指向存储一个或者多个ID的变量或数组的指针。它返回未使用的FBO的ID。ID为0表示默认帧缓存,即window系统提供的帧缓存。

    当FBO不再被使用时,FBO可以通过调用glDeleteFramebuffers()来删除。

     

    glBindFramebuffer()

    一旦一个FBO被创建,在使用它之前必须绑定。

    void glBindFramebuffer(GLenum target, GLuint id)

    第一个参数target应该是GL_FRAMEBUFFER,第二个参数是FBO的ID号。一旦FBO被绑定,之后的所有的OpenGL操作都会对当前所绑定的FBO造成影响。ID号为0表示缺省帧缓存,即默认的window提供的帧缓存。因此,在glBindFramebuffer()中将ID号设置为0可以解绑定当前FBO。

     

    渲染缓存对象(Renderbuffer Object)

    另外,渲染缓存是为离线渲染而新引进的。它允许将一个场景直接渲染到一个渲染缓存对象中,而不是渲染到纹理对象中。渲染缓存对象是用于存储单幅图像的数据存储区域。该图像按照一种可渲染的内部格式存储。它用于存储没有相关纹理格式的OpenGL逻辑缓存,比如模板缓存或者深度缓存。

    glGenRenderbuffers()

    void glGenRenderbuffers(GLsizei n, GLuint* ids)

    void glDeleteRenderbuffers(GLsizei n, const Gluint* ids)

    一旦一个渲染缓存被创建,它返回一个非零的正整数。ID为0是OpenGL保留值。

    glBindRenderbuffer()

    void glBindRenderbuffer(GLenum target, GLuint id)

    和OpenGL中其他对象一样,在引用渲染缓存之前必须绑定当前渲染缓存对象。他target参数应该是GL_RENDERBUFFER。

    glRenderbufferStorage()

    void glRenderbufferStorage(GLenum target, GLenum internalFormat,

                                 GLsizei width, GLsizei height)

    当一个渲染缓存被创建,它没有任何数据存储区域,所以我们还要为他分配空间。这可以通过用glRenderbufferStorage()实现。第一个参数必须是GL_RENDERBUFFER。第二个参数可以是用于颜色的(GL_RGB,GL_RGBA,etc.),用于深度的(GL_DEPTH_COMPONENT),或者是用于模板的格式(GL_STENCIL_INDEX)。Width和height是渲染缓存图像的像素维度。

    width和height必须比GL_MAX_RENDERBUFFER_SIZE_EXT小,否则将会产生GL_UNVALID_VALUE错误。

     

    glGetRenderbufferParameteriv()

    void glGetRenderbufferParameteriv(GLenum target, GLenum param,GLint* value);

    我们也可以得到当前绑定的渲染缓存对象的一些参数。Target应该是GL_RENDERBUFFER,第二个参数是所要得到的参数名字。最后一个是指向存储返回值的整型量的指针。渲染缓存的变量名有如下:

    GL_RENDERBUFFER_WIDTH

    GL_RENDERBUFFER_HEIGHT

    GL_RENDERBUFFER_INTERNAL_FORMAT

    GL_RENDERBUFFER_RED_SIZE

    GL_RENDERBUFFER_GREEN_SIZE

    GL_RENDERBUFFER_BLUE_SIZE

    GL_RENDERBUFFER_ALPHA_SIZE

    GL_RENDERBUFFER_DEPTH_SIZE

    GL_RENDERBUFFER_STENCIL_SIZE

     

    将图像和FBO关联

    FBO本身没有图像存储区。我们必须帧缓存关联图像(纹理或渲染对象)关联到FBO。这种机制允许FBO快速地切换(分离和关联)帧缓存关联图像。切换帧缓存关联图像比在FBO之间切换要快得多。而且,它节省了不必要的数据拷贝和内存消耗。比如,一个纹理可以被关联到多个FBO上,图像存储区可以被多个FBO共享。

    把2D纹理图像关联到FBO

    glFramebufferTexture2D(GLenum target,

                              GLenumattachmentPoint,

                             GLenum textureTarget,

                             GLuint textureId,

                             GLint  level)

    glFramebufferTexture2D()把一幅纹理图像关联到一个FBO。第一个参数一定是GL_FRAMEBUFFER_,第二个参数是关联纹理图像的关联点。一个帧缓冲区对象可以有多个颜色关联点(GL_COLOR_ATTACHMENT0, ..., GL_COLOR_ATTACHMENTn),L_DEPTH_ATTACHMENT, 和GL_STENCIL_ATTACHMENT。第三个参数textureTarget在多数情况下是GL_TEXTURE_2D。第四个参数是纹理对象的ID号。最后一个参数是要被关联的纹理的mipmap等级

    如果参数textureId被设置为0,那么纹理图像将会被从FBO分离。如果纹理对象在依然关联在FBO上时被删除,那么纹理对象将会自动从当前帮的FBO上分离。然而,如果它被关联到多个FBO上然后被删除,那么它将只被从绑定的FBO上分离,而不会被从其他非绑定的FBO上分离。

    把渲染缓存对象关联到FBO

    void glFramebufferRenderbuffer(GLenum target,

                                     GLenum attachmentPoint,

                                     GLenum renderbufferTarget,

                                     GLuint renderbufferId)

    通过调用glFramebufferRenderbuffer()可以关联渲染缓存图像。前两个参数和glFramebufferTexture2D()一样。第三个参数只能是GL_RENDERBUFFER,最后一个参数是渲染缓存对象的ID号。

    如果参数renderbufferId被设置为0,渲染缓存图像将会从FBO的关联点分离。如果渲染缓存图像在依然关联在FBO上时被删除,那么纹理对象将会自动从当前绑定的FBO上分离,而不会从其他非绑定的FBO上分离。

    检查FBO状态

    一旦关联图像(纹理和渲染缓存)被关联到FBO上,在执行FBO的操作之前,你必须检查FBO的状态:完整或不完整。这可以通过调用glCheckFramebufferStatusEXT()来检查其状态。如果帧缓冲区状态是不完整的,那么任何绘制命令(glBegin(),glCopyTexImage2D()等等)都会失败。

     

    GLenum glCheckFramebufferStatus(GLenum target)

    glCheckFramebufferStatus()检查当前帧缓存的关联图像和帧缓存参数。这个函数不能在glBegin()/glEnd()之间调用。Target参数必须为GL_FRAMEBUFFER。它返回一个非零值。如果所有要求和准则都满足,它返回GL_FRAMEBUFFER_COMPLETE。否则,返回一个相关错误代码告诉我们哪条准则没有满足。

    FBO完整性准则有:

    (1)帧缓存关联图像的宽度和高度必须非零。

    (2)如果一幅图像被关联到一个颜色关联点,那么这幅图像必须有颜色可渲染的内部格式(GL_RGBA, GL_DEPTH_COMPONENT, GL_LUMINANCE, etc)。

    (3)如果一幅被图像关联到GL_DEPTH_ATTACHMENT,那么这幅图像必须有深度可渲染的内部格式(GL_DEPTH_COMPONENT,GL_DEPTH_COMPONENT24, etc)。

    (4)如果一幅被图像关联到GL_STENCIL_ATTACHMENT,那么这幅图像必须有模板可渲染的内部格式(GL_STENCIL_INDEX,GL_STENCIL_INDEX8, etc)。

    (5)FBO至少有一幅图像关联。

    (6)被关联到FBO的缩影图像必须有相同的宽度和高度。

    (7)被关联到颜色关联点上的所有图像必须有相同的内部格式。

    注意:即使以上所有条件都满足,你的OpenGL驱动也可能不支持某些格式和参数的组合。如果一种特别的实现不被OpenGL驱动支持,那么glCheckFramebufferStatus()返回GL_FRAMEBUFFER_UNSUPPORTED。

    示例:渲染到纹理

    源码下载地址
    其它:
    只渲染到深度缓冲区中。下载地址
    使用模板渲染对象外轮廓 下载地址

     

    有时候,你需要产生动态纹理。比较常见的例子是产生镜面反射效果、动态环境贴图和阴影等效果。动态纹理可以通过把场景渲染到纹理来实现。渲染到纹理的一种传统方式是将场景绘制到普通的帧缓存上,然后调用glCopyTexSubImage2D()拷贝帧缓存图像至纹理。

    使用FBO,我们能够将场景直接渲染到纹理,所以我们不必使用window系统提供的帧缓存。并且,我们能够去除额外的数据拷贝(从帧缓存到纹理);。

    这个demo实现了使用FBO和不使用FBO两种情况下渲染到纹理的操作,并且比较了性能差异。除了能够获得性能上的提升,使用FBO的还有另外一个优点。在传统的渲染到纹理的模式中(不使用FBO),如果纹理分辨率比渲染窗口的尺寸大,超出窗口区域的部分将被剪切掉。然后,使用FBO就不会有这个问题。你可以产生比显示窗口大的帧缓存渲染图像。

    以下代码在渲染循环开始之前,对FBO和帧缓存关联图像进行了初始化。注意只有一幅纹理图像被关联到FBO,但是,一个深度渲染图像被关联到FBO的深度关联点。实际上我们并没有使用这个深度缓存,但是FBO本身需要它进行深度测试。如果我们不把这个深度可渲染的图像关联到FBO,那么由于缺少深度测试渲染输出结果是不正确的。如果在FBO渲染期间模板测试也是必要的,那么也需要把额外的渲染图像和GL_STENCIL_ATTACHMENT_EXT关联起来。

    1. // create a texture object  
    2. GLuint textureId;  
    3. glGenTextures(1, &textureId);  
    4. glBindTexture(GL_TEXTURE_2D, textureId);  
    5. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
    6. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);  
    7. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);  
    8. glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);  
    9. glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap  
    10. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_WIDTH, TEXTURE_HEIGHT, 0,  
    11.              GL_RGBA, GL_UNSIGNED_BYTE, 0);  
    12. glBindTexture(GL_TEXTURE_2D, 0);  
    13.   
    14. // create a renderbuffer object to store depth info  
    15. GLuint rboId;  
    16. glGenRenderbuffersEXT(1, &rboId);  
    17. glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rboId);  
    18. glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT,  
    19.                          TEXTURE_WIDTH, TEXTURE_HEIGHT);  
    20. glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);  
    21.   
    22. // create a framebuffer object  
    23. GLuint fboId;  
    24. glGenFramebuffersEXT(1, &fboId);  
    25. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);  
    26.   
    27. // attach the texture to FBO color attachment point  
    28. glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,  
    29.                           GL_TEXTURE_2D, textureId, 0);  
    30.   
    31. // attach the renderbuffer to depth attachment point  
    32. glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,  
    33.                              GL_RENDERBUFFER_EXT, rboId);  
    34.   
    35. // check FBO status  
    36. GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);  
    37. if(status != GL_FRAMEBUFFER_COMPLETE_EXT)  
    38.     fboUsed = false;  
    39.   
    40. // switch back to window-system-provided framebuffer  
    41. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);  
    42. ...  

    渲染到纹理的过程和普通的绘制过程基本一样。我们只需要把渲染的目的地由window系统提供的帧缓存改成不可显示的应用程序创建的帧缓存(FBO)就可以了。

     

    1. ...  
    2. // set rendering destination to FBO  
    3. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);  
    4.   
    5. // clear buffers  
    6. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
    7.   
    8. // draw a scene to a texture directly  
    9. draw();  
    10.   
    11. // unbind FBO  
    12. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);  
    13.   
    14. // trigger mipmaps generation explicitly  
    15. // NOTE: If GL_GENERATE_MIPMAP is set to GL_TRUE, then glCopyTexSubImage2D()  
    16. // triggers mipmap generation automatically. However, the texture attached  
    17. // onto a FBO should generate mipmaps manually via glGenerateMipmapEXT().  
    18. glBindTexture(GL_TEXTURE_2D, textureId);  
    19. glGenerateMipmapEXT(GL_TEXTURE_2D);  
    20. glBindTexture(GL_TEXTURE_2D, 0);  
    21. ...  

    注意到,glGenerateMipmapEXT()也是作为FBO扩展的一部分,用来在改变了纹理图像的基级之后显式生成mipmap的。如果GL_GENERATE_MIPMAP被设置为GL_TRUE,那么glTex{Sub}Image2D()和 glCopyTex{Sub}Image2D()将会启用自动mipmap生成(在OpenGL版本1.4或者更高版本中)。然后,当纹理基级被改变时,FBO操作不会自动产生mipmaps。因为FBO不会调用glCopyTex{Sub}Image2D()来修改纹理。因此,要产生mipmap,glGenerateMipmapEXT()必须被显示调用。

  • 相关阅读:
    Java实现 蓝桥杯 算法提高 小X的购物计划
    Java实现 蓝桥杯 算法提高 小X的购物计划
    Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
    Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
    Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
    Java 第十一届 蓝桥杯 省模拟赛 小明的城堡
    Java 第十一届 蓝桥杯 省模拟赛 小明的城堡
    Java 第十一届 蓝桥杯 省模拟赛 小明的城堡
    129. Sum Root to Leaf Numbers
    117. Populating Next Right Pointers in Each Node II
  • 原文地址:https://www.cnblogs.com/mazhenyu/p/6439304.html
Copyright © 2011-2022 走看看