OpenGL ES 案例05:GLSL使用索引繪圖

OpenGL + OpenGL ES +Metal 系列文章匯總

本案例的主要目的是理解GLSL中如何索引繪圖

本文案例代碼有OC及Swift版本,詳情見文末鏈接,講解以O(shè)C版本為主

在介紹本案例之前,首先說說什么是索引繪圖
一個(gè)圖形中,有許多頂點(diǎn),例如本案例中的金字塔,有5個(gè)面,由6個(gè)三角形組成,一共有18個(gè)頂點(diǎn),然而實(shí)際肉眼可見的只有5個(gè)頂點(diǎn),如下圖所示


金字塔圖示

索引繪圖技巧就是指將圖形中的肉眼可見的頂點(diǎn),通過索引的方式表示頂點(diǎn)之間的連接,將重復(fù)頂點(diǎn)復(fù)用進(jìn)行圖形繪制的一種技巧

案例的整體效果圖如下


OpenGL ES_05_效果圖.gif

案例的整體流程如圖所示


整體流程

主要包含三部分

  • 準(zhǔn)備工作:主要是導(dǎo)入三方數(shù)學(xué)庫以及全局變量的定義
  • 自定義著色器:自定義頂點(diǎn)、片元著色器
  • layoutSubview函數(shù):繪制圖形
  • 按鈕點(diǎn)擊事件:根據(jù)點(diǎn)擊不同的按鈕實(shí)現(xiàn)不同方向的旋轉(zhuǎn)

下面主要說說后面三部分

自定義著色器

用GLSL語言自定義頂點(diǎn)、片元著色器,步驟如下


自定義著色器流程

頂點(diǎn)著色器

  • 由于在案例中圖形會(huì)發(fā)生旋轉(zhuǎn),涉及矩陣的變換,所以需要定義uniform修飾的投影矩陣模型視圖矩陣
  • 在main函數(shù)中,需要將頂點(diǎn)數(shù)據(jù)與矩陣相乘,其相乘也是有順序的,因?yàn)樵贠penGL ES中是以列向量為主,position是列向量,即4*1矩陣,而投影矩陣、模型視圖矩陣是4*4矩陣,矩陣相乘 本質(zhì)是 矩陣叉乘,需要滿足A x B 時(shí), A的列數(shù) = B的行數(shù),所以需要將position X mvp相乘的順序 顛倒為 pvm X position
//頂點(diǎn)坐標(biāo)
attribute vec4 position;
//頂點(diǎn)顏色
attribute vec4 positionColor;
//投影矩陣
uniform mat4 projectionMatrix;
//模型視圖矩陣
uniform mat4 modelViewMatrix;
//頂點(diǎn)顏色--用于與片元著色器橋接
varying lowp vec4 varyColor;

void main(){
    //將頂點(diǎn)顏色賦值給橋接的頂點(diǎn)顏色變量
    varyColor = positionColor;
    
    vec4 vPos;
    //頂點(diǎn)坐標(biāo)變換: 4*4 x 4*4 x 4*1
    vPos = projectionMatrix * modelViewMatrix * position;
    //將變換后的頂點(diǎn)坐標(biāo)賦值給內(nèi)建變量
    gl_Position = vPos;
}

片元著色器

  • 片元著色器中比較簡單,只需要將頂點(diǎn)著色器中傳過來的頂點(diǎn)顏色賦值給內(nèi)建變量gl_FragColor
  • 如果頂點(diǎn)顏色少于頂點(diǎn)個(gè)數(shù),例如只有【紅色,藍(lán)色】,其他部分會(huì)進(jìn)行顏色插值,類似于漸變色,如上面的效果圖中所示
  • 如果不想進(jìn)行插值,就需要指定面顏色,不指定頂點(diǎn)顏色
//橋接的頂點(diǎn)顏色
varying lowp vec4 varyColor;

