文章出處

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

總結

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()