OpenGL超級(jí)寶典第七版簡(jiǎn)體中文-第五章:數(shù)據(jù)(二部分)

一致區(qū)塊(Uniform Blocks)

有一天我們將會(huì)要編寫(xiě)非常復(fù)雜的著色器。其中的一些需要很多的常量數(shù)據(jù),使用一致變量傳遞所有的數(shù)據(jù)變得十分低效。如果我們的應(yīng)用中有很多的著色器,我們需要調(diào)用各種glUniform*()函數(shù)來(lái)一一設(shè)置這些著色器。我們還需要記錄各個(gè)一致變量的變化情況。有一些變化發(fā)生在每個(gè)對(duì)象上,有一些變化發(fā)生在每一幀,而另一些可能只需要初始化一次。這意味著我們要么我們?cè)诓煌膱?chǎng)合更新不同的一致變量(這使得應(yīng)用難以維護(hù))或者在所有時(shí)候更新全部的一致變量(這使用應(yīng)用變得低效)。

為了避免對(duì)所有的一致變量都調(diào)用一次glUniform*(),使得更新大批量的一致變量更容易,以及在不同程式(program)間方便地共享一系列的一致變量,OpenGL允許我們將一致變量編組為一個(gè)一致區(qū)塊并將整個(gè)區(qū)塊存入一個(gè)緩沖對(duì)象(buffer object)。這里的緩沖對(duì)象與我們之前討論過(guò)的一樣。通過(guò)改變緩沖綁定(buffer binding)或者覆寫(xiě)綁定緩沖(bound buffer)的內(nèi)容使得我們可以快速地設(shè)置整個(gè)組內(nèi)的一致變量的值。我們還可以在改變程式的情況下使得緩沖保持綁定,這樣新的程式將會(huì)看到當(dāng)前一系列一致變量的值。這個(gè)功能稱(chēng)為一致變量緩沖對(duì)象(uniform buffer object - UBO)。事實(shí)上迄今為止我們所使用過(guò)的一致變量都存于缺省區(qū)塊(default block)。在著色器全局范圍內(nèi)聲明的任何一致變量都存于缺省區(qū)塊。我們無(wú)法將缺省區(qū)塊存入一致變量緩沖對(duì)象,要使用一致變量緩沖對(duì)象我們需要?jiǎng)?chuàng)建至少一個(gè)有名一致區(qū)塊。

為了將一系列的一致變量存入一個(gè)緩沖對(duì)象,我們需要在著色器中使用一個(gè)有名一致區(qū)塊。這看起來(lái)很像第三章中的數(shù)據(jù)塊接口(Interface Blocks),但一致區(qū)塊使用uniform關(guān)鍵字而不是in或者out。清單5.9展示了在著色器中一致區(qū)塊的代碼看起來(lái)是什么樣。

uniform TransformBlock
{
    float scale;    // Global scale to apply to everything
    vec3 translation;   // Translation in X,Y and Z
    float rotation[3];  // Rotation around X,Y and Z axes
    mat4 projection_matrix; // A generalized projection matrix to apply
                            // after scale and rotate
} transform;

清單5.9: 一致區(qū)塊聲明示例

這段代碼聲明了一個(gè)名為TransformBlock的一致區(qū)塊,同時(shí)還聲明了一個(gè)名為transform的實(shí)例。在著色器代碼中我們可以用它的實(shí)例名字transform(比如transform.scale或者transform.projection_matrix)來(lái)引用一致區(qū)塊的成員。不過(guò)我們?nèi)粝朐O(shè)置用以供給數(shù)據(jù)的緩沖對(duì)象的數(shù)據(jù),我們需要知道一致區(qū)塊的名字TransformBlock。如果我們想擁有多個(gè)一致區(qū)塊的實(shí)例,且每個(gè)實(shí)例使用自己的緩沖對(duì)象,我們可以將transform開(kāi)辟為一個(gè)數(shù)組。一致區(qū)塊內(nèi)的成員在每個(gè)實(shí)例內(nèi)都擁有同樣的位置,不過(guò)在著色器內(nèi)我們可以引用一致區(qū)塊的好幾個(gè)實(shí)例。在接下來(lái)的章節(jié)我們將會(huì)看到,當(dāng)我們要為一致區(qū)塊填充數(shù)據(jù)時(shí),檢索一致區(qū)塊內(nèi)的成員位置是十分重要的。

構(gòu)建一致區(qū)塊

著色器中的具名一致區(qū)塊的數(shù)據(jù)可以存入緩沖對(duì)象。通常來(lái)講填充緩沖對(duì)象(使用glBufferData()以及glMapBuffer())是應(yīng)用的任務(wù)。問(wèn)題是緩沖對(duì)象中的數(shù)據(jù)看起來(lái)是怎樣的?實(shí)際上有兩種可能性,選擇哪一種都是一種權(quán)衡。

