iOS-OpenGLES-入門-立方體

前言

這是一篇OpenGlES 系統(tǒng)學(xué)習(xí)教程,記錄自己的學(xué)習(xí)過程。
環(huán)境: Xcode10.2 + OpenGL ES 3.0
目標(biāo): 3D 立方體
這里是demo,你的star和fork是對我最好的支持和動(dòng)力。

效果展示

立方體.jpg

坐標(biāo)系統(tǒng)

主要有5個(gè)不同的坐標(biāo)系統(tǒng):

  • 局部空間(Local Space,或者稱為物體空間(Object Space))
  • 世界空間(World Space)
  • 觀察空間(View Space,或者稱為視覺空間(Eye Space))
  • 裁剪空間(Clip Space)
  • 屏幕空間(Screen Space)

變換
為了將坐標(biāo)從一個(gè)坐標(biāo)系變換到另一個(gè)坐標(biāo)系,我們需要用到幾個(gè)變換矩陣,最重要的幾個(gè)分別是模型(Model)、觀察(View)、投影(Projection)三個(gè)矩陣。我們的頂點(diǎn)坐標(biāo)起始于局部空間(Local Space),在這里它稱為局部坐標(biāo)(Local Coordinate),它在之后會(huì)變?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é)束。下面的這張圖展示了整個(gè)流程以及各個(gè)變換過程做了什么:

坐標(biāo)系變換.png
  • 局部坐標(biāo)是對象相對于局部原點(diǎn)的坐標(biāo),也是物體起始的坐標(biāo)。
  • 下一步是將局部坐標(biāo)變換為世界空間坐標(biāo),世界空間坐標(biāo)是處于一個(gè)更大的空間范圍的。這些坐標(biāo)相對于世界的全局原點(diǎn),它們會(huì)和其它物體一起相對于世界的原點(diǎn)進(jìn)行擺放。
  • 接下來我們將世界坐標(biāo)變換為觀察空間坐標(biāo),使得每個(gè)坐標(biāo)都是從攝像機(jī)或者說觀察者的角度進(jìn)行觀察的。
  • 坐標(biāo)到達(dá)觀察空間之后,我們需要將其投影到裁剪坐標(biāo)。裁剪坐標(biāo)會(huì)被處理至-1.0到1.0的范圍內(nèi),并判斷哪些頂點(diǎn)將會(huì)出現(xiàn)在屏幕上
  • 最后,我們將裁剪坐標(biāo)變換為屏幕坐標(biāo),我們將使用一個(gè)叫做視口變換(Viewport Transform)的過程。視口變換將位于-1.0到1.0范圍的坐標(biāo)變換到由glViewport函數(shù)所定義的坐標(biāo)范圍內(nèi)。最后變換出來的坐標(biāo)將會(huì)送到光柵器,將其轉(zhuǎn)化為片段。
    以上摘自這里,感興趣的朋友可以細(xì)讀一番。

投影變換

透視投影
在現(xiàn)實(shí)生活中近大遠(yuǎn)小的效果稱之為透視。如鐵軌的兩條軌道,由于透視,在很遠(yuǎn)的地方看起來會(huì)相交一樣,這就是透視投影想要模仿的效果,它通過透視投影矩陣來完成,推導(dǎo)過程可以看這里。

透視投影.png

上圖是視椎體,透視投影圖形化的過程。
如果要對視椎體進(jìn)行完全控制,可以使用frustum方法,或者也可以使用更為直觀的lookA方法。

    // 根據(jù)給定的視椎體設(shè)置返回一個(gè)透視投影矩陣。近平面的矩形通過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定義的上方向,返回一個(gè)透視投影矩陣。
    static func lookAt(resultM4 result:UnsafeMutablePointer<MatrixArray<Float>>, eye:UnsafePointer<Vec3>, target:UnsafePointer<Vec3>, up:UnsafePointer<Vec3>)

正交投影
當(dāng)使用正射投影時(shí),每一個(gè)頂點(diǎn)坐標(biāo)都會(huì)直接映射到裁剪空間中而不經(jīng)過任何精細(xì)的透視除法(它仍然會(huì)進(jìn)行透視除法,只是w分量沒有被改變(它保持為1),因此沒有起作用)。因?yàn)檎渫队皼]有使用透視,遠(yuǎn)處的物體不會(huì)顯得更小,所以產(chǎn)生奇怪的視覺效果。由于這個(gè)原因,正射投影主要用于二維渲染以及一些建筑或工程的程序,在這些場景中我們更希望頂點(diǎn)不會(huì)被透視所干擾

正交投影.png

組合

把以上每個(gè)步驟創(chuàng)建的變換矩陣:模型矩陣、觀察矩陣和投影矩陣組合起來。一個(gè)頂點(diǎn)坐標(biāo)將會(huì)根據(jù)以下過程被變換到剪裁坐標(biāo)系:


變換過程.png

注意矩陣運(yùn)算的順序是相反的(記住我們需要從右往左閱讀矩陣的乘法)。最后的頂點(diǎn)應(yīng)該被賦值到頂點(diǎn)著色器中的gl_Position,OpenGL將會(huì)自動(dòng)進(jìn)行透視除法和裁剪。

3D立方體

頂點(diǎn)數(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)的觀察者位置在原點(diǎn),視線朝向 -Z 方向,因此近裁剪面其實(shí)就在 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);

最后注意點(diǎn)

  • 需要手動(dòng)開啟深度緩存,否則立方體將丟失深度信息 glEnable(GLenum(GL_DEPTH_TEST))
    注:OpenGL應(yīng)該只需要開啟就ok了,OpenGlES 還需要手動(dòng)創(chuàng)建深度緩存(這點(diǎn)還未深究,有知道朋友可以留言)。如下
        // 創(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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