前言
這是一篇OpenGlES 系統(tǒng)學(xué)習(xí)教程,記錄自己的學(xué)習(xí)過程英妓。
環(huán)境: Xcode10.2 + OpenGL ES 3.0
目標(biāo): 3D 立方體
這里是demo也拜,你的star和fork是對我最好的支持和動力。
效果展示
坐標(biāo)系統(tǒng)
主要有5個不同的坐標(biāo)系統(tǒng):
- 局部空間(Local Space丑瞧,或者稱為物體空間(Object Space))
- 世界空間(World Space)
- 觀察空間(View Space,或者稱為視覺空間(Eye Space))
- 裁剪空間(Clip Space)
- 屏幕空間(Screen Space)
變換
為了將坐標(biāo)從一個坐標(biāo)系變換到另一個坐標(biāo)系蜀肘,我們需要用到幾個變換矩陣绊汹,最重要的幾個分別是模型
(Model)、觀察
(View)扮宠、投影
(Projection)三個矩陣西乖。我們的頂點坐標(biāo)起始于局部空間
(Local Space),在這里它稱為局部坐標(biāo)
(Local Coordinate)坛增,它在之后會變?yōu)?code>世界坐標(biāo)(World Coordinate)获雕,觀察坐標(biāo)
(View Coordinate),裁剪坐標(biāo)
(Clip Coordinate)收捣,并最后以屏幕坐標(biāo)
[圖片上傳中...(坐標(biāo)系變換.png-d41a13-1555986546530-0)]
(Screen Coordinate)的形式結(jié)束届案。下面的這張圖展示了整個流程以及各個變換過程做了什么:
- 局部坐標(biāo)是對象相對于局部原點的坐標(biāo),也是物體起始的坐標(biāo)罢艾。
- 下一步是將局部坐標(biāo)變換為世界空間坐標(biāo)楣颠,世界空間坐標(biāo)是處于一個更大的空間范圍的。這些坐標(biāo)相對于世界的全局原點咐蚯,它們會和其它物體一起相對于世界的原點進(jìn)行擺放童漩。
- 接下來我們將世界坐標(biāo)變換為觀察空間坐標(biāo),使得每個坐標(biāo)都是從攝像機或者說觀察者的角度進(jìn)行觀察的春锋。
- 坐標(biāo)到達(dá)觀察空間之后矫膨,我們需要將其投影到裁剪坐標(biāo)。裁剪坐標(biāo)會被處理至-1.0到1.0的范圍內(nèi)期奔,并判斷哪些頂點將會出現(xiàn)在屏幕上
- 最后侧馅,我們將裁剪坐標(biāo)變換為屏幕坐標(biāo),我們將使用一個叫做視口變換(Viewport Transform)的過程呐萌。視口變換將位于-1.0到1.0范圍的坐標(biāo)變換到由glViewport函數(shù)所定義的坐標(biāo)范圍內(nèi)施禾。最后變換出來的坐標(biāo)將會送到光柵器,將其轉(zhuǎn)化為片段搁胆。
以上摘自這里,感興趣的朋友可以細(xì)讀一番邮绿。
投影變換
透視投影
在現(xiàn)實生活中近大遠(yuǎn)小的效果稱之為透視
渠旁。如鐵軌的兩條軌道,由于透視船逮,在很遠(yuǎn)的地方看起來會相交一樣顾腊,這就是透視投影想要模仿的效果,它通過透視投影矩陣
來完成挖胃,推導(dǎo)過程可以看這里杂靶。
上圖是視椎體梆惯,透視投影圖形化的過程。
如果要對視椎體進(jìn)行完全控制吗垮,可以使用frustum方法垛吗,或者也可以使用更為直觀的lookA方法。
// 根據(jù)給定的視椎體設(shè)置返回一個透視投影矩陣烁登。近平面的矩形通過left怯屉、right、bottom和top定義饵沧。近平面和遠(yuǎn)平面的距離通過near和far定義
static func frustum(resultM4 result:UnsafeMutablePointer<MatrixArray<Float>>, _ left:Float, _ right:Float, _ bottom:Float, _ top:Float, _ nearZ:Float, _ farZ:Float)
// 根據(jù)eye朝向target的視線锨络,以及up定義的上方向,返回一個透視投影矩陣狼牺。
static func lookAt(resultM4 result:UnsafeMutablePointer<MatrixArray<Float>>, eye:UnsafePointer<Vec3>, target:UnsafePointer<Vec3>, up:UnsafePointer<Vec3>)
正交投影
當(dāng)使用正射投影時羡儿,每一個頂點坐標(biāo)都會直接映射到裁剪空間中而不經(jīng)過任何精細(xì)的透視除法(它仍然會進(jìn)行透視除法,只是w分量沒有被改變(它保持為1)是钥,因此沒有起作用)掠归。因為正射投影沒有使用透視,遠(yuǎn)處的物體不會顯得更小咏瑟,所以產(chǎn)生奇怪的視覺效果拂到。由于這個原因,正射投影主要用于二維渲染以及一些建筑或工程的程序码泞,在這些場景中我們更希望頂點不會被透視所干擾
組合
把以上每個步驟創(chuàng)建的變換矩陣:模型矩陣兄旬、觀察矩陣和投影矩陣組合起來。一個頂點坐標(biāo)將會根據(jù)以下過程被變換到剪裁坐標(biāo)系:
注意矩陣運算的順序是相反的(記住我們需要從右往左閱讀矩陣的乘法)余寥。最后的頂點應(yīng)該被賦值到頂點著色器中的gl_Position领铐,OpenGL將會自動進(jìn)行透視除法和裁剪。
3D立方體
頂點數(shù)據(jù)
let vertices: [GLfloat] = [
// 前面
-0.5, 0.5, 0.5, 0.0, 1.0, // 前左上 0
-0.5, -0.5, 0.5, 0.0, 0.0, // 前左下 1
0.5, -0.5, 0.5, 1.0, 0.0, // 前右下 2
0.5, 0.5, 0.5, 1.0, 1.0, // 前右上 3
// 后面
-0.5, 0.5, -0.5, 1.0, 1.0, // 后左上 4
-0.5, -0.5, -0.5, 1.0, 0.0, // 后左下 5
0.5, -0.5, -0.5, 0.0, 0.0, // 后右下 6
0.5, 0.5, -0.5, 0.0, 1.0, // 后右上 7
// 左面
-0.5, 0.5, -0.5, 0.0, 1.0, // 后左上 8
-0.5, -0.5, -0.5, 0.0, 0.0, // 后左下 9
-0.5, 0.5, 0.5, 1.0, 1.0, // 前左上 10
-0.5, -0.5, 0.5, 1.0, 0.0, // 前左下 11
// 右面
0.5, 0.5, 0.5, 0.0, 1.0, // 前右上 12
0.5, -0.5, 0.5, 0.0, 0.0, // 前右下 13
0.5, -0.5, -0.5, 1.0, 0.0, // 后右下 14
0.5, 0.5, -0.5, 1.0, 1.0, // 后右上 15
// 上面
-0.5, 0.5, 0.5, 0.0, 0.0, // 前左上 16
0.5, 0.5, 0.5, 1.0, 0.0, // 前右上 17
-0.5, 0.5, -0.5, 0.0, 1.0, // 后左上 18
0.5, 0.5, -0.5, 1.0, 1.0, // 后右上 19
// 下面
-0.5, -0.5, 0.5, 0.0, 1.0, // 前左下 20
0.5, -0.5, 0.5, 1.0, 1.0, // 前右下 21
-0.5, -0.5, -0.5, 0.0, 0.0, // 后左下 22
0.5, -0.5, -0.5, 1.0, 0.0, // 后右下 23
]
// 索引
let indices:[GLubyte] = [
// 前面
0, 1, 2,
0, 2, 3,
// 后面
4, 5, 6,
4, 6, 7,
// 左面
8, 9, 11,
8, 11, 10,
// 右面
12, 13, 14,
12, 14, 15,
// 上面
18, 16, 17,
18, 17, 19,
// 下面
20, 22, 23,
20, 23, 21
]
矩陣變換過程
let width = frame.size.width
let height = frame.size.height
var projectionMatrix = Matrix.matrix4(0)
Matrix.matrixLoadIdentity(resultM4: &projectionMatrix)
let aspect = width / height
// 我們設(shè)置視錐體的近裁剪面到觀察者的距離為 0.1宋舷, 遠(yuǎn)裁剪面到觀察者的距離為 100绪撵,視角為 35度,然后裝載投影矩陣祝蝠。默認(rèn)的觀察者位置在原點音诈,視線朝向 -Z 方向,因此近裁剪面其實就在 z = -0.01 這地方绎狭,遠(yuǎn)裁剪面在 z = -100 這地方细溅,z 值不在(-0.01, -100) 之間的物體是看不到的
Matrix.perspective(resultM4: &projectionMatrix, 35, Float(aspect), 0.1, 100) //透視變換,視角30°
// 設(shè)置glsl投影矩陣
glUniformMatrix4fv(GLint(projectionMatrixSlot), 1, GLboolean(GL_FALSE), projectionMatrix.array)
var modelViewMatrix = Matrix.matrix4(0)
Matrix.matrixLoadIdentity(resultM4: &modelViewMatrix)
var viewMatrix = Matrix.matrix4(0)
Matrix.matrixLoadIdentity(resultM4: &viewMatrix)
var eyeVec3 = Vec3(x:0,y:0,z:3)
var targetVec3 = Vec3(x:0,y:0,z:0)
var upVec3 = Vec3(x:0,y:1,z:0)
Matrix.lookAt(resultM4: &viewMatrix, eye: &eyeVec3, target: &targetVec3, up: &upVec3)
glUniformMatrix4fv(GLint(viewSlot), 1, GLboolean(GL_FALSE), viewMatrix.array)
// 平移
// 設(shè)置 z 值 (-0.01,-100)之間
Matrix.matrixTranslate(resultM4: &modelViewMatrix, tx: 0, ty: 0, tz: -3)
var rotationMatrix = Matrix.matrix4(0)
Matrix.matrixLoadIdentity(resultM4: &rotationMatrix)
// 旋轉(zhuǎn)
Matrix.matrixRotate(resultM4: &rotationMatrix, angle: degreeY, x: 1, y: 0, z: 0)
Matrix.matrixRotate(resultM4: &rotationMatrix, angle: degreeX, x: 0, y: 1, z: 0)
var modelViewMatrixCopy = modelViewMatrix
Matrix.matrixMultiply(resultM4: &modelViewMatrix, aM4: &rotationMatrix, bM4: &modelViewMatrixCopy)
glUniformMatrix4fv(GLint(modelViewMatrixSlot), 1, GLboolean(GL_FALSE), modelViewMatrix.array);
最后注意點
- 需要手動開啟深度緩存儡嘶,否則立方體將丟失深度信息
glEnable(GLenum(GL_DEPTH_TEST))
注:OpenGL應(yīng)該只需要開啟就ok了喇聊,OpenGlES 還需要手動創(chuàng)建深度緩存(這點還未深究,有知道朋友可以留言)蹦狂。如下
// 創(chuàng)建深度緩沖區(qū)
var depthRenderBuffer:GLuint = 0
glGenRenderbuffers(1, &depthRenderBuffer)
glBindRenderbuffer(GLenum(GL_RENDERBUFFER), depthRenderBuffer)
glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_DEPTH_COMPONENT16), width, height)
myDepthRenderBuffer = depthRenderBuffer