第一種方法是使用一種標(biāo)準(zhǔn)的、普遍贊成的數(shù)據(jù)布局。這意味著我們的應(yīng)用程式可以簡(jiǎn)單地將數(shù)據(jù)拷貝到緩沖對(duì)象中并假定一致區(qū)塊內(nèi)的成員都有特定的位置--甚至于我們可以事先將數(shù)據(jù)存儲(chǔ)在磁盤(pán)上然后將數(shù)據(jù)從磁盤(pán)直接讀入到映射的緩沖對(duì)象(使用glMapBuffer()映射)內(nèi)。標(biāo)準(zhǔn)布局可能會(huì)在一致區(qū)塊內(nèi)的各個(gè)成員間留有空白,這使得緩沖對(duì)象會(huì)比本來(lái)的要大,并且這種便捷可能會(huì)損失一些性能。盡管如此,使用標(biāo)準(zhǔn)布局在所有情況下幾乎都是安全的。

另外一種方法是讓OpenGL來(lái)決定如何放置數(shù)據(jù)。這能生成最高效的著色器,但這樣也意味著我們的應(yīng)用需要搞明白把數(shù)據(jù)放在哪才能讓OpenGL順利讀取。在這種情況下,存儲(chǔ)在一致區(qū)塊緩沖對(duì)象中的數(shù)據(jù)被以共享布局(shared layout)進(jìn)行排列。這是缺省的布局方式,在我們不進(jìn)行任何其他顯式指定的情況下OpenGL將會(huì)這么辦。在共享布局下數(shù)據(jù)在一致區(qū)塊緩沖對(duì)象中的布局由OpenGL依據(jù)運(yùn)行時(shí)最高效能和著色器最佳訪(fǎng)問(wèn)決定。在某些情況下這能為著色器帶來(lái)最大的效能,但隨之而來(lái)的會(huì)給應(yīng)用程式增加工作量。這種布局之所以被稱(chēng)為共享布局,是因?yàn)橐坏㎡penGL將緩沖對(duì)象內(nèi)的數(shù)據(jù)排列好,這種排列方式將在共享同樣的一致區(qū)塊聲明的多個(gè)程式和著色器間保持一致。這使得我們可以和任何程式使用同一個(gè)緩沖對(duì)象。為了使用共享布局,應(yīng)用程式必須獲知緩沖對(duì)象中一致區(qū)塊內(nèi)的各個(gè)成員的位置。

首先我們會(huì)描述標(biāo)準(zhǔn)布局(standard layout),這也是我們推薦使用的布局方式(盡管它不是缺省的布局方式)。為了指示OpenGL我們要使用標(biāo)準(zhǔn)布局,我們必須在聲明一致區(qū)塊時(shí)帶上布局指示器。在清單5.10中我們展示了使用標(biāo)準(zhǔn)布局指示器(std140)來(lái)聲明一致區(qū)塊TransformBlock

layout (std140) uniform TransformBlock
{
    float scale;    // Global scale to everything
    vec3 translation;   // Translation in X,Y and Z
    float rotation[3];  // Rotation around X,Y and Z axes
    mat4 projection_matrix; // A generalized projection matrix to
                            // apply after scale and rotate
} transform;

清單5.10: 使用std140布局聲明一致區(qū)塊

一旦一致區(qū)塊使用標(biāo)準(zhǔn)布局(std140)進(jìn)行了聲明,那區(qū)塊的每個(gè)成員占用的空間即為預(yù)定的,并且在緩沖對(duì)象中開(kāi)始的偏移也是可預(yù)知的,這些都是通過(guò)一套規(guī)則可以演算出的。我們總結(jié)一下這套規(guī)則:

在緩沖中任何占用N個(gè)字節(jié)的類(lèi)型都始于緩沖的N字節(jié)邊界處。這意味著諸如intfloat、bool這些標(biāo)準(zhǔn)GLSL數(shù)據(jù)類(lèi)型(皆為32位即4字節(jié)長(zhǎng))都會(huì)始于4字節(jié)倍數(shù)處。長(zhǎng)度為2的這些類(lèi)型的向量則總是始于2N字節(jié)邊界處。比如在內(nèi)存中長(zhǎng)度為8字節(jié)的vec2則總是始于8字節(jié)邊界處。三或者四元素向量則總是始于4N字節(jié)邊界處。比如vec3vec4類(lèi)型始于16字節(jié)邊界處。數(shù)組的每個(gè)成員(標(biāo)量或是向量類(lèi)型,比如int數(shù)組或者vec3數(shù)組)也依照這些規(guī)則總是始于某個(gè)邊界處,不過(guò)這個(gè)邊界總是湊足vec4來(lái)算的。這意味著除了vec4數(shù)組(以及Nx4矩陣)以外其他類(lèi)型都不會(huì)被緊湊打包,在每個(gè)元素之間都會(huì)有間隔。矩陣被當(dāng)做較短的向量數(shù)組,矩陣數(shù)組被當(dāng)做很長(zhǎng)的向量數(shù)組。最后,結(jié)構(gòu)體和結(jié)構(gòu)體數(shù)組還有額外的打包要求,整個(gè)結(jié)構(gòu)體始于它的最長(zhǎng)的成員的邊界處,這個(gè)邊界湊足vec4的長(zhǎng)度。

