前言
本文是關(guān)于OpenGL ES的系統(tǒng)性學習過程澳盐,記錄了自己在學習OpenGL ES時的收獲泌神。
這篇文章的目標是用OpenGL ES實現(xiàn)多實例渲染坑匠,在2.0版本中蘋果是以擴展的形式來提供相關(guān)支持的后室,在接下來也會講到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入門專題
概述
實例化(instancing)或者多實例渲染(instancd rendering)是一種連續(xù)執(zhí)行多條相同渲染命令的方法副编。并且每個命令的所產(chǎn)生的渲染結(jié)果都會有輕微的差異秒拔。是一種非常有效的,實用少量api調(diào)用來渲染大量幾何體的方法串塑。OpenGL提供多種機制,允許著色器對不同渲染實例賦予不同的頂點屬性。
實現(xiàn)效果
渲染命令
- 多實例渲染命令
glDrawArraysInstanced函數(shù)是glDrawArrays()的多實例版本枉证,參數(shù)完全等價,只是多了個instancecount移必,該參數(shù)用于設(shè)置渲染實例個數(shù)室谚。
void glDrawArraysInstanced (GLenum mode, GLint first, GLsizei count, GLsizei instancecount)
參數(shù) mode :繪制方式,例如:GL_POINTS崔泵、GL_LINES秒赤。
參數(shù) first :從數(shù)組緩存中的哪一位開始繪制,一般為0憎瘸。
參數(shù) count :數(shù)組中頂點的數(shù)量入篮。
參數(shù) instancecount :該參數(shù)用于設(shè)置渲染實例個數(shù)。
glDrawElementsInstanced是glDrawElements()的多實例版本含思,同樣只是多了個instancecount參數(shù)而已崎弃,同樣是用于設(shè)置渲染實例個數(shù)甘晤。
void glDrawElementsInstanced (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLsizei instancecount)
參數(shù) mode :指定繪制圖元的類型。例如:GL_POINTS饲做、GL_LINES线婚。
參數(shù) count :為繪制圖元的數(shù)量乘上一個圖元的頂點數(shù)。
參數(shù) type :為索引值的類型盆均,只能是下列值之一:GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT塞弊。
參數(shù) indices :指向索引存貯位置的指針。
參數(shù) instancecount :該參數(shù)用于設(shè)置渲染實例個數(shù)泪姨。
- 多實例渲染頂點屬性控制:
多實例的頂點屬性與正規(guī)的頂點屬性是類似的游沿。它們可以通過glGetAttribLocation查詢,通過glVertexAttribPointer來設(shè)置肮砾。通過glEnableVertexAttribArray和glDisableVertexAttribArray進行啟用和禁用诀黍。
void glVertexAttribDivisor (GLuint index, GLuint divisor)
參數(shù) index : 對應(yīng)著色器中的索引。
參數(shù) divisor :表示頂點屬性的更新頻率仗处,每隔多少個實例將重新設(shè)置實例的該屬性眯勾,例如設(shè)置為1,那么每個實例的屬性都不一樣婆誓,設(shè)置為2則每兩個實例相同吃环,3則每三個實例改變屬性。
實現(xiàn)步驟
- 創(chuàng)建著色器洋幻。在片元著色器中我們增加一個偏移量的屬性(attribute vec3 offset)郁轻,在每次繪制之后它的值會發(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è)置頂點屬性好唯。設(shè)置頂點屬性方便我們進行紋理貼圖。
- (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è)置紋理厂庇。通過讀取紋理圖片渠啊,生成紋理緩存對象。
- (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è)置偏移量权旷。偏移量和普通的頂點數(shù)據(jù)一樣可以使用VBO來存儲替蛉。我們希望每次繪制頂點數(shù)組都發(fā)生一定的偏移,總共發(fā)生三次偏移(gl_Position = vec4(position+offset, 1.0))拄氯。這樣我們總共需要9個GLfloat的空間來存儲偏移數(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);
// 每次繪制之后译柏,對offset進行1個偏移
glVertexAttribDivisor(glGetAttribLocation(_program, "offset"), 1);
glDrawArraysInstanced(GL_TRIANGLES, 0, _vertCount, 3);
//將指定 renderbuffer 呈現(xiàn)在屏幕上镣煮,在這里我們指定的是前面已經(jīng)綁定為當前 renderbuffer 的那個,在 renderbuffer 可以被呈現(xiàn)之前鄙麦,必須調(diào)用renderbufferStorage:fromDrawable: 為之分配存儲空間典唇。
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
最后
由于上述API都是OpenGL ES 3.0的相關(guān)API镊折,因此如果在OpenGL ES 2.0想實現(xiàn)相同的效果,我們可以用蘋果的OpenGL ES 2.0的擴展API介衔。OpenGL ES 2.0的擴展都在glext.h中恨胚,區(qū)別就是API加了EXT、APPLE炎咖、OES等后綴赃泡。比如多實例渲染OpenGL ES 2.0的擴展API為 glVertexAttribDivisorEXT、 glDrawArraysInstancedEXT乘盼、glDrawElementsInstancedEXT