OpenGL學習腳印: uniform blocks在着色器中的使用 轉自https://blog.csdn.net/wangdingqiaoit/article/details/52717963

寫在前面
目前,咱們在着色器中要傳遞多個uniform變量時,老是使用多個uniform,而後在主程序中設置這些變量的值;同時若是要在多個shader之間共享變量,例如投影矩陣projection和視變換矩陣view的話,仍然須要爲不一樣shader分別設置這些uniform變量。本節將爲你們介紹interface block,以及基於此的uniform buffer object(UBO),這些技術將簡化着色器中變量的傳遞和共享問題。本節示例程序都可以從個人github下載html

本節內容參考自:
1.www.learningopengl.com Advanced GLSL
2.GLSL Tutorial – Uniform Blocks
3.《OpenGL 4.0 Shading Language Cookbook》-Using Uniform Blocks and Uniform Buffer Objectsgit

interface block

interfac block是一組GLSL着色器裏面的輸入、輸出、uniform等變量的集合,有一些相似於C語言中的struct,可是不像struct那樣簡單明瞭,還有一些其餘的選項包含在裏面。經過使用interface block,咱們能夠將着色器中的變量以組的形式來管理,這樣書寫更整潔。github

interface block的聲明形式爲:數組

storage_qualifier block_name
{
  <define members here> } instance_name;

其中storage_qualifier指明這個block的存儲限定符,限定符可使用in​, out​, uniform​, 或者buffer​(GLSL4.3支持)等,block_name則給定名稱,而instance_name給定實例名稱。函數

例如,咱們以前在實現點光源的過程當中,頂點着色器和片元着色器之間須要傳遞法向量、紋理座標等變量,將他們封裝到一個block中,代碼顯得更緊湊。頂點着色器中輸出變量定義形式以下:佈局

// 定義輸出interface block out VS_OUT { vec3 FragPos; vec2 TextCoord; vec3 FragNormal; }vs_out;

而在片元着色器中,要以相同的block_name接受,實例名稱則能夠不一樣,形式能夠定義爲:學習

// 定義輸入interface block in VS_OUT { vec3 FragPos; vec2 TextCoord; vec3 FragNormal; }fs_in;

若是指定了instance_name,則在片元着色器中引用這些變量時須要加上instance_name前綴,例如:測試

// 環境光成分 vec3 ambient = light.ambient * vec3(texture(material.diffuseMap, fs_in.TextCoord));

反之若是沒有指定instance_name,則這個block中的變量將和uniform同樣是全局的,能夠直接使用。若是沒有給定instance_name,則須要注意,interface block中給定的變量名不要和uniform給定的重複,不然形成重定義錯誤,例以下面的定義將形成重定義錯誤:優化

uniform MatrixBlock { mat4 projection; mat4 modelview; }; uniform vec3 modelview; // 重定義錯誤 和MatrixBlock中衝突 

相比於以前以分散形式書寫這些變量,interface block可以讓你更合理的組織變量爲一組,邏輯更清晰。ui

主程序部分未變,實現的點光源效果相同,這裏仍是給出效果圖以下:

這裏寫圖片描述

從上面能夠看出,interface block確實解決了咱們一直想要合理組織着色器中變量的問題。這是咱們提到的第一個問題。

UBO的概念

本節開始提到的第二個問題是,如何在多個着色器之間簡潔的共享變量。GLSL中能夠經過uniform buffer來實現。uniform buffer的實現思路爲: 在多個着色器中定義相同的uniform block(就是上面的interface block,使用uniform限定符定義),而後將這些uniform block綁定到對應的uniform buffer object,而uniform buffer object中實際存儲這些須要共享的變量。着色器中的uniform block和主程序中的uniform buffer object,是經過OpenGL的綁定點(binding points)鏈接起來的,它們的關係以下圖所示(來自www.learningopengl.com Advanced GLSL):

uniform buffer