特別要注意的是std140布局和諸如C++之類(lèi)的應(yīng)用程式語(yǔ)言的打包規(guī)則之間的區(qū)別。尤其是一致區(qū)塊內(nèi)的數(shù)組是不一定會(huì)緊湊打包的。這意味我們沒(méi)法在一致區(qū)塊中創(chuàng)建一個(gè)float數(shù)組,然后簡(jiǎn)單地從一個(gè)floatC數(shù)組拷貝數(shù)據(jù)進(jìn)去,因?yàn)镃數(shù)組的數(shù)據(jù)會(huì)被緊湊打包而一致區(qū)塊內(nèi)的數(shù)組數(shù)據(jù)不會(huì)。

這聽(tīng)起來(lái)很復(fù)雜,但它是合符邏輯且定義良好的,并且使得很多的圖形硬件可以高效地實(shí)現(xiàn)一致區(qū)塊緩沖對(duì)象。回到我們的TransformBlock示例,我們可以通過(guò)上述規(guī)則計(jì)算出區(qū)塊的各個(gè)成員在緩沖對(duì)象內(nèi)的偏移。清單5.11展示了一個(gè)一致區(qū)塊的聲明,并附有各個(gè)成員的偏移:

layout (std140) uniform TransformBlock
{
    // Member               base-alignment  offset  aligned-offset
    float scale;            // 4            0       0
    vec3 translation;       // 16           4       16
    float rotation[3];      // 16           28      32 (rotation[0])
                            //                      48 (rotation[1])
                            //                      64 (rotation[2])
    mat4 projection_matrix; // 16           80      80 (column 0)
                            //                      96 (column 1)
                            //                      112 (column 2)
                            //                      128 (column 3)
} transform;

清單5.11: 一致區(qū)塊示例和偏移

在原始的ARB_uniform_buffer_object擴(kuò)展規(guī)格文檔有各種類(lèi)型的對(duì)齊的完整示例。

在使用std140布局時(shí)還可以在著色器中直接指定一致區(qū)塊內(nèi)各成員的偏移。當(dāng)然這種情況下我們?nèi)孕枳裱?strong>std140布局的對(duì)齊規(guī)則,但我們可以在成員間留一些空白以及?無(wú)次序地聲明成員。使用offset布局指示器來(lái)指定一致區(qū)塊內(nèi)成員的偏移。清單5.12示一例:

layout (std140) uniform ManuallyLaidOutBlock
{
    layout (offset = 32) vec4 foo;  // At offset 32 bytes
    layout (offset = 8) vec2 bar;   // At offset 8 bytes
    layout (offset = 48) vec3 baz;  // At offset 48 bytes
} myBlock;

清單5.12: 帶有用戶(hù)指定偏移的一致區(qū)塊

我們注意到在清單5.12中的一致區(qū)塊的第一個(gè)成員foo被聲明為始于區(qū)塊的偏移32處。這是沒(méi)問(wèn)題的,因?yàn)?2是16的整數(shù)倍(16是vec4的大小),并且bar始于偏移8處也是符合對(duì)齊需求的(8是vec2的大小)。不過(guò)bar在內(nèi)存中是在foo之前的,這是因?yàn)槲覀儾](méi)有按照次序指定它們的位置。然后我們聲明baz于偏移48處。盡管bazvec3類(lèi)型的,但它必須始于16字節(jié)邊界處。

我們還可以顯示地讓數(shù)據(jù)類(lèi)型對(duì)齊在它們?cè)紝?duì)齊邊界的整數(shù)倍處。我們可以使用align布局指示器達(dá)成比目的。這和offset布局指示器的用法一致,但它只是簡(jiǎn)單地將成員推到指定對(duì)齊邊界的整數(shù)倍處,只要這個(gè)邊界值符合成員的對(duì)齊要求。align指示器還可以用于整個(gè)一致區(qū)塊以強(qiáng)制所有成員對(duì)齊到指定邊界處。我們可以同時(shí)使用alignoffset將成員推到大于它原始偏移的下一個(gè)偏移處或者等于它原始偏移的偏移處。

在清單5.13中我們使用偏移16重新聲明了ManuallyLaidOutBlock。這個(gè)對(duì)齊邊界滿(mǎn)足vec4vec3的需求,所以foobaz的偏移不會(huì)受到影響。但bar的原始對(duì)齊邊界是8,這并不是16的整數(shù)倍。所以bar會(huì)被對(duì)齊到指定對(duì)齊邊界(也就是16)的下一個(gè)16字節(jié)邊界處。

