• CSharpGL(47)你好,Framebuffer!


    CSharpGL(47)你好,Framebuffer!

    Framebuffer对象(FBO)是一种复杂的OpenGL对象。使用自定义的framebuffer,可以实现离屏渲染,进而实现很多高级功能,例如阴影。

    下载

    CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL

    FBO基本结构

    【注:本节(FBO基本结构)是翻译的(https://www.khronos.org/opengl/wiki/Framebuffer_Object),略有修改。】

    类似其它的OpenGL对象,FBO也有一套glGen, glDelete, glBind的API。

    FBO这套API里的target可接受3种值:GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER或GL_DRAW_FRAMEBUFFER。后两种允许你可以让读操作(glReadPixels等)和写操作(所有的渲染命令)发生到不同的FBO上。GL_FRAMEBUFFER 则将读写发生到同一个FBO。

    名词术语

    为了叙述方便,首先定义一些术语。

    Image

    像素的二维数组(Pixel[ , ]),有特定的格式。

    Layered Image

    相同大小和格式的一组Image。

    Texture

    包含若干Image的OpenGL对象。这些Image的格式相同,但大小未必相同(例如不同mipmap level的Image大小是不同的)。Texture可以以多种方法被shader读取。

    Renderbuffer

    包含1个Image的OpenGL对象。不能被shader读取。只能被创建,然后放到FBO里。

    Attach

    把一个对象关联(附着)到另一个对象上。附着attach不同于绑定binding。对象被绑定到上下文context,对象被附着到另一个对象。

    Attachment point

    Framebuffer对象里可以让Image或Layered Image附着的位置。只有符合规定的图像格式才能被附着。

    Framebuffer-attachable image

    格式符合规定,可以被附着到framebuffer对象的Image。

    Framebuffer-attachable layered image

    格式符合规定,可以被附着到framebuffer对象的Layered Image。

    附着点Attachment Point

    FBO有若干Image的附着点(位置):

    GL_COLOR_ATTACHMENTi

    这些附着点的数量依不同的实现而不同。你可以用GL_MAX_COLOR_ATTACHMENTS 查询一个OpenGL实现支持的颜色附着点的数量。最少有8个,所以你最少可以放心使用附着点0-7。这些附着点只能让可渲染色彩的Image来附着。所有compressed image formats都不是可渲染色彩的,所以都不能附着到FBO。

    GL_DEPTH_ATTACHMENT

    这个附着点只能让depth格式的Image附着。附着的Image就成了此FBO的depth buffer。**注意**,即使你不打算从深度附着点上读取什么东西,也应该给深度附着点设定一个Image。

    GL_STENCIL_ATTACHMENT

    这个附着点只能让stencil格式的Image附着。附着的Image就成了此FBO的stencil buffer。

    GL_DEPTH_STENCIL_ATTACHMENT

    这是“depth+stencil”的简写。附着的Image既是depth buffer又是stencil buffer。注意:如果你使用GL_DEPTH_STENCIL_ATTACHMENT,你应当使用一个以packed depth-stencil为内部格式的Texture或Renderbuffer。

    Attaching Images

    现在我们已经知道了Image可以附着到FBO的哪些位置上,我们可以开始谈谈如何将Image附着到FBO上。首先,我们必须用glBindFramebuffer把FBO绑定到context。

    Attaching Texture

    首先来了解一下各种类型的Texture:

    图中列出了8种类型的Texture。上方分别是1D Texture、2D Texture、3D Texture和2D Array Texture,下方分别是1D Array、Cubemap Texture、Rectangle Texture和Buffer Texture。大多数Texture都支持mipmap(上图中每个Texture从上到下分别为mipmap level0,1,2,3…)。

    你可以将基本上任何类型的Texture里的Image附着到FBO。不过,FBO是被设计来做2D渲染的。所以有必要考虑一下不同类型的Texture是如何映射到FBO里的Image的。记住,Texture就是一组Image,Texture可能包含多个mipmap level,每个mipmap level都可能包含1到多个Image。

    然后,对照上图,不同类型的Texture映射到FBO里的Image的方式如下:

    1D Texture里的Image被视作高度为1的2D Image。1个Image可以被mipmap level标识。

    2D Texture里的Image就照常使用了。1个Image可以被mipmap level标识。

    3D Texture的1个mipmap level被视作2D Image的集合,此集合的元素数量即为此mipmap level的Z坐标。Z坐标的每个整数值都是一个单独的2D层(layer)。所以3D Texture里的的一个Image由layer和mipmap level共同标识。记得3D Texture的不同mipmap level的Z坐标数量是不同的。

    Rectangle Textures只有1个2D Image,因此直接用mipmap level 0标识。

    Cubemap Textures里每个mipmap都包含6个2D Image。因此,1个Image可以被面target和mipmap level标识。然而有些API函数里,1个mipmap level里的各个face是用layer索引标识的。

    1D或2D Array Textures的每个mipmap level都包含多个Image,其数量等于数组元素的数量。因此,每个Image可以被layer(数组索引)和mipmap level标识。1D Array Texture里,每个Image都是高度为1。与3D Texture不同的是,layer不随mipmap层的递进而改变。(即各个mipap level的layer数量都相同)

    Cubemap Array Textures类似2D Array Texture,只是Image数量乘以6。因此一个2D Image由layer(具体的说是layer-face)和mipmap level标识。(这个太难画我就不画了)

    Buffer Textures 不能被附着到FBO。

    上面带下划线的字很重要,因为他们对应了下面的API函数(用于附着Texture)的参数:

    1 void glFramebufferTexture1D(GLenum target​, GLenum attachment​, GLenum textarget​, GLuint texture​, GLint level​);
    2 void glFramebufferTexture2D(GLenum target​, GLenum attachment​, GLenum textarget​, GLuint texture​, GLint level​);
    3 void glFramebufferTextureLayer(GLenum target​, GLenum attachment​, GLuint texture​, GLint level​, GLint layer​);

    参数target与glBind用的相同。但是这里GL_FRAMEBUFFER的意思不是“既可读又可写”(那没有意义),他是和GL_DRAW_FRAMEBUFFER相同的意思。参数attachment是上面介绍的附着点。

    参数texture是你想要附着到FBO的的Texture的名字。如果你传入“0”,就会清除指定的attachment位置上的附着物(不管附着物是什么)。

    因为Texture可能包含多个Image,你必须详细说明要将哪个Image附着到附着点。除textarget之外,参数都符合上文的定义。

    当附着一个非cubemap的Texture时,textarget应当是合适的类型:GL_TEXTURE_1D, GL_TEXTURE_2D_MULTISAMPLE等。当附着一个非数组的cubemap时,你必须使用glFramebufferTexture2D函数,且textarget必须是cubemap binding的6个target之一。当附着一个cubemap array时,你必须使用TextureLayer,用layer标识layer-face。

    注意:如果OpenGL4.5或ARB_direct_state_access可用,那么glFramebufferTextureLayer可以接受非数组cubemap类型的Texture。他会被视作只有1个layer(即6个layer-face)的数组cubemap类型的Texture。这意味着你永远不需要使用glFramebufferTexture2D或者glFramebufferTexture1D

    又注意:有一个函数glFramebufferTexture3D,专用于3D Texture。但是你不应该使用他,因为TextureLayer函数能够完成他所有的功能。

    Attaching Renderbuffer

    Renderbuffers也可以被附着到FBO。实际上,这也是除了创建他们之外唯一的使用方法。

    1 void glFramebufferRenderbuffer(GLenum target​, GLenum attachment​, GLenum renderbuffertarget​, GLuint renderbuffer​);

    参数与附着Texture的类似。参数renderbuffertarget必须是GL_RENDERBUFFER,参数renderbuffer是renderbuffer的名字。

    Layered Images

    Layered Image,如前所述,是一组有序的大小相同的Image。多种Texture都可以被认为是layered。

    1D或2D Array Texture的1个mipmap level就是一个Layered Image,数组的元素数就是层数。3D Texture的1个mipmap level同样也是一个Layered Image,层数就是此mipmap level的depth。Cubemap Texture的1个mipmap level也是一个Layered Image,他有且只有6个layer,每个face是一个,且face的顺序与下面的枚举值相同:

    Layer number

    Cubemap face

    0

    GL_TEXTURE_CUBE_MAP_POSITIVE_X

    1

    GL_TEXTURE_CUBE_MAP_NEGATIVE_X

    2

    GL_TEXTURE_CUBE_MAP_POSITIVE_Y

    3

    GL_TEXTURE_CUBE_MAP_NEGATIVE_Y

    4

    GL_TEXTURE_CUBE_MAP_POSITIVE_Z

    5

    GL_TEXTURE_CUBE_MAP_NEGATIVE_Z

    对于cubemap array texture,Layer 代表的是layer-face的索引。他是带layer的face,按上表排列。所以如果你想渲染到第三个layer的+z face,你就要设置gl_Layer 为(2 * 6) + 4或者16。

    每个Texture,被用作Layered Image的时候,都有特定数量的layer。对于Array Texture或3D Texture,layer数就是Texture 的depth。对于cubemap,总是有且只有6个layer:每个face即为1个layer。Cubmap Array 有6*layer(layer-face数)。

    使用下述指令可以将Texture的一个mipmap level附着为一个Layered Image:

    1 void glFramebufferTexture(GLenum target​, GLenum attachment​, GLuint texture​, GLint level​);

    参数含义与上文的相同。实际上,如果你不要求附着Array Texture, Cubemap或3D Texture的单独一个Image,那么这个函数可以代替很多glFramebufferTexture1D,2D或Layer。但如果texture是这种情况,那么给定的整个mipmap level将作为一个Layered Image整体被附着,即此Layered Image里所有的layer都会被附着。(译者注:有什么用呢?下面立即分解)

    Layered Image用于Layered Rendering,即向FBO的不同Layer发送不同的图元(在同一次渲染中形成不同的图像)。

    Empty framebuffers

    有时候会需要向一个没有附着对象的FBO渲染。显然fragment shader的输出不会写入到任何地方,但是渲染过程还是可以正常进行的。这对于shader的arbitrary reading and writing of image data是有用的。

    但是,图元的渲染总是基于FBO的性质(大小,sample数量等)进行的,这些性质通常由被附着的Image定义。如果没有附着Image,这些性质就必须用其它的方式定义。

    没有附着Image的FBO的性质可以用下述函数设置:

    1 void glFramebufferParameteri(GLenum target​, GLenum pname​, GLint param​);

    target是FBO绑定的位置。如果要设置width,就设pname为GL_FRAMEBUFFER_DEFAULT_WIDTH;,如果要设置height,就设pname为GL_FRAMEBUFFER_DEFAULT_HEIGHT。

    Layered FBO可以通过设置GL_FRAMEBUFFER_DEFAULT_LAYERS 为大于0的值来模仿。Multisample FBO可以通过设置GL_FRAMEBUFFER_DEFAULT_SAMPLES 为大于0的值来模仿。Fixed multisample位置可以通过设置GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS 为非零值来模仿。

    注意,仅在FBO没有附着对象的时候,这些参数才会起作用。如果附着了Image,那么这些参数会被无视。你应该仅在你想要使用无Image的FBO时才设置这些值。

    Framebuffer Completeness

    FBO里每个附着点都对能附着的Image的格式有要求。但是,如果附着了不符合要求的Image,不会立即产生GL error。在使用不合适的设置的FBO时才会引发错误。为了安全地使用FBO,必须检测各种可能出现的问题(例如Image的大小等)。

    一个可以正常使用的FBO被称作是“完整的FBO”。想要测试FBO的完整性,请调用这个函数:

    1 GLenum glCheckFramebufferStatus(GLenum target​);

    你不是非得调用这个函数不可。但是,使用不完整的FBO是错误的,所以检测一下总是好的。

    如果FBO能用,会返回GL_FRAMEBUFFER_COMPLETE 。否则就是有问题。

    FBO in C#

    FBO最复杂的操作就是Attach不同类型的Texture。根据上文,可以总结出来,只需要glFramebufferTexture和glFramebufferTextureLayer两个函数就可以实现对所有类型Texture的Attach的支持。Wiki说OpenGL3.2开始才支持glFramebufferTexture,这我就不管了。

     1         /// <summary>
     2         /// Attach a level of the <paramref name="texture"/> as a logical buffer to the currently bound framebuffer object.
     3         /// If there are multiple images in one mipmap level of the <paramref name="texture"/>, then we will start 'layered rendering'.
     4         /// <para>Bind() this framebuffer before invoking this method.</para>
     5         /// </summary>
     6         /// <param name="target">GL_FRAMEBUFFER is equivalent to GL_DRAW_FRAMEBUFFER</param>
     7         /// <param name="texture">Specifies the texture object to attach to the framebuffer attachment point named by <paramref name="location"/>.</param>
     8         /// <param name="location">Specifies the attachment point of the framebuffer.</param>
     9         /// <param name="mipmapLevel">Specifies the mipmap level of <paramref name="texture"/> to attach.</param>
    10         public void Attach(FramebufferTarget target, Texture texture, AttachmentLocation location, int mipmapLevel = 0)
    11         {
    12             if (texture == null) { throw new ArgumentNullException("texture"); }
    13 
    14             if (location == AttachmentLocation.Color)
    15             {
    16                 if (this.nextColorAttachmentIndex >= Framebuffer.maxColorAttachmentCount)
    17                 { throw new IndexOutOfRangeException("Not enough color attach points!"); }
    18 
    19                 glFramebufferTexture((uint)target, GL.GL_COLOR_ATTACHMENT0 + this.nextColorAttachmentIndex, texture != null ? texture.Id : 0, mipmapLevel);
    20                 this.nextColorAttachmentIndex++;
    21             }
    22             else
    23             {
    24                 glFramebufferTexture((uint)target, (uint)location, texture != null ? texture.Id : 0, mipmapLevel);
    25             }
    26         }
    27 
    28         /// <summary>
    29         /// Attach a single layer of a <paramref name="cubemapArrayTexture"/> to the currently bound framebuffer object.
    30         /// <para>Bind() this framebuffer before invoking this method.</para>
    31         /// </summary>
    32         /// <param name="target">GL_FRAMEBUFFER is equivalent to GL_DRAW_FRAMEBUFFER</param>
    33         /// <param name="cubemapArrayTexture">texture must either be null or an existing cube map array texture.</param>
    34         /// <param name="location">attachment point.</param>
    35         /// <param name="layer">Specifies the layer of <paramref name="cubemapArrayTexture"/> to attach.</param>
    36         /// <param name="face">Specifies the face of <paramref name="cubemapArrayTexture"/> to attach.</param>
    37         /// <param name="mipmapLevel">Specifies the mipmap level of <paramref name="cubemapArrayTexture"/> to attach.</param>
    38         public void Attach(FramebufferTarget target, Texture cubemapArrayTexture, AttachmentLocation location, int layer, CubemapFace face, int mipmapLevel = 0)
    39         {
    40             this.Attach(target, cubemapArrayTexture, location, (layer * 6 + (int)((uint)face - GL.GL_TEXTURE_CUBE_MAP_POSITIVE_X)), mipmapLevel);
    41         }
    42 
    43         /// <summary>
    44         /// Attach a single layer of a <paramref name="texture"/> to the currently bound framebuffer object.
    45         /// <para>Bind() this framebuffer before invoking this method.</para>
    46         /// </summary>
    47         /// <param name="target">GL_FRAMEBUFFER is equivalent to GL_DRAW_FRAMEBUFFER</param>
    48         /// <param name="texture">texture must either be null or an existing three-dimensional texture, one- or two-dimensional array texture, cube map array texture, or multisample array texture.</param>
    49         /// <param name="location">attachment point.</param>
    50         /// <param name="layer">Specifies the layer of <paramref name="texture"/> to attach.</param>
    51         /// <param name="mipmapLevel">Specifies the mipmap level of <paramref name="texture"/> to attach.</param>
    52         public void Attach(FramebufferTarget target, Texture texture, AttachmentLocation location, int layer, int mipmapLevel = 0)
    53         {
    54             if (location == AttachmentLocation.Color)
    55             {
    56                 if (this.nextColorAttachmentIndex >= Framebuffer.maxColorAttachmentCount)
    57                 { throw new IndexOutOfRangeException("Not enough color attach points!"); }
    58 
    59                 glFramebufferTextureLayer((uint)target, GL.GL_COLOR_ATTACHMENT0 + this.nextColorAttachmentIndex, texture != null ? texture.Id : 0, mipmapLevel, layer);
    60                 this.nextColorAttachmentIndex++;
    61             }
    62             else
    63             {
    64                 glFramebufferTextureLayer((uint)target, (uint)location, texture != null ? texture.Id : 0, mipmapLevel, layer);
    65             }
    66         }
    Attach Texture

    总结

  • 相关阅读:
    not(expr|ele|fn)从匹配元素的集合中删除与指定表达式匹配的元素
    has(expr|ele)保留包含特定后代的元素,去掉那些不含有指定后代的元素。
    map(callback)将一组元素转换成其他数组(不论是否是元素数组)
    is(expr|obj|ele|fn)
    filter(expr|obj|ele|fn)筛选出与指定表达式匹配的元素集合。
    eq(index|-index)
    clone([Even[,deepEven]])克隆匹配的DOM元素并且选中这些克隆的副本。
    detach([expr]) 从DOM中删除所有匹配的元素。
    scrollTop([val])
    offset([coordinates])
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-47-hello-framebuffer.html
Copyright © 2020-2023  润新知