NDK OpenGL ES 3.0 開發(十三):實例化(Instancing)

該原創文章首發於微信公衆號:字節流動

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

聯繫與交流

我的公衆號
在這裏插入圖片描述
我的微信
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章