layout (stdout, align = 16) uniform ManuallyLaidOutBlock
{
    layout (offset = 32) vec4 foo;  // At offset 32 bytes
    layout (offset = 8) vec2 bar;   // At offset 16 bytes
    layout (offset = 48) vec3 baz;  // At offset 48 bytes
}

清單5.13:帶有用戶(hù)指定偏移和對(duì)齊邊界的一致區(qū)塊

當(dāng)然,我們可以選擇使用共享布局(shared layout)把所有的事情都丟給OpenGL,并且這樣OpenGL可能會(huì)生成一個(gè)比std140略微高效的布局,但這么一點(diǎn)高效可能并不值得附加的額外工作。如果我們決意要使用共享布局,那我們可以確定出OpenGL為每個(gè)成員賦予的偏移。一致區(qū)塊內(nèi)的每個(gè)成員都有一個(gè)索引值,這個(gè)索引值可以用來(lái)引用這個(gè)成員以便查找成員在區(qū)塊中的大小和位置。要獲取一致區(qū)塊內(nèi)成員的索引值,調(diào)用:

void glGetUniformIndices(GLuint program,
                         GLsizei uniformCount,
                         const GLchar ** uniformNames,
                         GLuint * uniformIndices);

通過(guò)這個(gè)函數(shù)的一次調(diào)用我們就可以獲得一大批一致變量的索引值(甚至是整個(gè)程式的一致變量),盡管它們是不同一致區(qū)塊的成員。它接收一個(gè)參數(shù)(uniformCount)用來(lái)表示要獲取索引值的一致變量的個(gè)數(shù)、一個(gè)一致變量名稱(chēng)的數(shù)組(uniformNames)以及最終存放獲取的索引值的數(shù)組(uniformIndices)。清單5.14展示了我們?nèi)绾潍@取TransformBlock各個(gè)成員的索引值:

static const GLchar * uniformNames[4] = 
{
    "TransformBlock.scale",
    "TransformBlock.translation",
    "TransformBlock.rotation",
    "TransformBlock.projection_matrix"
};
GLuint uniformIndices[4];

glGetUniformIndices(program, 4, uniformNames, uniformIndices);

清單5.14: 獲取一致區(qū)塊成員的索引值

這段代碼運(yùn)行之后,一致區(qū)塊的四個(gè)成員的偏移將會(huì)被存放在數(shù)組uniformIndices中?,F(xiàn)在我們掌握了一致變量的索引值,我們可以用它們來(lái)獲取一致區(qū)塊成員在緩沖中的位置。我們可以調(diào)用:

void glGetActiveUniformsiv(GLuint program,
                           GLsizei uniformCount,
                           const GLuint * uniformIndices,
                           GLenum pname,
                           GLint * params);

這個(gè)函數(shù)可以獲取指定一致區(qū)塊成員的許多信息。我們感興趣的信息有成員在緩沖內(nèi)的偏移、數(shù)組跨度(對(duì)于TransformBlock.rotation)以及矩陣跨度(TransformBlock.projection_matrix)。這些值告訴我們?cè)摪褦?shù)據(jù)置于緩沖的何處以便著色器使用。我們可以將pname分別設(shè)置為GL_UNIFORM_OFFSET、GL_UNIFORM_ARRAY_STRIDE、GL_UNIFORM_MATRIX_STRIDE從而獲得這些值。清單5.15展示了這些代碼:

GLint uniformOffsets[4];
GLint arrayStrides[4];
GLint matrixStrides[4];
glGetActiveUniformsiv(program, 4, uniformIndices, GL_UNIFORM_OFFSET, uniformOffsets);
glGetActiveUniformsiv(program, 4, uniformIndices, GL_UNIFORM_ARRAY_STRIDE, arrayStrides);
glGetActiveUniformsiv(program, 4, uniformIndices, GL_UNIFORM_MATRIX_STRIDE, matrixStrides);

清單5.15:獲取一致區(qū)塊成員的信息

清單5.15的代碼運(yùn)行之后,uniformOffsets包含有區(qū)塊TransformBlock成員的偏移值,arrayStrides包含有數(shù)組成員的跨度(現(xiàn)在只有rotation),matrixStrides包含有矩陣成員的跨度(現(xiàn)在只有projection_matrix)。

其他可以獲得的關(guān)于一致區(qū)塊成員的信息包含一致變量的數(shù)據(jù)類(lèi)型、占用的內(nèi)存大小以及區(qū)塊內(nèi)數(shù)組和矩陣的相關(guān)信息。為了初始化有更多復(fù)雜數(shù)據(jù)類(lèi)型的緩沖對(duì)象我們會(huì)用到其中的一些信息,盡管在編寫(xiě)著色器時(shí)成員的大小和數(shù)據(jù)類(lèi)型我們應(yīng)該是已經(jīng)知道的。pname其他可接收的值和返回?cái)?shù)據(jù)在表5.4中進(jìn)行羅列:

value of pname                              what you get back
GL_UNIFORM_TYPE                             一致變量的數(shù)據(jù)類(lèi)型(GLenum)
GL_UNIFORM_SIZE                             數(shù)組的大小(以GL_UNIFORM_TYPE為單位)。
                                            若一致變量不是數(shù)組,則總為1.
GL_UNIFORM_NAME_LENGTH                      一致變量名稱(chēng)長(zhǎng)度
GL_UNIFORM_BLOCK_INDEX                      一致變量所屬區(qū)塊的索引值
GL_UNIFORM_OFFSET                           一致變量在區(qū)塊內(nèi)的偏移值
GL_UNIFORM_ARRAY_STRIDE                     數(shù)組內(nèi)相鄰成員間的字節(jié)間隔。
                                            若一致變量不是數(shù)組,則總為0.
GL_UNIFORM_MATRIX_STRIDE                    列優(yōu)先矩陣的每個(gè)列首元素間的字節(jié)間隔
                                            或者行優(yōu)先矩陣的每個(gè)行首元素間的字節(jié)間隔。
                                            若一致變量不是矩陣,則總為0.
GL_UNIFORM_IS_ROW_MAJOR                     若一致變量為行優(yōu)先的矩陣,則為1,反之為0.

表5.4:glGetActiveUniformsiv()可使用的一致變量查詢(xún)參數(shù)

如果我們感興趣的一致變量的類(lèi)型都是簡(jiǎn)單類(lèi)型,比如: int, float, bool甚至這些變量的向量(vec4之類(lèi)),那我們需要的就只是它們的偏移。一旦我們獲知這些一致變量在緩沖中的位置,那我們就可以通過(guò)將偏移傳遞給glBufferSubData()來(lái)更新適當(dāng)位置的數(shù)據(jù),或者直接用代碼中在內(nèi)存中組裝號(hào)緩沖。我們演示后一種情況,因?yàn)檫@能再次凸顯出一致變量是存儲(chǔ)在內(nèi)存中的,一如頂點(diǎn)信息可以存儲(chǔ)在緩沖中。同時(shí)這樣也會(huì)減少對(duì)OpenGL的調(diào)用次數(shù),有時(shí)這會(huì)帶來(lái)更高的性能。在下面的例子中我們?cè)趹?yīng)用程序的內(nèi)存中組裝好數(shù)據(jù)然后用glBufferSubData()來(lái)載入到緩沖。當(dāng)然我們還可以用glMapBufferRange()得到緩沖內(nèi)存區(qū)域的指針,然后直接將數(shù)據(jù)組裝到里面。讓我們從一致區(qū)塊TransformBlock中最簡(jiǎn)單的一致變量scale開(kāi)始設(shè)置。這個(gè)一致變量是一個(gè)單精度浮點(diǎn)數(shù),它的位置存儲(chǔ)在數(shù)組uniformIndices的第一個(gè)元素中。清單5.16展示了如果設(shè)置這個(gè)單精度浮點(diǎn)數(shù)的值。

// 為緩沖分配內(nèi)存(記得要釋放)
unsigned char* buffer = (unsigned char*)malloc(4096);

// TransformBlock.scale存儲(chǔ)在uniformOffset[0]偏移處,所以我們可以直接偏移到此處然后將scale存儲(chǔ)至此
*((float*)(buffer+uniformOffsets[0])) = 3.0f;

清單5.16:設(shè)置一致區(qū)塊中的一個(gè)單精度浮點(diǎn)數(shù)

接下來(lái)我們初始化TransformBlock.translation的數(shù)據(jù)。這是一個(gè)vec3,這意味著它由3個(gè)在內(nèi)存中緊湊打包的單精度浮點(diǎn)數(shù)組成。要更新它的數(shù)據(jù),我們只需要找到這個(gè)向量的第一個(gè)元素的位置并將3個(gè)連續(xù)的單精度浮點(diǎn)數(shù)存儲(chǔ)在那。我們?cè)谇鍐?.17展示。

// 在內(nèi)存中存儲(chǔ)3個(gè)連續(xù)的GLfloat值來(lái)更新vec3
((float*)(buffer+uniformOffsets[1]))[0] = 1.0f;
((float*)(buffer+uniformOffsets[1]))[1] = 2.0f;
((float*)(buffer+uniformOffsets[1]))[2] = 3.0f;

清單5.17:獲取一致區(qū)塊成員的各個(gè)索引

現(xiàn)在來(lái)看看rotation數(shù)組。這里我們本可以用一個(gè)vec3,但是為了做為示例,我們還是用一個(gè)三元素的數(shù)組從而演示GL_UNIFORM_ARRAY_STRIDE參數(shù)的使用。當(dāng)使用共享布局時(shí),數(shù)組元素之間被一個(gè)由具體實(shí)現(xiàn)定義的間隔隔開(kāi),這個(gè)間隔的大小單位是字節(jié)。這意味著我們把數(shù)據(jù)存放到緩沖中時(shí),得同時(shí)考慮GL_UNIFORM_OFFSETEGL_UNIFORM_ARRAY_STRIDE,清單5.18展示這種情況。