void main(){
    //頂點(diǎn)顏色賦值給內(nèi)建變量
    gl_FragColor = varyColor;
}

layoutSubviews

這部分主要是繪制前的準(zhǔn)備工作和圖形的繪制

  • 初始化:繪制前的準(zhǔn)備工作
  • 繪制:使用索引繪圖技巧繪制圖形

初始化

這部分內(nèi)容與OpenGL ES 案例04:GLSL加載圖片中并沒有什么區(qū)別

繪制前的準(zhǔn)備工作

如圖所示,共有5個(gè)步驟,這里簡要說明下

  • 繪制圖層:用于創(chuàng)建揮之內(nèi)容顯示的載體
  • 繪制上下文:用于記錄狀態(tài)
  • 清除緩存區(qū):避免之前的緩存,對(duì)本次繪制造成影響
  • 設(shè)置renderBuffer:用于實(shí)際存儲(chǔ)顏色、頂點(diǎn)等的緩存區(qū)
  • 設(shè)置FrameBuffer:用于管理renderBuffer

繪制**

繪制的流程圖如圖所示,主要分為5部分

  • 初始化
  • GLSL自定義著色器加載
  • 設(shè)置頂點(diǎn)數(shù)據(jù)
  • 構(gòu)建矩陣 & 傳遞到頂點(diǎn)著色器
  • 索引繪圖


    繪制流程

其中初始化、自定義著色器兩部分的代碼可參考OpenGL ES 案例04:GLSL加載圖片,也可直接在文末的github上查看源代碼,在下面的說明中只簡述下大致步驟

初始化

這部分主要有以下幾步:

  • 設(shè)置清屏顏色
  • 清理緩存區(qū)
  • 設(shè)置視口

GLSL自定義著色器加載

著色器的加載的流程如圖所示


自定義著色器加載
  • 獲取自定義著色器文件地址
  • 判斷program是否已經(jīng)存在,如果存在則刪除
  • 加載著色器

加載著色器中主要分為4步

  • 編譯著色器
  • 創(chuàng)建最終的程序:shader附著在program上
  • 鏈接program & 判斷是否鏈接成功
  • 使用program

到此,GLSL編寫的自定義著色器就加載完成了。

設(shè)置頂點(diǎn)數(shù)據(jù)

由于圖形的繪制是使用的索引繪制,所以除了創(chuàng)建頂點(diǎn)數(shù)組外,還需要?jiǎng)?chuàng)建索引數(shù)組,大致流程如下


設(shè)置頂點(diǎn)數(shù)據(jù)流程
  • 設(shè)置頂點(diǎn)數(shù)組 & 索引數(shù)組
    • 頂點(diǎn)數(shù)組中包含一個(gè)頂點(diǎn)包含6個(gè)數(shù)據(jù),前3位表示頂點(diǎn)值(x,y,z),后3位表示顏色值(RGB)
    • 索引數(shù)組中每3個(gè)數(shù)表示一個(gè)三角形,其中的數(shù)據(jù)為頂點(diǎn)的標(biāo)識(shí)符,表示三角形由這三個(gè)頂點(diǎn)構(gòu)成
    GLfloat attrArr[] =
    {
        -0.5f, 0.5f, 0.0f,      1.0f, 0.0f, 1.0f, //左上0
        0.5f, 0.5f, 0.0f,       1.0f, 0.0f, 1.0f, //右上1
        -0.5f, -0.5f, 0.0f,     1.0f, 1.0f, 1.0f, //左下2
        
        0.5f, -0.5f, 0.0f,      1.0f, 1.0f, 1.0f, //右下3
        0.0f, 0.0f, 1.0f,       0.0f, 1.0f, 0.0f, //頂點(diǎn)4
    };
    
    //(2).索引數(shù)組
    GLuint indices[] =
    {
        0, 3, 2,
        0, 1, 3,
        0, 2, 4,
        0, 4, 1,
        2, 3, 4,
        1, 4, 3,
    };

