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

总结

 

posted @ 2017-11-20 17:19  BIT祝威  阅读(2456)  评论(2编辑  收藏  举报