// TransformBlock.ratations[0]存放在緩沖的uniformOffsets[2]處。數(shù)組中的每個(gè)元素都有多個(gè)arrayStrides[2]字節(jié)間隔。
const GLfloat rotations[] = { 30.0f, 40.0f, 60.0f };
unsigned int offset = uniformOffsets[2];

for (int n = 0; n < 3; n++)
{
    *((float*)(buffer+offset)) = rotations[n];
    offset += arrayStrides[2];
}

清單5.18:指定一致區(qū)塊中數(shù)組的數(shù)據(jù)。

最后我們來(lái)設(shè)置TransformBlock.projection_matrix

在一致區(qū)塊中矩陣(Matrix)和向量數(shù)組表現(xiàn)地極為相似。對(duì)于列優(yōu)先的矩陣(缺省情況)來(lái)說(shuō),矩陣的每一列即被當(dāng)做一個(gè)向量,這個(gè)向量的長(zhǎng)度即為矩陣的高度。同樣的,一個(gè)行優(yōu)先的矩陣也被當(dāng)做一個(gè)向量數(shù)組對(duì)待,只是數(shù)組的每一個(gè)元素將是矩陣的一行。一如正常的數(shù)組,矩陣的每個(gè)行(列)的起始偏移由具體的實(shí)現(xiàn)定義。這個(gè)值可傳遞參數(shù)GL_UNIFORM_MATRIX_STRIDE來(lái)調(diào)用函數(shù)glGetActiveUniformsiv()獲知。矩陣的每列可使用類(lèi)似設(shè)置TransformBlock.translationvec3的代碼來(lái)設(shè)置。在清單5.19中我們將進(jìn)行示例。

// the first column of TransformBlock.project_matrix is at 
// uniformOffsets[3] bytes into the buffer. The columns are
// spaced matrixStride[3] bytes apart and are essentially vec4s.
// This is the source matrix - remember, it's column major.

const GLfloat matrix[] = 
{
    1.0f, 2.0f, 3.0f, 4.0f,
    9.0f, 8.0f, 7.0f, 6.0f,
    2.0f, 4.0f, 6.0f, 8.0f,
    1.0f, 3.0f, 5.0f, 7.0f
};

for (int col = 0; col < 4; col ++)
{
    GLuint offset = uniformOffsets[3] + matrixStride[3]*col;
    for (int row = 0; row < 4; row ++) 
    {
        *((float*)(buffer+offset)) = matrix[col*4+row];
        offset += sizeof(GLfloat);
    }
}

清單5.19:設(shè)置一致區(qū)塊的矩陣

這種查詢(xún)偏移和間距的方法適用于任何布局方式。當(dāng)然對(duì)于共享布局來(lái)說(shuō)這是唯一的辦法。顯而易見(jiàn)的是這樣搞我們得寫(xiě)很多代碼來(lái)準(zhǔn)確無(wú)誤地將數(shù)據(jù)放置到緩沖中。這也是我們?yōu)槭裁赐扑]使用標(biāo)準(zhǔn)布局。標(biāo)準(zhǔn)布局可以使得我們依據(jù)一套標(biāo)準(zhǔn)的規(guī)則將數(shù)據(jù)放置到緩沖中。這套規(guī)則對(duì)于所有的OpenGL實(shí)現(xiàn)來(lái)說(shuō)都是通用的,所以我們可以無(wú)需查詢(xún)?nèi)魏螙|西從而使用這套規(guī)則(如果你樂(lè)意也可以查詢(xún)偏移和間距,結(jié)果一定是正確的)。有時(shí)候我們會(huì)犧牲一點(diǎn)著色器性能從而換取易用性,但從中節(jié)省出的代碼復(fù)雜性和應(yīng)用性能是值得的。

無(wú)論我們選擇哪種數(shù)據(jù)打包模式,我們都可以把程式(program)中填充數(shù)據(jù)的緩沖和一致區(qū)塊進(jìn)行綁定。在此之前我們得查詢(xún)到一致區(qū)塊的索引。程式(program)中的每個(gè)一致區(qū)塊都有一個(gè)編譯器賦予的索引。單個(gè)程式(program)中可用的一致區(qū)塊有一個(gè)上限,每個(gè)著色器階段也各有上限。我們可以調(diào)用glGetIntegerv()獲取這些上限值,使用參數(shù)GL_MAX_UNIFORM_BUFFERS獲取單個(gè)程式的一致區(qū)塊上限,還可使用GL_MAX_VERTEX_UNIFORM_BUFFERS、GL_MAX_GEOMETRY_UNIFORM_BUFFERSGL_MAX_TESS_CONTROL_UNIFORM_BUFFERS、GL_MAX_TESS_EVALUATION_UNIFORM_BUFFERSGL_MAX_FRAGMENT_UNIFORM_BUFFERS分別獲取頂點(diǎn)、圖形、細(xì)分曲面控制、細(xì)分曲面運(yùn)算和片段著色器的上限。我們可以使用

