iOS-OpenGLES-入门-立方体

前言

这是一篇OpenGlES 系统学习教程,记录自己的学习过程。
环境: Xcode10.2 + OpenGL ES 3.0
目标: 3D 立方体
这里是demo,你的star和fork是对我最好的支持和动力。

效果展示

立方体.jpg

坐标系统

主要有5个不同的坐标系统:

  • 局部空间(Local Space,或者称为物体空间(Object Space))
  • 世界空间(World Space)
  • 观察空间(View Space,或者称为视觉空间(Eye Space))
  • 裁剪空间(Clip Space)
  • 屏幕空间(Screen Space)

变换
为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。我们的顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标[图片上传中...(坐标系变换.png-d41a13-1555986546530-0)]
(Screen Coordinate)的形式结束。下面的这张图展示了整个流程以及各个变换过程做了什么:

坐标系变换.png
  • 局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。
  • 下一步是将局部坐标变换为世界空间坐标,世界空间坐标是处于一个更大的空间范围的。这些坐标相对于世界的全局原点,它们会和其它物体一起相对于世界的原点进行摆放。
  • 接下来我们将世界坐标变换为观察空间坐标,使得每个坐标都是从摄像机或者说观察者的角度进行观察的。
  • 坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上
  • 最后,我们将裁剪坐标变换为屏幕坐标,我们将使用一个叫做视口变换(Viewport Transform)的过程。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。
    以上摘自这里,感兴趣的朋友可以细读一番。

投影变换

透视投影
在现实生活中近大远小的效果称之为透视。如铁轨的两条轨道,由于透视,在很远的地方看起来会相交一样,这就是透视投影想要模仿的效果,它通过透视投影矩阵来完成,推导过程可以看这里

透视投影.png

上图是视椎体,透视投影图形化的过程。
如果要对视椎体进行完全控制,可以使用frustum方法,或者也可以使用更为直观的lookA方法。

    // 根据给定的视椎体设置返回一个透视投影矩阵。近平面的矩形通过left、right、bottom和top定义。近平面和远平面的距离通过near和far定义
    static func frustum(resultM4 result:UnsafeMutablePointer<MatrixArray<Float>>, _ left:Float, _ right:Float, _ bottom:Float, _ top:Float, _ nearZ:Float, _ farZ:Float)

    // 根据eye朝向target的视线,以及up定义的上方向,返回一个透视投影矩阵。
    static func lookAt(resultM4 result:UnsafeMutablePointer<MatrixArray<Float>>, eye:UnsafePointer<Vec3>, target:UnsafePointer<Vec3>, up:UnsafePointer<Vec3>)

正交投影
当使用正射投影时,每一个顶点坐标都会直接映射到裁剪空间中而不经过任何精细的透视除法(它仍然会进行透视除法,只是w分量没有被改变(它保持为1),因此没有起作用)。因为正射投影没有使用透视,远处的物体不会显得更小,所以产生奇怪的视觉效果。由于这个原因,正射投影主要用于二维渲染以及一些建筑或工程的程序,在这些场景中我们更希望顶点不会被透视所干扰

正交投影.png

组合

把以上每个步骤创建的变换矩阵:模型矩阵、观察矩阵和投影矩阵组合起来。一个顶点坐标将会根据以下过程被变换到剪裁坐标系:


变换过程.png

注意矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。

3D立方体

顶点数据

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
        
        //        我们设置视锥体的近裁剪面到观察者的距离为 0.1, 远裁剪面到观察者的距离为 100,视角为 35度,然后装载投影矩阵。默认的观察者位置在原点,视线朝向 -Z 方向,因此近裁剪面其实就在 z = -0.01 这地方,远裁剪面在 z = -100 这地方,z 值不在(-0.01, -100) 之间的物体是看不到的
        Matrix.perspective(resultM4: &projectionMatrix, 35, Float(aspect), 0.1, 100)  //透视变换,视角30°
        
        // 设置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)
        
        // 平移
        // 设置 z 值 (-0.01,-100)之间
        Matrix.matrixTranslate(resultM4: &modelViewMatrix, tx: 0, ty: 0, tz: -3)
        
        var rotationMatrix = Matrix.matrix4(0)
        Matrix.matrixLoadIdentity(resultM4: &rotationMatrix)
        
        // 旋转
        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应该只需要开启就ok了,OpenGlES 还需要手动创建深度缓存(这点还未深究,有知道朋友可以留言)。如下
        // 创建深度缓冲区
        var depthRenderBuffer:GLuint = 0
        glGenRenderbuffers(1, &depthRenderBuffer)
        glBindRenderbuffer(GLenum(GL_RENDERBUFFER), depthRenderBuffer)
        glRenderbufferStorage(GLenum(GL_RENDERBUFFER), GLenum(GL_DEPTH_COMPONENT16), width, height)
        myDepthRenderBuffer = depthRenderBuffer
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容