使用時,每一個shader中定義的uniform block有一個索引,經過這個索引鏈接到OpenGL的綁定點x;而主程序中建立uniform buffer object,傳遞數據後,將這個UBO綁定到對應的x,此後shader中的uniform block就和OpenGL中的UBO聯繫起來,咱們在程序中操做UBO的數據,就可以在不一樣着色器之間共享了。例如上圖中,着色器A和B定義的Matrices的索引都指向綁定點0,他們共享openGL的uboMatrices這個UBO的數據。同時着色器A的Lights和着色器B的Data,分別指向不一樣的UBO。

UBO的使用

在上面咱們介紹了UBO的概念,下面經過實例瞭解UBO的實際使用。UBO的實現依賴於着色器中uniform block的定義,uniform block的內存佈局四種形式:shared​, packed​, std140​, and std430​(GLSL4.3以上支持),默認是shared內存佈局。本節咱們重點學習shared和std140這兩種內存佈局形式,其餘的形式能夠在須要時自行參考OpenGL規範

  • shared 默認的內存佈局 採用依賴於具體實現的優化方案,可是保證在不一樣程序中具備相同定義的block擁有相同的佈局,所以能夠在不一樣程序之間共享。要使block可以共享必須注意block具備相同定義,同時全部成員顯式指定數組的大小。同時shared保證全部成員都是激活狀態,沒有變量被優化掉。

  • std140 這種方式明確的指定alignment的大小,會在block中添加額外的字節來保證字節對齊,於是能夠提早就計算出佈局中每一個變量的位移偏量,而且可以在shader之間共享;不足在於添加了額外的padding字節。稍後會介紹字節對齊和padding相關內容

下面經過兩個簡單例子,來熟悉std140和默認的shared內存佈局。這個例子將會在屏幕上經過4個着色器繪製4個不一樣顏色的立方體,在着色器之間共享的是投影矩陣和視變換矩陣,以及爲了演示shared layout而添加的混合顏色的示例。

layout std140

字節對齊的概念

字節對齊的一個經典案例就是C語言中的結構體變量,例以下面的結構體:

struct StructExample { char c; int i; short s; }; 

你估計它佔用內存大小多少字節? 假設在int 佔用4字節,short佔用2個字節,那麼總體大小等於 1+ 4+ 2 = 7字節嗎?

答案是否認的。在Windows平臺測試,當int佔用4個字節,short佔用2個字節是,實際佔用大小爲12個字節,這12個字節是怎麼算出來的呢? 就是用到了字節補齊的概念。實際上上述結構體的內存佈局爲:

struct StructExample { char c; // 0 bytes offset, 3 bytes padding int i; // 4 bytes offset short s; // 8 bytes offset, 2 bytes padding }; // End of 12 bytes

內存佈局以下圖所示:
內存佈局

字節對齊的一個重要緣由是爲了使機器訪問更迅速。例如在32字長的地址的機器中,每次讀取4個字節數據,因此將字節對齊到上述地址 0x0000,0x0004和0x0008, 0x000C將使讀取更加迅速。不然例如上面結構體中的int i將跨越兩個字長(0x0000和0x0004),須要兩次讀取操做,影響效率。固然關於爲何使用字節對齊的更詳細分析,感興趣地能夠參考SO Purpose of memory alignment

關於字節對齊,咱們須要知道的幾個要點就是(參考自wiki Data structure alignment):

  • 一個內存地址,當它是n字節的倍數時,稱之爲n字節對齊,這裏n字節是2的整數冪。

  • 每種數據類型都有它本身的字節對齊要求(alignment),例如char是1字節,int通常爲4字節,float爲4字節對齊,8字節的long則是8字節對齊。