后面3步的代碼在之前的案例OpenGL ES 案例04:GLSL加載圖片中也有涉及,就不再過多說明

  • 開辟緩存區(qū),將頂點(diǎn)數(shù)據(jù)從CPU拷貝至GPU
  • 處理頂點(diǎn)坐標(biāo)數(shù)據(jù):打開attribute通道,將頂點(diǎn)坐標(biāo)傳入頂點(diǎn)著色器
  • 處理頂點(diǎn)顏色數(shù)據(jù):打開attribute通道,將頂點(diǎn)顏色傳入頂點(diǎn)著色器

構(gòu)建矩陣
這部分主要是構(gòu)建mvp矩陣,由于案例圖形有旋轉(zhuǎn)操作,還需要構(gòu)建旋轉(zhuǎn)矩陣

構(gòu)建矩陣流程

主要有以下4步

  • 獲取頂點(diǎn)著色器中uniform修飾的投影矩陣、模型視圖矩陣的入口,方便后續(xù)將對(duì)應(yīng)矩陣傳入頂點(diǎn)著色器中,入口的獲取與attribute入口獲取類似,其中的name都需要與著色器中一模一樣
GLuint projectionMatrixSlot = glGetUniformLocation(self.myPrograme, "projectionMatrix");
GLuint modelViewMatrixSlot = glGetUniformLocation(self.myPrograme, "modelViewMatrix");
  • 獲取屏幕的縱橫比,用于設(shè)置透視投影
float width = self.frame.size.width;
float height = self.frame.size.height;
//獲取縱橫比
float aspect = width / height;
  • 創(chuàng)建投影矩陣
    • 定義一個(gè)4*4的投影矩陣
    • 通過ksMatrixLoadIdentity將投影矩陣初始化為單元矩陣
    • 通過ksPerspective方法,設(shè)置透視投影
    • 通過glUniformMatrix4fv方法,將投影矩陣傳遞到vertex shader

矩陣涉及的方法均為數(shù)學(xué)三方庫中的封裝好的

KSMatrix4 _projectionMatrix;
    //(1)獲取單元矩陣
    ksMatrixLoadIdentity(&_projectionMatrix);
    //(3)獲取透視矩陣
    /*
     參數(shù)1:矩陣
     參數(shù)2:視角,度數(shù)為單位
     參數(shù)3:縱橫比
     參數(shù)4:近平面距離
     參數(shù)5:遠(yuǎn)平面距離
     */
    ksPerspective(&_projectionMatrix, 30.0, aspect, 5.0f, 20.0f);
    
    //(4)將投影矩陣傳遞到頂點(diǎn)著色器
    /*
     參數(shù)1-location:指要更改的uniform變量的位置
     參數(shù)2-count:更改矩陣的個(gè)數(shù)
     參數(shù)3-transpose:是否要轉(zhuǎn)置矩陣,并將它作為uniform變量的值。必須為GL_FALSE
     參數(shù)4-value:執(zhí)行count個(gè)元素的指針,用來更新指定uniform變量
     */
    glUniformMatrix4fv(projectionMatrixSlot, 1, GL_FALSE, (GLfloat*)&_projectionMatrix.m[0][0]);
  • 創(chuàng)建模型試圖矩陣
    • 定義一個(gè)4*4的模型視圖矩陣
    • 模型視圖通過ksMatrixLoadIdentity載入一個(gè)單元矩陣
    • 將圖形往屏幕里平移,即z軸平移-10,為了方便觀察圖形
    • 定義一個(gè)4*4的旋轉(zhuǎn)矩陣
    • 旋轉(zhuǎn)矩陣通過ksMatrixLoadIdentity載入一個(gè)單元矩陣
    • 通過ksRotate分別設(shè)置x、y、z相對(duì)應(yīng)的旋轉(zhuǎn)
    • 將旋轉(zhuǎn)矩陣與模型視圖矩陣相乘,將結(jié)果存儲(chǔ)到模型視圖矩陣
    • 將mv矩陣傳遞到vertex shader
