作者:位元組流動
來源:
https://blog.csdn.net/Kennethdroid/article/details/112379836VBO 和 EBO
VBO(Vertex Buffer Object)是指頂點緩沖區對象,而 EBO(Element Buffer Object)是指圖元索引緩沖區對象,VAO 和 EBO 實際上是對同一類 Buffer 按照用途的不同稱呼。
OpenGL ES 2.0 程式設計中,用于繪制的頂點數組資料首先儲存在 CPU 記憶體,在調用 glDrawArrays 或者 glDrawElements 等進行繪制時,需要将頂點數組資料從 CPU 記憶體拷貝到顯存。
但是很多時候我們沒必要每次繪制的時候都去進行記憶體拷貝,如果可以在顯存中緩存這些資料,就可以在很大程度上降低記憶體拷貝帶來的開銷。
OpenGL ES 3.0 程式設計中, VBO 和 EBO 的出現就是為了解決這個問題。
VBO 和 EBO 的作用是在顯存中提前開辟好一塊記憶體,用于緩存頂點資料或者圖元索引資料,進而避免每次繪制時的 CPU 與 GPU 之間的記憶體拷貝,可以改進渲染性能,降低記憶體帶寬和功耗。
OpenGL ES 3.0 支援兩類緩沖區對象:頂點數組緩沖區對象、圖元索引緩沖區對象。
GL_ARRAY_BUFFER 标志指定的緩沖區對象用于儲存頂點數組,GL_ELEMENT_ARRAY_BUFFER 标志指定的緩存區對象用于儲存圖元索引。
VBO(EBO)的建立和更新:
// 建立 2 個 VBO(EBO 實際上跟 VBO 一樣,隻是按照用途的另一種稱呼)
glGenBuffers(2, m_VboIds);
// 綁定第一個 VBO,拷貝頂點數組到顯存
glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 綁定第二個 VBO(EBO),拷貝圖元索引資料到顯存
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
GL_STATIC_DRAW 标志辨別緩沖區對象資料被修改一次,使用多次,用于繪制。
本例中頂點着色器和片段着色器增加 color 屬性:
//頂點着色器
#version 300 es
layout(location = 0) in vec4 a_position; // 位置變量的屬性位置值為 0
layout(location = 1) in vec3 a_color; // 顔色變量的屬性位置值為 1
out vec3 v_color; // 向片段着色器輸出一個顔色
void main()
{
v_color = a_color;
gl_Position = a_position;
};
//片段着色器
#version 300 es
precision mediump float;
in vec3 v_color;
out vec4 o_fragColor;
void main()
{
o_fragColor = vec4(v_color, 1.0);
}
頂點數組資料和圖元索引資料:
// 4 vertices, with(x,y,z) ,(r, g, b, a) per-vertex
GLfloat vertices[] =
{
-0.5f, 0.5f, 0.0f, // v0
1.0f, 0.0f, 0.0f, // c0
-0.5f, -0.5f, 0.0f, // v1
0.0f, 1.0f, 0.0f, // c1
0.5f, -0.5f, 0.0f, // v2
0.0f, 0.0f, 1.0f, // c2
0.5f, 0.5f, 0.0f, // v3
0.5f, 1.0f, 1.0f, // c3
};
// Index buffer data
GLushort indices[6] = { 0, 1, 2, 0, 2, 3};
由于頂點位置和顔色資料在同一個數組裡,一起更新到 VBO 裡面,是以需要知道 2 個屬性的步長和偏移量。
為獲得資料隊列中下一個屬性值(比如位置向量的下個 3 維分量)我們必須向右移動 6 個 float ,其中 3 個是位置值,另外 3 個是顔色值,那麼步長就是 6 乘以 float 的位元組數(= 24 位元組)。
同樣,也需要指定頂點位置屬性和顔色屬性在 VBO 記憶體中的偏移量。
對于每個頂點來說,位置頂點屬性在前,是以它的偏移量是 0 。而顔色屬性緊随位置資料之後,是以偏移量就是 3 * sizeof(GLfloat) ,用位元組來計算就是 12 位元組。
使用 VBO 和 EBO 進行繪制。
glUseProgram(m_ProgramObj);
//不使用 VBO 的繪制
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (3+3)*sizeof(GLfloat), vertices);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, (3+3)*sizeof(GLfloat), (vertices + 3));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
//使用 VBO 的繪制
glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (3+3)*sizeof(GLfloat), (const void *)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, (3+3)*sizeof(GLfloat), (const void *)(3 *sizeof(GLfloat)));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[1]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);
VAO
VAO(Vertex Array Object)是指頂點數組對象,主要用于管理 VBO 或 EBO ,減少 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 這些調用操作,高效地實作在頂點數組配置之間切換。
基于上小節的例子建立 VAO :
// 建立并綁定 VAO
glGenVertexArrays(1, &m_VaoId);
glBindVertexArray(m_VaoId);
// 在綁定 VAO 之後,操作 VBO ,目前 VAO 會記錄 VBO 的操作
glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (3+3)*sizeof(GLfloat), (const void *)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, (3+3)*sizeof(GLfloat), (const void *)(3 *sizeof(GLfloat)));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[1]);
glBindVertexArray(GL_NONE);
使用 VAO 進行繪制:
// 是不是精簡了很多?
glUseProgram(m_ProgramObj);
glBindVertexArray(m_VaoId);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0)
UBO
UBO,Uniform Buffer Object 顧名思義,就是一個裝載 uniform 變量資料的緩沖區對象,本質上跟 OpenGL ES 的其他緩沖區對象沒有差別,建立方式也大緻一緻,都是顯存上一塊用于儲存特定資料的區域。
當資料加載到 UBO ,那麼這些資料将存儲在 UBO 上,而不再交給着色器程式,是以它們不會占用着色器程式自身的 uniform 存儲空間,UBO 是一種新的從記憶體到顯存的資料傳遞方式,另外 UBO 一般需要與 uniform 塊配合使用。
本例将 MVP 變換矩陣設定為一個 uniform 塊,即我們後面建立的 UBO 中将儲存 3 個矩陣。
#version 310 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
layout (std140) uniform MVPMatrix
{
mat4 projection;
mat4 view;
mat4 model;
};
out vec2 v_texCoord;
void main()
{
gl_Position = projection * view * model * a_position;
v_texCoord = a_texCoord;
}
設定 uniform 塊的綁定點為 0 ,生成一個 UBO 。
GLuint uniformBlockIndex = glGetUniformBlockIndex(m_ProgramObj, "MVPMatrix");
glUniformBlockBinding(m_ProgramObj, uniformBlockIndex, 0);
glGenBuffers(1, &m_UboId);
glBindBuffer(GL_UNIFORM_BUFFER, m_UboId);
glBufferData(GL_UNIFORM_BUFFER, 3 * sizeof(glm::mat4), nullptr, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
//定義綁定點為 0 buffer 的範圍
glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_UboId, 0, 3 * sizeof(glm::mat4));
繪制的時候更新 Uniform Buffer 的資料,更新三個矩陣的資料,注意偏移量。
glBindBuffer(GL_UNIFORM_BUFFER, m_UboId);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), &m_ProjectionMatrix[0][0]);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), &m_ViewMatrix[0][0]);
glBufferSubData(GL_UNIFORM_BUFFER, 2 *sizeof(glm::mat4), sizeof(glm::mat4), &m_ModelMatrix[0][0]);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
FBO
FBO(Frame Buffer Object)即幀緩沖區對象,實際上是一個可添加緩沖區的容器,可以為其添加紋理或渲染緩沖區對象(RBO)。
FBO 本身不能用于渲染,隻有添加了紋理或者渲染緩沖區之後才能作為渲染目标,它僅且提供了 3 個附着(Attachment),分别是顔色附着、深度附着和模闆附着。
RBO(Render Buffer Object)即渲染緩沖區對象,是一個由應用程式配置設定的 2D 圖像緩沖區。渲染緩沖區可以用于配置設定和存儲顔色、深度或者模闆值,可以用作 FBO 中的顔色、深度或者模闆附着。
使用 FBO 作為渲染目标時,首先需要為 FBO 的附着添加連接配接對象,如顔色附着需要連接配接紋理或者渲染緩沖區對象的顔色緩沖區。
TBO
紋理緩沖區對象,即 TBO(Texture Buffer Object),是 OpenGL ES 3.2 引入的概念,是以在使用時首先要檢查 OpenGL ES 的版本,Android 方面需要保證 API >= 24 。
TBO 需要配合緩沖區紋理(Buffer Texture)一起使用,Buffer Texture 是一種一維紋理,其存儲資料來自紋理緩沖區對象(TBO),用于允許着色器通路由緩沖區對象管理的大型記憶體表。
在 GLSL 中,隻能使用 texelFetch 函數通路緩沖區紋理,緩沖區紋理的采樣器類型為 samplerBuffer 。
生成一個 TBO 的方式跟 VBO 類似,隻需要綁定到 GL_TEXTURE_BUFFER ,而生成緩沖區紋理的方式與普通的 2D 紋理一樣。
//生成一個 Buffer Texture
glGenTextures(1, &m_TboTexId);
float *bigData = new float[BIG_DATA_SIZE];
for (int i = 0; i < BIG_DATA_SIZE; ++i) {
bigData[i] = i * 1.0f;
}
//生成一個 TBO ,并将一個大的數組上傳至 TBO
glGenBuffers(1, &m_TboId);
glBindBuffer(GL_TEXTURE_BUFFER, m_TboId);
glBufferData(GL_TEXTURE_BUFFER, sizeof(float) * BIG_DATA_SIZE, bigData, GL_STATIC_DRAW);
delete [] bigData;
使用紋理緩沖區的片段着色器,需要引入擴充 texture buffer ,注意版本聲明為 #version 320 es 。
#version 320 es
#extension GL_EXT_texture_buffer : require
in mediump vec2 v_texCoord;
layout(location = 0) out mediump vec4 outColor;
uniform mediump samplerBuffer u_buffer_tex;
uniform mediump sampler2D u_2d_texture;
uniform mediump int u_BufferSize;
void main()
{
mediump int index = int((v_texCoord.x +v_texCoord.y) /2.0 * float(u_BufferSize - 1));
mediump float value = texelFetch(u_buffer_tex, index).x;
mediump vec4 lightColor = vec4(vec3(vec2(value / float(u_BufferSize - 1)), 0.0), 1.0);
outColor = texture(u_2d_texture, v_texCoord) * lightColor;
}
繪制時如何使用緩沖區紋理和 TBO ?
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER, m_TboTexId);
glTexBuffer(GL_TEXTURE_BUFFER, GL_R32F, m_TboId);
GLUtils::setInt(m_ProgramObj, "u_buffer_tex", 0);
跟普通紋理的使用方式大緻一樣,隻不過需要使用 glTexBuffer 綁定 TBO 到緩沖區紋理。
PBO
PBO (Pixel Buffer Object)是 OpenGL ES 3.0 的概念,稱為像素緩沖區對象,主要被用于異步像素傳輸操作。PBO 僅用于執行像素傳輸,不連接配接到紋理,且與 FBO (幀緩沖區對象)無關。
PBO 類似于 VBO(頂點緩沖區對象),PBO 開辟的也是 GPU 緩存,而存儲的是圖像資料。
PBO 可以在 GPU 的緩存間快速傳遞像素資料,不影響 CPU 時鐘周期,除此之外,PBO 還支援異步傳輸。
PBO 類似于“以空間換時間”政策,在使用一個 PBO 的情況下,性能無法有效地提升,通常需要多個 PBO 交替配合使用。
如上圖所示,利用 2 個 PBO 從幀緩沖區讀回圖像資料,使用 glReadPixels 通知 GPU 将圖像資料從幀緩沖區讀回到 PBO1 中,同時 CPU 可以直接處理 PBO2 中的圖像資料。
關于 PBO 的詳細使用可以參考文章:
OpenGL ES 3.0 開發連載(22):PBO, 這裡不再贅述。
技術交流
技術交流/擷取源碼可以添加我的微信:Byte-Flow
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。