一致區(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é)邊界處。這意味著諸如int、float、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é)邊界處。比如vec3和vec4類(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處。盡管baz是vec3類(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í)使用align和offset將成員推到大于它原始偏移的下一個(gè)偏移處或者等于它原始偏移的偏移處。
在清單5.13中我們使用偏移16重新聲明了ManuallyLaidOutBlock。這個(gè)對(duì)齊邊界滿(mǎn)足vec4和vec3的需求,所以foo和baz的偏移不會(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_OFFSETE和GL_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_BUFFERS、GL_MAX_TESS_CONTROL_UNIFORM_BUFFERS、GL_MAX_TESS_EVALUATION_UNIFORM_BUFFERS和GL_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)系

在圖示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、Bob和Susan將會(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ū)塊緩沖綁定要快。