//創(chuàng)建4*4的模型視圖矩陣
    KSMatrix4 _modelViewMatrix;
    //初始化
    //(1)獲取單元矩陣
    ksMatrixLoadIdentity(&_modelViewMatrix);
    //為了方便觀察,圍繞z軸往屏幕里平移10個(gè)像素點(diǎn)
    //(2)平移,z軸平移-10
    ksTranslate(&_modelViewMatrix, 0.0, 0.0, -10.0);
    
    //創(chuàng)建旋轉(zhuǎn)
    //(3)創(chuàng)建一個(gè)4 * 4 矩陣,旋轉(zhuǎn)矩陣
    KSMatrix4 _rotationMatrix;
    //初始化
    //(4)初始化為單元矩陣
    ksMatrixLoadIdentity(&_rotationMatrix);
    //有可能圍繞 x / y / z任一軸旋轉(zhuǎn)(為什么不寫一行?不確定圍繞哪個(gè)軸旋轉(zhuǎn))
    //(5)旋轉(zhuǎn)
    ksRotate(&_rotationMatrix, xDegree, 1, 0, 0);
    ksRotate(&_rotationMatrix, yDegree, 0, 1, 0);
    ksRotate(&_rotationMatrix, zDegree, 0, 0, 1);
    
    //(6)把變換矩陣相乘.將_modelViewMatrix矩陣與_rotationMatrix矩陣相乘,結(jié)合到模型視圖
    //矩陣相乘 modelview = rotation x modelview
    ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
    
    //將mv矩陣傳遞到頂點(diǎn)著色器
    //(7)將模型視圖矩陣傳遞到頂點(diǎn)著色器
    /*
     void glUniformMatrix4fv(GLint location,  GLsizei count,  GLboolean transpose,  const GLfloat *value);
     參數(shù)列表:
     location:指要更改的uniform變量的位置
     count:更改矩陣的個(gè)數(shù)
     transpose:是否要轉(zhuǎn)置矩陣,并將它作為uniform變量的值。必須為GL_FALSE
     value:執(zhí)行count個(gè)元素的指針,用來更新指定uniform變量
     */
    glUniformMatrix4fv(modelViewMatrixSlot, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);

索引繪圖

索引繪圖

  • 開啟正背面剔除glEnable(GL_CULL_FACE);
  • 通過glDrawElements函數(shù),使用索引繪圖,方法中3個(gè)參數(shù)
    • mode:要呈現(xiàn)的畫圖的模式
    • count:繪圖個(gè)數(shù),這里不是指頂點(diǎn)的個(gè)數(shù),是索引的個(gè)數(shù)
    • type:類型
    • indices:索引數(shù)組
mode模式 type 類型
GL_POINTS GL_BYTE
GL_LINES GL_UNSIGNED_BYTE
GL_LINE_LOOP GL_SHORT
GL_LINE_STRIP GL_UNSIGNED_SHORT
GL_TRIANGLES GL_INT
GL_TRIANGLE_STRIP GL_UNSIGNED_INT
GL_TRIANGLE_FAN ----
glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(indices[0]), GL_UNSIGNED_INT, indices);
  • 要求本地窗口顯示渲染的目標(biāo)
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];

按鈕點(diǎn)擊事件

主要是3個(gè)按鈕的點(diǎn)擊事件,點(diǎn)擊事件實(shí)現(xiàn)分為以下兩步

  • 開啟定時(shí)器
  • 判斷是否旋轉(zhuǎn)
  • 執(zhí)行定時(shí)器方法
    • 更新角度
    • 重新渲染
點(diǎn)擊事件圖示

完整的代碼見github - 11_01_GLSL三角形變換_OC、11_01_GLSL三角形變換_Swift

最后編輯于
?著作權(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ù)。

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