學習OpenGL ES之繪製三角形

handyTool發表於2019-03-04

獲取示例程式碼


- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    ...

    // 使用fragment.glsl 和 vertex.glsl中的shader
    glUseProgram(self.shaderProgram);
    [self drawTriangle];
}

- (void)drawTriangle {
    static GLfloat triangleData[18] = {
        0,      0.5f,  0,  1,  0,  0, // x, y, z, r, g, b,每一行儲存一個點的資訊,位置和顏色
        -0.5f, -0.5f,  0,  0,  1,  0,
        0.5f,  -0.5f,  0,  0,  0,  1,
    };
    
    // 啟用Shader中的兩個屬性
    // attribute vec4 position;
    // attribute vec4 color;
    GLuint positionAttribLocation = glGetAttribLocation(self.shaderProgram, "position");
    glEnableVertexAttribArray(positionAttribLocation);
    GLuint colorAttribLocation = glGetAttribLocation(self.shaderProgram, "color");
    glEnableVertexAttribArray(colorAttribLocation);
    
    // 為shader中的position和color賦值
    // glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
    // indx: 上面Get到的Location
    // size: 有幾個型別為type的資料,比如位置有x,y,z三個GLfloat元素,值就為3
    // type: 一般就是陣列裡元素資料的型別
    // normalized: 暫時用不上
    // stride: 每一個點包含幾個byte,本例中就是6個GLfloat,x,y,z,r,g,b
    // ptr: 資料開始的指標,位置就是從頭開始,顏色則跳過3個GLFloat的大小
    glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)triangleData);
    glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)triangleData + 3 * sizeof(GLfloat));
    
    glDrawArrays(GL_TRIANGLES, 0, 3);
}
複製程式碼

在上篇文章的基礎上我們增加了- (void)drawTriangle。效果如下

學習OpenGL ES之繪製三角形

在解釋程式碼之前我們先來了解一下OpenGL渲染的基本流程。

學習OpenGL ES之繪製三角形

從圖中可以看出,最開始的輸入是頂點資料。比如三角形,就是三個點。每個頂點資料可以包含任意數量的資訊,最基本的有位置,顏色。後面介紹貼圖時還會包含UV資訊。經過各種處理,最終放入FrameBuffer,就是上一篇文章說的緩衝區。接下來我們按照這個流程解釋繪製的程式碼。

第一步,提供 Vertex Data

 static GLfloat triangleData[18] = {
        0,      0.5f,  0,  1,  0,  0, // x, y, z, r, g, b,每一行儲存一個點的資訊,位置和顏色
        -0.5f, -0.5f,  0,  0,  1,  0,
        0.5f,  -0.5f,  0,  0,  0,  1,
    };
複製程式碼

我們要繪製的是一個三角形,三角形有3個點,每個點我希望包含位置資訊和顏色資訊,至於兩點之間的顏色如何,我們不關心,OpenGL ES會處理。綜上,我們為每一個點分配6個GLfloat大小的空間,前三個儲存位置x,y,z,後三個儲存顏色r,g,b。三個點就是18個GLfloat的陣列。

使用GLfloat而不是float是為了跨平臺,保證不同平臺的GLfloat佔用的位元組數都是一致的。從而規範化了傳遞給Shader的資料的格式和大小。

第二步,呼叫Vertex Shader

attribute vec4 position;
attribute vec4 color;

varying vec4 fragColor;

void main(void) {
    fragColor = color;
    gl_Position = position;
}

複製程式碼

這是本例使用的Vertex Shader,他只做了兩件事,將頂點資料裡的顏色傳遞給了Fragment Shader,將位置傳遞給了OpenGL ES。更詳細的解釋會再下一篇介紹。

第三步,Primitive Assembly

 glDrawArrays(GL_TRIANGLES, 0, 3);
複製程式碼

這一步,以形狀為單位彙總渲染指令,為下一步柵格化顏色插值做準備。本例中只繪製了三角形,還可以通過glDrawArrays繪製直線,點等。

第四步,Rasterization

這一步會柵格化繪製的形狀。第一步我們說過只需傳遞頂點的顏色,兩點中間的顏色OpenGL會幫我們處理。OpenGL將會計算出每一個畫素對應的屬性,比如顏色,這些值都是根據頂點的屬性值以及形狀計算而來的。

