該原創文章首發於微信公衆號:字節流動
OpenGL 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;
}
}
}
對偏移量數組進行賦值,然後進行實例化繪製,繪製出 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);
實現代碼路徑:
NDK_OpenGLES_3_0
聯繫與交流
我的公衆號
我的微信