本案例的主要目的是理解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)行圖形繪制的一種技巧
案例的整體效果圖如下

案例的整體流程如圖所示

主要包含三部分
- 準(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ū)別

如圖所示,共有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ù)組 & 索引數(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)矩陣

主要有以下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í)器方法
- 更新角度
- 重新渲染

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