GLuint glGetUniformBlockIndex(GLuint program, const GLchar* uniformBlockName);

來(lái)獲取具名一致區(qū)塊(named uniform block)的索引。在我們的一致區(qū)塊聲明示例中,uniformBlockName應(yīng)該為"TransformBlock"。

有一系列的緩沖綁定點(diǎn)可以用來(lái)將緩沖和一致區(qū)塊綁定。將緩沖和一致區(qū)塊綁定大致分為兩個(gè)步驟。一致區(qū)塊被賦予綁定點(diǎn),然后緩沖可以綁定到綁定點(diǎn)上,由此緩沖便與一致區(qū)塊進(jìn)行了配對(duì)。通過(guò)這種綁定方式(使用綁定點(diǎn)解耦一致區(qū)塊和緩沖),不同的程式(program)可以換入換出而毋需更改緩沖綁定,固定集合中的一致變量即可為新的程式(program)所見(jiàn)。與缺省區(qū)塊中的一致變量對(duì)比而言,缺省區(qū)塊的一致變量的值是關(guān)聯(lián)于某個(gè)程式(program)的。就算兩個(gè)程式(program)中的一致變量具有同樣的名字,但它們的值都必須分別進(jìn)行設(shè)置,而且一旦當(dāng)前程式(active program)變化了它們的值也將變化。

調(diào)用

void glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);

給一致區(qū)塊綁定一個(gè)綁定點(diǎn)。其中,program是一致區(qū)塊所屬的程式(program)。uniformBlockIndex是我們要賦予綁定點(diǎn)的一致區(qū)塊索引(調(diào)用glGetUniformBlockIndex()獲得)。uniformBlockBinding是一致區(qū)塊綁定點(diǎn)的索引。一個(gè)特定的OpenGL實(shí)現(xiàn)有固定的綁定點(diǎn)上限,我們可以調(diào)用glGetIntegerv()時(shí)傳遞GL_MAX_UNIFORM_BUFFER_BINDINGS參數(shù)來(lái)獲知這一上限。

另外,我們可以在著色器代碼中直接給一致區(qū)塊指定綁定點(diǎn)索引。我們要再次用到布局指示器,這次用到的關(guān)鍵字是binding。舉個(gè)例子,給TransformBlock賦予綁定點(diǎn)索引2,我們可以如下聲明:

layout(std140, binding = 2) uniform TransformBlock
{
...
} transform;

binding布局指示器可以和std140(或者其他)指示器同時(shí)指定。在著色器代碼中賦予綁定可以避免調(diào)用glUniformBlockBinding(),甚至還可以避免調(diào)用glGetUniformBlockIndex()獲知一致區(qū)塊的索引??偟膩?lái)說(shuō),在著色器代碼中賦予綁定是更好的方法。

一旦我們?yōu)槌淌?program)中的一致區(qū)塊賦予了綁定點(diǎn)(不管是通過(guò)glUniformBlockBingding()函數(shù)或者使用布局指示器),我們就可以將緩沖綁定到同一綁定點(diǎn)上,以此讓緩沖中的數(shù)據(jù)為一致區(qū)塊所用。我們可以調(diào)用

glBindBufferBase(GL_UNIFORM_BUFFER, index, buffer);

來(lái)達(dá)此目的。GL_UNIFORM_BUFFER指示OpenGL我們將要把一個(gè)緩沖綁定到一致區(qū)塊綁定點(diǎn)上。index是綁定點(diǎn)的索引,這個(gè)索引必須與我們?cè)谥鞔a中指定的索引或者調(diào)用glUniformBlockBinding()指定的索引一致。buffer則是我們要我們要綁定的緩沖對(duì)象名字。圖示5.1刻畫(huà)了緩沖、一致區(qū)塊、綁定點(diǎn)三者之間的關(guān)系

figure 5.1

在圖示5.1中,一個(gè)程式(program)和三個(gè)一直區(qū)塊(Harry, Bob, Susan)以及三個(gè)緩沖對(duì)象(A, B, C)。Harry被賦予綁定點(diǎn)1,并且緩沖C和綁定點(diǎn)1關(guān)聯(lián),于是緩沖C的數(shù)據(jù)將供應(yīng)給Harry。同樣的Bob被賦予綁定點(diǎn)3,然后緩沖A與綁定點(diǎn)3關(guān)聯(lián),于是緩沖A的數(shù)據(jù)將供應(yīng)給Bob。同理Susan、綁定點(diǎn)0和緩沖B綁定在一起。值得注意的是綁定點(diǎn)2并沒(méi)有被用到。這不要緊。有空著的綁定點(diǎn)不用很正常。