  • 當變量的字節沒有對齊時,將額外填充字節(padding)來使之對齊。

上面的結構體中,int變量i須要4字節對齊,所以在char後面填充了3個字節,同時結構體變量總體大小須要知足最長alignment成員的字節對齊,所以在short後面補充了2個字節,總計達到12字節。

關於字節對齊這個概念,介紹到這裏,但願瞭解更多地能夠參考The Lost Art of C Structure Packing

std140的字節對齊

std140內存佈局一樣存在字節對齊的概念,你能夠參考官方文檔獲取完整描述。經常使用標量int,float,bool要求4字節對齊,4字節也被做爲一個基礎值N,這裏列舉幾個經常使用的結構的字節對齊要求:

類型 對齊基數(base alignment)
標量,例如 int bool 每一個標量對齊基數爲N
vector 2N 或者 4N, vec3的基數爲4N.
標量或者vector的數組 每一個元素的基數等於vec4的基數.
矩陣 以列向量存儲, 列向量基數等於vec4的基數.
結構體 元素按以前規則,同時總體大小填充爲vec4的對齊基數

例如一個複雜的uniform block定義爲:

layout (std140) uniform ExampleBlock { // // base alignment // aligned offset float value; // 4 // 0 vec3 vector; // 16 // 16 (must be multiple of 16 so 4->16) mat4 matrix; // 16 // 32 (column 0) // 16 // 48 (column 1) // 16 // 64 (column 2) // 16 // 80 (column 3) float values[3]; // 16 // 96 (values[0]) // 16 // 112 (values[1]) // 16 // 128 (values[2]) bool boolean; // 4 // 144 int integer; // 4 // 148 }; 

上面的註釋給出了它的字節對齊,其中填充了很多字節,能夠根據上面表中給定的對齊基數提早計算出來,在主程序中能夠設置這個UBO的變量:

GLuint exampleUBOId;
    glGenBuffers(1, &exampleUBOId); glBindBuffer(GL_UNIFORM_BUFFER, exampleUBOId); glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_DYNAMIC_DRAW); // 預分配空間 大小能夠提早根據alignment計算 glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBufferBase(GL_UNIFORM_BUFFER, 1, exampleUBOId); // 綁定點爲1 // step4 只更新一部分值 glBindBuffer(GL_UNIFORM_BUFFER, exampleUBOId); GLint b = true; // 布爾變量在GLSL中用4字節表示 所以這裏用int存儲 glBufferSubData(GL_UNIFORM_BUFFER, 144, 4, &b); // offset能夠根據UBO中alignment提早計算 glBindBuffer(GL_UNIFORM_BUFFER, 0);

說明: 上面最終計算出的大小爲152,UBO總體沒必要知足vec4的字節對齊要求。152 /4 = 38,知足N的對齊要求便可。

從上面能夠看到,當成員變量較多時,這種手動計算offset的方法比較笨拙,能夠事先編寫一個自動計算的函數庫,以減輕工做負擔。

std140的簡單例子

下面經過一個簡單例子來熟悉UBO的使用。

Step1: 首先咱們在頂點着色器中定義uniform block以下:

#version 330 core layout(location = 0) in vec3 position; layout(location = 1) in vec3 normal; uniform mat4 model; // 由於模型變換矩陣通常不能共享 因此單獨列出來 // 定義UBO layout (std140) uniform Matrices { mat4 projection; mat4 view; }; // 這裏沒有定義instance name,則在使用時不須要指定instance name void main() { gl_Position = projection * view * model * vec4(position, 1.0); }

Step2 在主程序中設置着色器的uniform block索引指向到綁定點0:

// step1 獲取shader中 uniform buffer 的索引 GLuint redShaderIndex = glGetUniformBlockIndex(redShader.programId, "Matrices"); GLuint greeShaderIndex = glGetUniformBlockIndex(greenShader.programId, "Matrices"); ... // step2 設置shader中 uniform buffer 的索引到指定綁定點 glUniformBlockBinding(redShader.programId, redShaderIndex, 0); // 綁定點爲0 glUniformBlockBinding(greenShader.programId, greeShaderIndex, 0); ...

這裏爲了演示代碼中重複寫出了4個着色器,實際中能夠經過vector裝入這4個着色器簡化代碼。

Step3: 建立UBO,並綁定到綁定點0
咱們須要傳入2個mat4矩陣,因爲mat4中每列的vec4對齊,所以兩個mat4中沒有額外的padding,大小即爲2*sizeof(mat4)。

GLuint UBOId;
    glGenBuffers(1, &UBOId); glBindBuffer(GL_UNIFORM_BUFFER, UBOId); glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_DYNAMIC_DRAW); // 預分配空間 glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBufferRange(GL_UNIFORM_BUFFER, 0, UBOId, 0, 2 * sizeof(glm::mat4)); // 綁定點爲0

