作者:位元組流動
來源:
https://blog.csdn.net/Kennethdroid/article/details/102770813OpenGL ES 執行個體化(Instancing)

OpenGL ES 執行個體化(Instancing)是一種隻調用一次渲染函數就能繪制出很多物體的技術,可以實作将資料一次性發送給 GPU ,告訴 OpenGL ES 使用一個繪制函數,将這些資料繪制成多個物體。
執行個體化(Instancing)避免了 CPU 多次向 GPU 下達渲染指令(避免多次調用 glDrawArrays 或 glDrawElements 等繪制函數),節省了繪制多個物體時 CPU 與 GPU 之間的通信時間,提升了渲染性能。
使用執行個體化渲染需要使用的繪制接口:
//普通渲染
glDrawArrays (GLenum mode, GLint first, GLsizei count);
glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices);
//執行個體化渲染
glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei instancecount);
glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount);
相對于普通繪制,執行個體化繪制多了一個參數
instancecount
,表示需要渲染的執行個體數量,調用完執行個體化繪制函數後,我們便将繪制資料一次性發送給 GPU,然後告訴它該如何使用一個函數來繪制這些執行個體。
執行個體化(Instancing)的目标并不是實作将同一物體繪制多次,而是能基于某一物體繪制出位置、大小、形狀或者顔色不同的多個物體。OpenGL ES 着色器中有一個與執行個體化繪制相關的内建變量
gl_InstanceID
。
gl_InstanceID
表示目前正在繪制執行個體的 ID ,每個執行個體對應一個唯一的 ID ,通過這個 ID 可以輕易實作基于一個物體而繪制出位置、大小、形狀或者顔色不同的多個物體(執行個體)。
利用内建變量
gl_InstanceID
在 3D 空間繪制多個位于不同位置的立方體,利用
u_offsets[gl_InstanceID]
對目前執行個體的位置進行偏移,對應的着色器腳本:
// vertex shader GLSL
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
uniform mat4 u_MVPMatrix;
uniform vec3 u_offsets[125];
void main()
{
//通過 u_offsets[gl_InstanceID] 對目前執行個體的位置進行偏移
gl_Position = u_MVPMatrix * (a_position + vec4(u_offsets[gl_InstanceID], 1.0));
v_texCoord = a_texCoord;
}
// fragment shader GLSL
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_TextureMap;
void main()
{
outColor = texture(s_TextureMap, v_texCoord);
}
在 3D 空間中産生 125 個偏移量(offset):
glm::vec3 translations[125];
int index = 0;
GLfloat offset = 0.2f;
for(GLint y = -10; y < 10; y += 4)
{
for(GLint x = -10; x < 10; x += 4)
{
for(GLint z = -10; z < 10; z += 4)
{
glm::vec3 translation;
translation.x = (GLfloat)x / 10.0f + offset;
translation.y = (GLfloat)y / 10.0f + offset;
translation.z = (GLfloat)z / 10.0f + offset;
translations[index++] = translation;
}
}
}
glm::vec3 translations[125];
int index = 0;
GLfloat offset = 0.2f;
for(GLint y = -10; y < 10; y += 4)
{
for(GLint x = -10; x < 10; x += 4)
{
for(GLint z = -10; z < 10; z += 4)
{
glm::vec3 translation;
translation.x = (GLfloat)x / 10.0f + offset;
translation.y = (GLfloat)y / 10.0f + offset;
translation.z = (GLfloat)z / 10.0f + offset;
translations[index++] = translation;
}
}
}
對偏移量數組進行指派,然後進行執行個體化繪制,繪制出 125 個不同位置的立方體:
glUseProgram(m_ProgramObj);
glBindVertexArray(m_VaoId);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
for(GLuint i = 0; i < 125; i++)
{
stringstream ss;
string index;
ss << i;
index = ss.str();
GLint location = glGetUniformLocation(m_ProgramObj, ("u_offsets[" + index + "]").c_str())
glUniform2f(location, translations[i].x, translations[i].y, translations[i].z);
}
glDrawArraysInstanced(GL_TRIANGLES, 0, 36, 125);
glBindVertexArray(0);
效果圖:
gl_InstanceID
和偏移數組進行執行個體化繪制還存在一個問題,那就是着色器中 uniform 類型資料存在上限,也就是 u_offsets 這個數組的大小有限制,最終導緻我們繪制的執行個體存在上限。
為了避免這個問題,我們可以使用執行個體化數組(Instanced Array),它使用頂點屬性來定義,這樣就允許我們使用更多的資料,而且僅當頂點着色器渲染一個新執行個體時它才會被更新。
這個時候我們需要用到函數
glVertexAttribDivisor
,它表示 OpenGL ES 什麼時候去更新頂點屬性的内容到下個元素。
void glVertexAttribDivisor (GLuint index, GLuint divisor);
// index 表示頂點屬性的索引
// divisor 表示每 divisor 個執行個體更新下頂點屬性到下個元素,預設為 0
利用頂點屬性來定義的執行個體化數組(Instanced Array) 在 3D 空間繪制多個位于不同位置的立方體,對應的着色器腳本:
// vertex shader GLSL
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec2 a_offset;
out vec2 v_texCoord;
uniform mat4 u_MVPMatrix;
void main()
{
gl_Position = u_MVPMatrix * (a_position + vec4(a_offset, 1.0));
v_texCoord = a_texCoord;
}
// fragment shader GLSL
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_TextureMap;
void main()
{
outColor = texture(s_TextureMap, v_texCoord);
}
設定 VAO 和 VBO :
// Generate VBO Ids and load the VBOs with data
glGenBuffers(2, m_VboIds);
glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * 125, &translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Generate VAO Id
glGenVertexArrays(1, &m_VaoId);
glBindVertexArray(m_VaoId);
glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (const void *) 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (const void *) (3* sizeof(GLfloat)));
glEnableVertexAttribArray(2);
//利用頂點屬性來定義的執行個體化數組(Instanced Array)
glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[1]);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//指定 index=2 的屬性為執行個體化數組,1 表示每繪制一個執行個體,更新一次數組中的元素
glVertexAttribDivisor(2, 1); // Tell OpenGL this is an instanced vertex attribute.
glBindVertexArray(GL_NONE);
其中
glVertexAttribDivisor(2, 1)
;是上述最重要的一步,用于指定 index = 2 的屬性為執行個體化數組,1 表示每繪制一個執行個體,更新一次數組中的元素。
利用頂點屬性來定義的執行個體化數組,然後繪制出 125 個不同位置的立方體:
glUseProgram(m_ProgramObj);
glBindVertexArray(m_VaoId);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
glDrawArraysInstanced(GL_TRIANGLES, 0, 36, 125);
glBindVertexArray(0);
聯系與交流
技術交流/擷取源碼可以添加我的微信:Byte-Flow
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。