設(shè)置如上綁定關(guān)系可以用清單5.20的代碼:

// Get the indices of the uniform blocks using glGetUniformBlockIndex
GLuint harry_index = glGetUniformBlockIndex(program, "Harry");
GLuint bob_index = glGetUniformBlockIndex(program, "Bob");
GLuint susan_index = glGetUniformBlockIndex(program, "Susan");

// Assign buffer bindings to uniform blocks, using their indices
glUniformBlockBinding(program, harry_index, 1);
glUniformBlockBinding(program, bob_index, 3);
glUniformBlockBinding(program, susan_index, 0);

// Bind buffers to the binding points
// Binding 0, buffer B, Susan's data
glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer_b);
// Binding 1, buffer C, Harry's data
glBindBufferBase(GL_UNIFORM_BUFFER, 1, buffer_c);
// Note that we skipped bingding 2
// Binding 3, buffer A, Bob's data
glBindBufferBase(GL_UNIFORM_BUFFER, 3, buffer_a);

清單5.20:指定一致區(qū)塊的綁定

如果我們?cè)谥鞔a中用binding布局指示器就可以避免調(diào)用glUniformBlockBinding()。清單5.21將進(jìn)行展示:

layout (binding = 1) uniform Harry
{
//...
};

layout (binding = 3) uniform Bob
{
//...
};

layout (binding = 0) uniform Susan
{
//...
};

清單5.21:一致區(qū)塊使用binding布局指示器

包含清單5.21代碼的著色器編譯鏈接到一個(gè)程式(program)對(duì)象后,一致區(qū)塊Harry、BobSusan將會(huì)和執(zhí)行清單5.20后設(shè)置的綁定一樣。在著色器中設(shè)置一致區(qū)塊綁定有幾個(gè)原因。首先,這樣可以降低應(yīng)用中調(diào)用OpenGL函數(shù)的數(shù)量。然后,這樣可以使得應(yīng)用在不知道一致區(qū)塊的名字(索引)下和特定綁定點(diǎn)進(jìn)行綁定。在某些情況這很有用,比如我們有一些數(shù)據(jù)以標(biāo)準(zhǔn)布局形式存放在一個(gè)緩沖中,但我們要對(duì)不同的著色器用不同的名字來(lái)引用這個(gè)緩沖。

一致區(qū)塊的一個(gè)典型用例是用來(lái)分隔常態(tài)和暫態(tài)。如果用一個(gè)標(biāo)準(zhǔn)的慣例來(lái)設(shè)置我們所有程式(program)的綁定,那我們改變當(dāng)前程式(program)的時(shí)候可以不用改動(dòng)緩沖的綁定。舉個(gè)例子,我們有一些相對(duì)比較固定的狀態(tài)(比如投影矩陣、視口大小,以及一些其他一些變化的東西),那我們可以把這些信息存放到一個(gè)緩沖中,并將這個(gè)緩沖綁定到綁定點(diǎn)0。然后我們把所有程式(program)的固定狀態(tài)與綁定點(diǎn)0綁定,那任何時(shí)候我們用glUseProgram()切換程式(program),緩沖中存儲(chǔ)的一致變量都會(huì)可用。

假設(shè)我們有一個(gè)要模擬各種材料(衣物、金屬)的片段著色器,我們可以把材料的參數(shù)放到一個(gè)緩沖中。將我們著色各種材料的程式(program)中包含材料參數(shù)的一致區(qū)塊綁定到綁定點(diǎn)1.每一個(gè)對(duì)象維護(hù)一個(gè)與之相關(guān)表面參數(shù)的緩沖對(duì)象。當(dāng)我們渲染每個(gè)對(duì)象時(shí),使用公共的材料著色器,并且簡(jiǎn)單地將相關(guān)的緩沖對(duì)象綁定到緩沖綁定點(diǎn)1即可。

使用一致區(qū)塊還有最后一個(gè)好處是它們可以非常大。一致區(qū)塊的最大尺寸可以調(diào)用glGetIntegerv()傳遞GL_MAX_UNIFORM_BLOCK_SIZE參數(shù)獲取。OpenGL保證一致區(qū)塊最小有64K字節(jié),一個(gè)程式(program)最少可以使用14個(gè)一致區(qū)塊。將上個(gè)段落的示例擴(kuò)展一點(diǎn),我們可以將所有材料的屬性打包在一個(gè)大的一致區(qū)塊中。當(dāng)我們渲染場(chǎng)景中各個(gè)對(duì)象時(shí),我們只需要使用不同的數(shù)組索引來(lái)使用不同的材料即可。比如這個(gè)索引我們可以使用靜態(tài)頂點(diǎn)屬性或者傳統(tǒng)的一致變量。這種方式一定會(huì)比替換緩沖對(duì)象的內(nèi)容或者切換不同對(duì)象的一致區(qū)塊緩沖綁定要快。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容