前面一篇我們了解了OpenGL渲染一張圖片的過程。接下來我們要了解的是使用GLSL如何渲染金字塔以及一些簡單的變換。先看效果圖

效果圖
步驟還是和之前是一樣的。
- 日常開發(fā)中OpenGL開發(fā)流程
- 1.設置圖層
- 2.設置圖形上下文
- 3.設置渲染緩沖區(qū)(renderBuffer)
- 4.設置幀緩沖區(qū)(frameBuffer)
- 5.編譯、鏈接著色器(shader)
- 6.設置VBO (Vertex Buffer Objects)
- 7.設置紋理
- 8.渲染
前5步除了著色器外和第7步是一致的代碼,就不貼了。
- 頂點著色器代碼:
attribute vec4 position;
attribute vec4 positionColor; //頂點顏色
attribute vec2 textCoordinate; //紋理坐標
uniform mat4 projectionMatrix; //投影矩陣
uniform mat4 modelViewMatrix; //模型視圖矩陣
varying lowp vec4 varyColor; //頂點顏色
varying lowp vec2 varyTextCoord; //傳遞給片元著色器紋理坐標
void main()
{
varyColor = positionColor;
varyTextCoord = textCoordinate;
vec4 vPos;
vPos = projectionMatrix * modelViewMatrix * position;
gl_Position = vPos;
}
- 片元著色器代碼:
varying lowp vec4 varyColor; //頂點顏色
varying lowp vec2 varyTextCoord; //頂點著色器傳遞過來的紋理坐標
uniform sampler2D colorMap; //紋理
void main()
{
gl_FragColor = texture2D(colorMap, varyTextCoord) * varyColor;
}
- 設置VBO (Vertex Buffer Objects)
金字塔一共有5個面:4面+底面(正方形) = 6個三角形
框架圖
//6.設置VBO (Vertex Buffer Objects)
func setupVBO() {
//6.設置頂點、紋理坐標
//頂點數(shù)組
//前3個元素,是頂點數(shù)據(jù);中間3個元素,是頂點顏色值,最后2個是紋理坐標
let attrArr: [GLfloat] = [
-0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 1.0,//左上
0.5, 0.5, 0.0, 0.0, 0.5, 0.0, 1.0, 1.0,//右上
-0.5, -0.5, 0.0, 0.5, 0.0, 1.0, 0.0, 0.0,//左下
0.5, -0.5, 0.0, 0.0, 0.0, 0.5, 1.0, 0.0,//右下
0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5,//頂點
]
//創(chuàng)建繪制索引數(shù)組
let indices: [GLuint] = [
0, 3, 2,
0, 1, 3,
0, 2, 4,
0, 4, 1,
2, 3, 4,
1, 4, 3,
]
self.indices = indices
//-----處理頂點數(shù)據(jù)--------
//頂點緩存區(qū)
var attrBuffer: GLuint = 0
//申請一個緩存區(qū)標識符
glGenBuffers(1, &attrBuffer)
//將attrBuffer綁定到GL_ARRAY_BUFFER標識符上
glBindBuffer(GLenum(GL_ARRAY_BUFFER), attrBuffer)
//把頂點數(shù)據(jù)從CPU拷貝到GPU上
glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * attrArr.count, attrArr, GLenum(GL_DYNAMIC_DRAW))
}
- 8.渲染繪制
我這么這里是通過索引來繪制的,這里我們繪制的是一個立體的圖形,所以我們引入了,OpenGL的另一個知識點,就是投影和變換,都是通過矩陣來實現(xiàn)的。這里有兩個重點的知識點。
//8.開始繪制
func renderLayer() {
//設置清屏顏色
glClearColor(0.0, 0.0, 1.0, 1.0)
//清除屏幕
glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
//1.設置視口大小
let scale = UIScreen.main.scale
glViewport(GLint(self.frame.origin.x * scale), GLint(self.frame.origin.y * scale), GLsizei(self.frame.size.width * scale), GLsizei(self.frame.size.height * scale))
//使用著色器
glUseProgram(myProgram)
#warning("注意??:想要獲取shader里面的變量,這里要記住要在glLinkProgram后面、后面、后面")
/*
一個一致變量在一個圖元的繪制過程中是不會改變的,所以其值不能在glBegin/glEnd中設置。一致變量適合描述在一個圖元中、一幀中甚至一個場景中都不變的值。一致變量在頂點shader和片段shader中都是只讀的。首先你需要獲得變量在內(nèi)存中的位置,這個信息只有在連接程序之后才可獲得。
*/
//--------處理頂點數(shù)據(jù)-------
//1.將頂點數(shù)據(jù)通過myProgram中的傳遞到頂點著色程序的position
let position = glGetAttribLocation(myProgram, "position")
//2.
glEnableVertexAttribArray(GLuint(position))
//3.設置讀取方式
//參數(shù)1:index,頂點數(shù)據(jù)的索引
//參數(shù)2:size,每個頂點屬性的組件數(shù)量,1,2,3,或者4.默認初始值是4.
//參數(shù)3:type,數(shù)據(jù)中的每個組件的類型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默認初始值為GL_FLOAT
//參數(shù)4:normalized,固定點數(shù)據(jù)值是否應該歸一化,或者直接轉換為固定值。(GL_FALSE)
//參數(shù)5:stride,連續(xù)頂點屬性之間的偏移量,默認為0;
//參數(shù)6:指定一個指針,指向數(shù)組中的第一個頂點屬性的第一個組件。默認為0
glVertexAttribPointer(GLuint(position), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 8), nil)
//--------處理頂點顏色值-------
//1.將頂點數(shù)據(jù)通過myProgram中的傳遞到頂點著色程序的positionColor
let positionColor = glGetAttribLocation(myProgram, "positionColor")
glEnableVertexAttribArray(GLuint(positionColor))
glVertexAttribPointer(GLuint(positionColor), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 8), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 3))
//----處理紋理數(shù)據(jù)-------
//1.glGetAttribLocation,用來獲取vertex attribute的入口的.
//注意:第二參數(shù)字符串必須和shaderv.vsh中的輸入變量:textCoordinate保持一致
let textCoord = glGetAttribLocation(myProgram, "textCoordinate")
//設置合適的格式從buffer里面讀取數(shù)據(jù)
glEnableVertexAttribArray(GLuint(textCoord))
//3.設置讀取方式
//參數(shù)1:index,頂點數(shù)據(jù)的索引
//參數(shù)2:size,每個頂點屬性的組件數(shù)量,1,2,3,或者4.默認初始值是4.
//參數(shù)3:type,數(shù)據(jù)中的每個組件的類型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默認初始值為GL_FLOAT
//參數(shù)4:normalized,固定點數(shù)據(jù)值是否應該歸一化,或者直接轉換為固定值。(GL_FALSE)
//參數(shù)5:stride,連續(xù)頂點屬性之間的偏移量,默認為0;
//參數(shù)6:指定一個指針,指向數(shù)組中的第一個頂點屬性的第一個組件。默認為0
glVertexAttribPointer(GLuint(textCoord), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 8), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 6))
//----處理矩陣數(shù)據(jù)-------
//找到myProgram中的projectionMatrix、modelViewMatrix 2個矩陣的地址。如果找到則返回地址,否則返回-1,表示沒有找到2個對象。
let projectionMatrixSlot = glGetUniformLocation(myProgram, "projectionMatrix")
let modelViewMatrixSlot = glGetUniformLocation(myProgram, "modelViewMatrix")
let width = self.frame.size.width
let height = self.frame.size.height
//創(chuàng)建4 * 4矩陣 獲取單元矩陣
var _projectionMatrix: GLKMatrix4 = GLKMatrix4Identity
//計算縱橫比例 = 長/寬
let aspect = width / height; //長寬比
//獲取透視矩陣
/*
參數(shù)1:矩陣
參數(shù)2:視角,度數(shù)為單位
參數(shù)3:縱橫比
參數(shù)4:近平面距離
參數(shù)5:遠平面距離
參考PPT
*/
//源碼實現(xiàn):在這里面
// ksPerspective(<#T##result: UnsafeMutablePointer<KSMatrix4>!##UnsafeMutablePointer<KSMatrix4>!#>, <#T##fovy: Float##Float#>, <#T##aspect: Float##Float#>, <#T##nearZ: Float##Float#>, <#T##farZ: Float##Float#>)
let perspectiveMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(30), Float(aspect), 5, 20)
_projectionMatrix = GLKMatrix4Multiply(_projectionMatrix, perspectiveMatrix)
//設置glsl里面的投影矩陣
/*
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
參數(shù)列表:
location:指要更改的uniform變量的位置
count:更改矩陣的個數(shù)
transpose:是否要轉置矩陣,并將它作為uniform變量的值。必須為GL_FALSE
value:執(zhí)行count個元素的指針,用來更新指定uniform變量
*/
// let count = MemoryLayout.size(ofValue: _projectionMatrix.m) / MemoryLayout.size(ofValue: _projectionMatrix.m.0)
// withUnsafePointer(to: &_projectionMatrix.m) { (pointer) in
// pointer.withMemoryRebound(to: GLfloat.self, capacity: count, { (pon) in
// glUniformMatrix4fv(projectionMatrixSlot, 1, GLboolean(GL_FALSE), pon)
// })
// }
glUniformMatrix4fv(projectionMatrixSlot, 1, GLboolean(GL_FALSE), &_projectionMatrix.m.0)
//開啟剔除操作效果 (三角形逆時針方向為正面)
glEnable(GLenum(GL_CULL_FACE))
//創(chuàng)建一個4 * 4 矩陣,模型視圖
var _modelViewMatrix: GLKMatrix4 = GLKMatrix4Identity
//平移,z軸平移-10
_modelViewMatrix = GLKMatrix4Translate(_modelViewMatrix, 0.0, 0.0, -10.0)
//創(chuàng)建一個4 * 4 矩陣,旋轉矩陣
var _rotationMatrix: GLKMatrix4 = GLKMatrix4Identity
//旋轉
_rotationMatrix = GLKMatrix4Rotate(_rotationMatrix, GLKMathDegreesToRadians(xDegree), 1.0, 0.0, 0.0)
_rotationMatrix = GLKMatrix4Rotate(_rotationMatrix, GLKMathDegreesToRadians(yDegree), 0.0, 1.0, 0.0)
_rotationMatrix = GLKMatrix4Rotate(_rotationMatrix, GLKMathDegreesToRadians(zDegree), 0.0, 0.0, 1.0)
//注意??????:把變換矩陣相乘,注意先后順序 ,將平移矩陣與旋轉矩陣相乘,結合到模型視圖
_modelViewMatrix = GLKMatrix4Multiply(_modelViewMatrix, _rotationMatrix)
// 加載模型視圖矩陣 modelViewMatrixSlot
//設置glsl里面的投影矩陣
/*
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
參數(shù)列表:
location:指要更改的uniform變量的位置
count:更改矩陣的個數(shù)
transpose:是否要轉置矩陣,并將它作為uniform變量的值。必須為GL_FALSE
value:執(zhí)行count個元素的指針,用來更新指定uniform變量
*/
// let count1 = MemoryLayout.size(ofValue: _modelViewMatrix.m) / MemoryLayout.size(ofValue: _modelViewMatrix.m.0)
// withUnsafePointer(to: &_modelViewMatrix.m) { (pointer) in
// pointer.withMemoryRebound(to: GLfloat.self, capacity: count1, { (pon) in
// glUniformMatrix4fv(modelViewMatrixSlot, 1, GLboolean(GL_FALSE), pon)
// })
// }
glUniformMatrix4fv(modelViewMatrixSlot, 1, GLboolean(GL_FALSE), &_modelViewMatrix.m.0)
//使用索引繪圖
/*
void glDrawElements(GLenum mode,GLsizei count,GLenum type,const GLvoid * indices);
參數(shù)列表:
mode:要呈現(xiàn)的畫圖的模型
GL_POINTS
GL_LINES
GL_LINE_LOOP
GL_LINE_STRIP
GL_TRIANGLES
GL_TRIANGLE_STRIP
GL_TRIANGLE_FAN
count:繪圖個數(shù)
type:類型
GL_BYTE
GL_UNSIGNED_BYTE
GL_SHORT
GL_UNSIGNED_SHORT
GL_INT
GL_UNSIGNED_INT
indices:繪制索引數(shù)組
注意:??????
glArrayElements()、glDrawElements()和glDrawRangeElements()能夠對數(shù)據(jù)數(shù)組進行隨機存取,
但是glDrawArrays()只能按順序訪問它們。因為前者支持頂點索引的機制
*/
let dotCount = MemoryLayout<GLfloat>.size * indices.count / MemoryLayout<GLfloat>.size
glDrawElements(GLenum(GL_TRIANGLES), GLsizei(dotCount), GLenum(GL_UNSIGNED_INT), indices)
myContext.presentRenderbuffer(Int(GL_RENDERBUFFER))
}
到此,金字塔就完成了。那么正方體又該如何渲染呢?聰明的同學已經(jīng)知道了。那就是,修改我們的頂點數(shù)據(jù)。接下來一起走進正方體的渲染。
金字塔Demo
- 正方體頂點數(shù)據(jù):圖片來自--CoderP1--06 - OpenGL ES學習之繪制一個立方體
正方體6面解析
//6.設置VBO (Vertex Buffer Objects)
func setupVBO() {
//------------- 正方體 -------------
let attrArr: [GLfloat] = [
// 頂點:(x, y, z) 顏色:(r, g, b) 紋理: (s, t)
// 前面
-0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 0.0, // 前左上 0
-0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, // 前左下 1
0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, // 前右下 2
0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 0.0, // 前右上 3
// 后面
-0.5, 0.5, -0.5, 1.0, 1.0, 1.0, 0.0, 1.0, // 后左上 4
-0.5, -0.5, -0.5, 1.0, 1.0, 1.0, 0.0, 0.0, // 后左下 5
0.5, -0.5, -0.5, 1.0, 1.0, 1.0, 1.0, 0.0, // 后右下 6
0.5, 0.5, -0.5, 1.0, 1.0, 1.0, 1.0, 1.0, // 后右上 7
// 左面
-0.5, 0.5, -0.5, 1.0, 1.0, 1.0, 0.0, 0.0, // 后左上 8
-0.5, -0.5, -0.5, 1.0, 1.0, 1.0, 0.0, 1.0, // 后左下 9
-0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 0.0, // 前左上 10
-0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, // 前左下 11
// 右面
0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 0.0, // 前右上 12
0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, // 前右下 13
0.5, -0.5, -0.5, 1.0, 1.0, 1.0, 1.0, 1.0, // 后右下 14
0.5, 0.5, -0.5, 1.0, 1.0, 1.0, 1.0, 0.0, // 后右上 15
// 上面
-0.5, 0.5, -0.5, 1.0, 1.0, 1.0, 0.0, 0.0, // 后左上 16
-0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 1.0, // 前左上 17
0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, // 前右上 18
0.5, 0.5, -0.5, 1.0, 1.0, 1.0, 1.0, 0.0, // 后右上 19
// 下面
-0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 0.0, // 前左下 20
0.5, -0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 0.0, // 前右下 21
-0.5, -0.5, -0.5, 1.0, 1.0, 1.0, 0.0, 1.0, // 后左下 22
0.5, -0.5, -0.5, 1.0, 1.0, 1.0, 1.0, 1.0, // 后右下 23
]
//創(chuàng)建繪制索引數(shù)組
let indices: [GLuint] = [
// 前面
0, 1, 2,
0, 2, 3,
// 后面
4, 6, 5,
4, 7, 6,
// 左面
8, 9, 11,
8, 11, 10,
// 右面
12, 13, 14,
12, 14, 15,
// 上面
16, 17, 18,
16, 18, 19,
// 下面
20, 22, 23,
20, 23, 21,
]
self.indices = indices
//-----處理頂點數(shù)據(jù)--------
//頂點緩存區(qū)
var attrBuffer: GLuint = 0
//申請一個緩存區(qū)標識符
glGenBuffers(1, &attrBuffer)
//將attrBuffer綁定到GL_ARRAY_BUFFER標識符上
glBindBuffer(GLenum(GL_ARRAY_BUFFER), attrBuffer)
//把頂點數(shù)據(jù)從CPU拷貝到GPU上
glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * attrArr.count, attrArr, GLenum(GL_DYNAMIC_DRAW))
}
但是會出現(xiàn)一種比較奇怪的現(xiàn)象:

奇怪現(xiàn)象1
原因:是開啟了背面剔除
關閉之后還是有其他的奇怪現(xiàn)象,就是感覺被遮擋,如下圖:

奇怪現(xiàn)象2
被遮擋了,怎么辦?--> 開啟深度測試。因為開啟深度測試后, OpenGL 就不會再去繪制模型被遮擋的部分。

深度測試
//4.設置FrameBuffer
func setupFrameBuffer() {
//1.定義一個緩存區(qū)
var buffer: GLuint = 0
//2.申請一個緩存區(qū)標志
glGenFramebuffers(1, &buffer)
//3.將標識符綁定到GL_FRAMEBUFFER
glBindFramebuffer(GLenum(GL_FRAMEBUFFER), buffer)
//4.
frameBuffer = buffer
//生成空間之后,則需要將renderbuffer跟framebuffer進行綁定,調用glFramebufferRenderbuffer函數(shù)進行綁定,后面的繪制才能起作用
//5.將_renderBuffer 通過glFramebufferRenderbuffer函數(shù)綁定到GL_COLOR_ATTACHMENT0上。
glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), renderBuffer)
#warning("設置深度測試")
// 設置深度調試
var width: GLint = 0
var height: GLint = 0
glGetRenderbufferParameteriv(GLenum(GL_RENDERBUFFER), GLenum(GL_RENDERBUFFER_WIDTH), &width)
glGetRenderbufferParameteriv(GLenum(GL_RENDERBUFFER), GLenum(GL_RENDERBUFFER_HEIGHT), &height)
var depthRenderBuffer: GLuint = 0
// 申請深度渲染緩存
glGenRenderbuffers(1, &depthRenderBuffer)
glBindRenderbuffer(GLenum(GL_RENDERBUFFER), depthRenderBuffer)
// 設置深度測試的存儲信息
glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_DEPTH_COMPONENT16), width, height)
// 將渲染緩存掛載到GL_DEPTH_ATTACHMENT這個掛載點上
glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_DEPTH_ATTACHMENT), GLenum(GL_RENDERBUFFER), depthRenderBuffer)
// GL_RENDERBUFFER綁定的是深度測試渲染緩存,所以要綁定回色彩渲染緩存
glBindRenderbuffer(GLenum(GL_RENDERBUFFER), renderBuffer)
//接下來,可以調用OpenGL ES進行繪制處理,最后則需要在EGALContext的OC方法進行最終的渲染繪制。這里渲染的color buffer,這個方法會將buffer渲染到CALayer上。- (BOOL)presentRenderbuffer:(NSUInteger)target;
}
申請了深度緩沖區(qū)之后還要開啟,默認是關閉的。所以在渲染的時候進行開啟:
//清除屏幕
glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
// 開啟深度測試
glEnable(GLenum(GL_DEPTH_TEST))

效果圖
-
問題是解決了。但是為什么開啟背后剔除還是不行?
奇怪現(xiàn)象
想到了是背后剔除,那就是可能頂點數(shù)據(jù)有問題。背面的三角形與正面的三角形的頂點順序相反
頂點順序相反
修改背面頂點索引順序:
// 后面
4, 6, 5,
4, 7, 6,
也是可以達到效果的,而且還開了背面剔除。那么應該不需要開啟深度測試了,為什么呢?
我猜想是因為沒修改頂點順序之前,如上圖?? 正面:[1, 2, 3],背面:[1,3, 2],OpenGL也以為是正面,但是實際是相反的。

效果圖
到此正方體渲染就結束了??赡苡行┩瑢W會想到正方體每個面怎么貼不一樣的圖片呢?那我們下回分解。



