OpenGL ES入門10-多實(shí)例渲染

前言

本文是關(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í)例渲染實(shí)例
渲染命令
  • 多實(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

參考鏈接

OpenGL-Refpages

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容