前言
本文是關(guān)于OpenGL ES的系統(tǒng)性學(xué)習(xí)過程,記錄了自己在學(xué)習(xí)OpenGL ES時(shí)的收獲。
這篇文章的目標(biāo)是用OpenGL ES實(shí)現(xiàn)多實(shí)例渲染,在2.0版本中蘋果是以擴(kuò)展的形式來提供相關(guān)支持的,在接下來也會(huì)講到2.0版本中的相關(guān)API。
環(huán)境是Xcode8.1+OpenGL ES 3.0
目前代碼已經(jīng)放到github上面,OpenGL ES入門10-Instance技術(shù)
歡迎關(guān)注我的 OpenGL ES入門專題
概述
實(shí)例化(instancing)或者多實(shí)例渲染(instancd rendering)是一種連續(xù)執(zhí)行多條相同渲染命令的方法。并且每個(gè)命令的所產(chǎn)生的渲染結(jié)果都會(huì)有輕微的差異。是一種非常有效的,實(shí)用少量api調(diào)用來渲染大量幾何體的方法。OpenGL提供多種機(jī)制,允許著色器對(duì)不同渲染實(shí)例賦予不同的頂點(diǎn)屬性。
實(shí)現(xiàn)效果

渲染命令
- 多實(shí)例渲染命令
glDrawArraysInstanced函數(shù)是glDrawArrays()的多實(shí)例版本,參數(shù)完全等價(jià),只是多了個(gè)instancecount,該參數(shù)用于設(shè)置渲染實(shí)例個(gè)數(shù)。
void glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)
參數(shù) mode :繪制方式,例如:GL_POINTS、GL_LINES。
參數(shù) first :從數(shù)組緩存中的哪一位開始繪制,一般為0。
參數(shù) count :數(shù)組中頂點(diǎn)的數(shù)量。
參數(shù) instancecount :該參數(shù)用于設(shè)置渲染實(shí)例個(gè)數(shù)。
glDrawElementsInstanced是glDrawElements()的多實(shí)例版本,同樣只是多了個(gè)instancecount參數(shù)而已,同樣是用于設(shè)置渲染實(shí)例個(gè)數(shù)。
void glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLsizei instancecount)
參數(shù) mode :指定繪制圖元的類型。例如:GL_POINTS、GL_LINES。
參數(shù) count :為繪制圖元的數(shù)量乘上一個(gè)圖元的頂點(diǎn)數(shù)。
參數(shù) type :為索引值的類型,只能是下列值之一:GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT。
參數(shù) indices :指向索引存貯位置的指針。
參數(shù) instancecount :該參數(shù)用于設(shè)置渲染實(shí)例個(gè)數(shù)。
- 多實(shí)例渲染頂點(diǎn)屬性控制:
多實(shí)例的頂點(diǎn)屬性與正規(guī)的頂點(diǎn)屬性是類似的。它們可以通過glGetAttribLocation查詢,通過glVertexAttribPointer來設(shè)置。通過glEnableVertexAttribArray和glDisableVertexAttribArray進(jìn)行啟用和禁用。
void glVertexAttribDivisor (GLuint index, GLuint divisor)
參數(shù) index : 對(duì)應(yīng)著色器中的索引。
參數(shù) divisor :表示頂點(diǎn)屬性的更新頻率,每隔多少個(gè)實(shí)例將重新設(shè)置實(shí)例的該屬性,例如設(shè)置為1,那么每個(gè)實(shí)例的屬性都不一樣,設(shè)置為2則每兩個(gè)實(shí)例相同,3則每三個(gè)實(shí)例改變屬性。
實(shí)現(xiàn)步驟
- 創(chuàng)建著色器。在片元著色器中我們?cè)黾右粋€(gè)偏移量的屬性(attribute vec3 offset),在每次繪制之后它的值會(huì)發(fā)生偏移。通過glVertexAttribDivisor來設(shè)置如何偏移。
precision mediump float;
uniform sampler2D image;
varying vec2 vTexcoord;
void main()
{
gl_FragColor = texture2D(image, vTexcoord);
}
attribute vec3 position;
attribute vec3 offset; //偏移量
attribute vec2 texcoord;
varying vec2 vTexcoord;
void main()
{
gl_Position = vec4(position+offset, 1.0);
vTexcoord = texcoord;
}
- 設(shè)置頂點(diǎn)屬性。設(shè)置頂點(diǎn)屬性方便我們進(jìn)行紋理貼圖。
- (void)setupVBO
{
_vertCount = 6;
GLfloat vertices[] = {
-0.5f, 1.0f, 0.0f, 1.0f, 0.0f, // 右上
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // 右下
-1.0f, 0.5f, 0.0f, 0.0f, 1.0f, // 左下
-1.0f, 0.5f, 0.0f, 0.0f, 1.0f, // 左下
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 左上
-0.5f, 1.0f, 0.0f, 1.0f, 0.0f, // 右上
};
// 創(chuàng)建VBO
_vbo = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
glEnableVertexAttribArray(glGetAttribLocation(_program, "position"));
glVertexAttribPointer(glGetAttribLocation(_program, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
glEnableVertexAttribArray(glGetAttribLocation(_program, "texcoord"));
glVertexAttribPointer(glGetAttribLocation(_program, "texcoord"), 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL+sizeof(GL_FLOAT)*3);
}
- 設(shè)置紋理。通過讀取紋理圖片,生成紋理緩存對(duì)象。
- (void)setupTexure
{
NSString *path = [[NSBundle mainBundle] pathForResource:@"wood" ofType:@"jpg"];
unsigned char *data;
int size;
int width;
int height;
// 加載紋理
if (read_jpeg_file(path.UTF8String, &data, &size, &width, &height) < 0) {
printf("%s\n", "decode fail");
}
// 創(chuàng)建紋理
_texture = createTexture2D(GL_RGB, width, height, data);
if (data) {
free(data);
data = NULL;
}
}
- 設(shè)置偏移量。偏移量和普通的頂點(diǎn)數(shù)據(jù)一樣可以使用VBO來存儲(chǔ)。我們希望每次繪制頂點(diǎn)數(shù)組都發(fā)生一定的偏移,總共發(fā)生三次偏移(gl_Position = vec4(position+offset, 1.0))。這樣我們總共需要9個(gè)GLfloat的空間來存儲(chǔ)偏移數(shù)據(jù)。
- (void)setupOffset
{
GLfloat vertices[] = {
0.1f, -0.1f, 0.0f,
0.7f, -0.7f, 0.0f,
1.3f, -1.3f, 0.0f,
};
// 創(chuàng)建VBO
_offsetVBO = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
glEnableVertexAttribArray(glGetAttribLocation(_program, "offset"));
glVertexAttribPointer(glGetAttribLocation(_program, "offset"), 3, GL_FLOAT, GL_FALSE, 0, NULL);
}
- 繪制。
- (void)render
{
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(2.0);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
// 激活紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _texture);
glUniform1i(glGetUniformLocation(_program, "image"), 0);
// 每次繪制之后,對(duì)offset進(jìn)行1個(gè)偏移
glVertexAttribDivisor(glGetAttribLocation(_program, "offset"), 1);
glDrawArraysInstanced(GL_TRIANGLES, 0, _vertCount, 3);
//將指定 renderbuffer 呈現(xiàn)在屏幕上,在這里我們指定的是前面已經(jīng)綁定為當(dāng)前 renderbuffer 的那個(gè),在 renderbuffer 可以被呈現(xiàn)之前,必須調(diào)用renderbufferStorage:fromDrawable: 為之分配存儲(chǔ)空間。
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
最后
由于上述API都是OpenGL ES 3.0的相關(guān)API,因此如果在OpenGL ES 2.0想實(shí)現(xiàn)相同的效果,我們可以用蘋果的OpenGL ES 2.0的擴(kuò)展API。OpenGL ES 2.0的擴(kuò)展都在glext.h中,區(qū)別就是API加了EXT、APPLE、OES等后綴。比如多實(shí)例渲染OpenGL ES 2.0的擴(kuò)展API為 glVertexAttribDivisorEXT、 glDrawArraysInstancedEXT、glDrawElementsInstancedEXT