[聚合文章] CSharpGL(47)你好,Framebuffer!

.Net 2017-11-20 1 阅读

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

总结

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。