Step4: 更新UBO中的數據
這裏使用前面介紹的glBufferSubData更新UBO中數據,例如更新視變換矩陣以下:

glm::mat4 view = camera.getViewMatrix(); // 視變換矩陣 glBindBuffer(GL_UNIFORM_BUFFER, UBOId); glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view)); glBindBuffer(GL_UNIFORM_BUFFER, 0);

經過上面的步驟,咱們完成了着色器中unifrom block和UBO的鏈接,實現了投影矩陣和視變換矩陣在4個着色器之間的共享,繪製4個立方體以下圖所示:

layout std140

驗證ExampleBlock

這裏在着色器中添加一段代碼測試下上面那個複雜的ExampleBlock的內容,咱們在主程序中設置boolean變量爲true,在着色器中添加一個判斷,若是boolean爲true,則輸出白色立方體:

if(boolean) { color = vec4(1.0, 1.0, 1.0, 1.0); }

最終顯示得到了4個全是白色的立方體,效果以下:
四個白色立方體

這就驗證了上述計算出那個複雜ExampleBlock的大小爲152,boolean變量位移偏量爲144是正確的。

layout shared

同std140內存佈局方式不同,shared方式的內存佈局依賴於具體實現,所以咱們沒法提早根據某種字節對齊規範計算出UBO中變量的位移偏量和總體大小,所以在使用shared方式時,咱們須要屢次利用OpenGL的函數來查詢UBO的信息。

這裏在着色器中定義一個用於混合顏色的uniform block:

#version 330 core // 使用默認shared​方式的UBO uniform mixColorSettings { vec4 anotherColor; float mixValue; }; out vec4 color; void main() { color = mix(vec4(0.0, 0.0, 1.0, 1.0), anotherColor, mixValue); }

在出程序中首先查詢UBO總體大小,預分配空間:

GLuint colorUBOId;
glGenBuffers(1, &colorUBOId); glBindBuffer(GL_UNIFORM_BUFFER, colorUBOId); // 獲取UBO大小 由於定義相同 只須要在一個shader中獲取大小便可 GLint blockSize; glGetActiveUniformBlockiv(redShader.programId, redShaderIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize); glBufferData(GL_UNIFORM_BUFFER, blockSize, NULL, GL_DYNAMIC_DRAW); // 預分配空間 glBindBuffer(GL_UNIFORM_BUFFER, 0); glBindBufferBase(GL_UNIFORM_BUFFER, 1, colorUBOId); // 綁定點爲1

而後,經過查詢UBO中成員變量的索引和位移偏量來設置變量值:

// 經過查詢獲取uniform buffer中各個變量的索引和位移偏量 const GLchar* names[] = { "anotherColor", "mixValue" }; GLuint indices[2]; glGetUniformIndices(redShader.programId, 2, names, indices); GLint offset[2]; glGetActiveUniformsiv(redShader.programId, 2, indices, GL_UNIFORM_OFFSET, offset); // 使用獲取的位移偏量更新數據 glm::vec4 anotherColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f); GLfloat mixValue = 0.5f; glBindBuffer(GL_UNIFORM_BUFFER, colorUBOId); glBufferSubData(GL_UNIFORM_BUFFER, offset[0], sizeof(glm::vec4), glm::value_ptr(anotherColor)); glBufferSubData(GL_UNIFORM_BUFFER, offset[1], sizeof(glm::vec4), &mixValue); glBindBuffer(GL_UNIFORM_BUFFER, 0);

和上面std140定義的uniform block一塊兒工做,產生的混合顏色效果以下圖所示:

混合顏色

從上面能夠看到,使用shared佈局時,當變量較多時,這種查詢成員變量索引和位移偏量的工做顯得比較麻煩。

最後的說明

本節學習了interface block概念,以及UBO的兩種內存佈局方式。限於本節內容較多,部分函數的具體使用未在此展開介紹,須要的能夠自行參考OpenGL文檔。同時本文中關於那個複雜的std140佈局的UBO的offset的計算方法,以及使用shared方式時經過查詢獲取UBO總體大小、索引和偏移量的方法,須要儘可能掌握。

相關文章
相關標籤/搜索