學習OpenGL ES之繪製三角形

這是例子渲染出來的三角形,由圖可以看出,三角形內部的每個畫素的顏色都是根據畫素點與三個點的距離計算出來的。離紅色點越近畫素的紅色成分越多。

第五步,Fragment Shader

經過柵格化之後,每一個畫素都要經過Fragment Shader處理一遍。下面就是本例使用的Fragment Shader。

varying lowp vec4 fragColor;

void main(void) {
    gl_FragColor = fragColor;
}

複製程式碼

本例使用的Fragment Shader什麼也沒做,就是把OpenGL計算出來的顏色直接遞交回給OpenGL。

第五步,Per-Fragment Operations

這裡主要處理OpenGL對畫素的一些固定操作。比如深度測試,剪裁測試等。通過OpenGL的API進行配置。

第六步,Framebuffer

最終寫入Framebuffer,交換緩衝區後顯示在視窗上。


最後再介紹兩個重要的點。

座標系

 static GLfloat triangleData[18] = {
        0,      0.5f,  0,  1,  0,  0, // x, y, z, r, g, b,每一行儲存一個點的資訊,位置和顏色
        -0.5f, -0.5f,  0,  0,  1,  0,
        0.5f,  -0.5f,  0,  0,  0,  1,
    };
複製程式碼
學習OpenGL ES之繪製三角形

中間點是0,0點。x,y,z的生長方向如圖,Z軸是由內向外。螢幕長寬都是2。

如何將頂點資料傳遞給Shader

上面解釋步驟時,從第一步到第二步如何傳遞頂點資料沒有介紹。它的程式碼實現如下。

    // 啟用Shader中的兩個屬性
    // attribute vec4 position;
    // attribute vec4 color;
    GLuint positionAttribLocation = glGetAttribLocation(self.shaderProgram, "position");
    glEnableVertexAttribArray(positionAttribLocation);
    GLuint colorAttribLocation = glGetAttribLocation(self.shaderProgram, "color");
    glEnableVertexAttribArray(colorAttribLocation);
    
    // 為shader中的position和color賦值
    // glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
    // indx: 上面Get到的Location
    // size: 有幾個型別為type的資料,比如位置有x,y,z三個GLfloat元素,值就為3
    // type: 一般就是陣列裡元素資料的型別
    // normalized: 暫時用不上
    // stride: 每一個點包含幾個byte,本例中就是6個GLfloat,x,y,z,r,g,b
    // ptr: 資料開始的指標,位置就是從頭開始,顏色則跳過3個GLFloat的大小
    glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)triangleData);
    glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)triangleData + 3 * sizeof(GLfloat));
複製程式碼

首先通過glEnableVertexAttribArray啟用shader中的兩個屬性。glGetAttribLocation是為了獲取shader中某個屬性的位置。這是shader與OpenGL約定的資料互通方式

GLuint positionAttribLocation = glGetAttribLocation(self.shaderProgram, "position");
glEnableVertexAttribArray(positionAttribLocation);
GLuint colorAttribLocation = glGetAttribLocation(self.shaderProgram, "color");
glEnableVertexAttribArray(colorAttribLocation);
複製程式碼

Shader中的屬性

attribute vec4 position;
attribute vec4 color;
複製程式碼

頂點資料只會傳遞給Vertex Shader,所以不能把attribute vec4 position;寫到Fragment Shader裡,從上面的流程圖也可以看出來。啟用Vertex Shader中的屬性後就可以傳值給它了,下面是傳值程式碼。

glVertexAttribPointer(positionAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)triangleData);
glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (char *)triangleData + 3 * sizeof(GLfloat));
複製程式碼

上面第一行程式碼就是告訴Vertex Shader,向位置屬性傳遞的資料大小是3個GLfloat,每個頂點資料有6個GLfloat,位置資料起始的指標是(char *)triangleData。OpenGL讀取完第一個位置資料後,就會將指標增加6個GLfloat的大小,訪問下一個頂點位置。顏色也是相同的道理。

本篇主要介紹了繪製三角形需要用到哪些API以及繪製流程。下一篇會重點介紹Shader的相關知識。

相關文章