一般咱们学习一种新的语言,创立的工程都是从Hello world开端的。不过OpenGL ES一般是从制作一个三角形开端的。Demo
屏幕图画的显现原理

    1. 首要画三角形,假如是在一张白纸上画。那画画的过程是这样的:

1.确定3个极点(不在同一直线上) 2.把它们用线段衔接起来 3.涂色(要是有色彩的话)

    1. 那OpenGL 制作三角形的过程又是怎样的呢? 咱们先简略的来了解一下 OpenGL 的作业流程

iOS视觉-- (02) OpenGL ES之初(从画一个三角形到一张图片)

iOS视觉-- (02) OpenGL ES之初(从画一个三角形到一张图片)

那么大致过程和白纸绘图是一样的便是:

1.极点数据(确定3个极点) 2.传输给极点着色器,然后进行图元装备(相当于:把它们用线段衔接起来) 3.然后进行光栅化和插值(涂色)

光栅化便是将图元转化成一个二维片段的过程,而这些转化的片段将由片元着色器处理,这些二维片段便是屏幕上可制作的像素。 OpenGL 能够制作三种根本元素:点、线、 三角形 三角形是计算机图形学中常用的根本形状图元,(个人了解:能够说计算机制作的一切多边形和柱体,都是由三角形组成的)




  • 3.代码完成 这儿先用GLKit完成,因为GLKit苹果封装了极点着色器和片元着色器,对于刚入门的咱们了解比较简略。
  • 1.设置图层
    //1.设置图层
    func setupLayer() {
        //创立一个OpenGL ES上下文并将其分配给从storyboard加载的视图
        //注意⚠️:这儿需求把storyboard的View记得添加为GLKView, 还有把ViewController承继自: GLKViewController
        glkView = self.view as? GLKView
        //装备视图创立的烘托缓冲区
        /*
         OpenGL ES 有一个缓存区,它用以存储将在屏幕中显现的色彩。你能够运用其特点来设置缓冲区中的每个
         像素的色彩格局。
         默许:GLKViewDrawableColorFormatRGBA8888,即缓存区的每个像素的最小组成部分(RGBA)运用
         8个bit,(所以每个像素4个字节,4*8个bit)。
         GLKViewDrawableColorFormatRGB565,假如你的APP允许更小规模的色彩,即可设置这个。会让你的
         APP消耗更小的资源(内存和处理时刻)
         */
        glkView.drawableColorFormat = .RGBA8888
        /*
         OpenGL ES 另一个缓存区,深度缓冲区。协助咱们保证能够更挨近观察者的方针显现在远一些的方针前面。
         (离观察者近一些的方针会挡住在它后边的方针)
         默许:OpenGL把挨近观察者的方针的一切像素存储到深度缓冲区,当开端制作一个像素时,它(OpenGL)
         首要查看深度缓冲区,看是否现已制作了更挨近观察者的什么东西,假如是则疏忽它(要制作的像素,
         便是说,在制作一个像素之前,看看前面有没有挡着它的东西,假如有那就不用制作了)。否则,
         把它增加到深度缓冲区和色彩缓冲区。
         缺省值是GLKViewDrawableDepthFormatNone,意味着彻底没有深度缓冲区。
         可是假如你要运用这个特点(一般用于3D游戏),你应该挑选GLKViewDrawableDepthFormat16
         或GLKViewDrawableDepthFormat24。这儿的差别是运用GLKViewDrawableDepthFormat16
         将消耗更少的资源,可是当方针非常挨近彼此刻,你或许存在烘托问题()
         */
        glkView.drawableDepthFormat = GLKViewDrawableDepthFormat.format24
        /*
         你的OpenGL上下文的另一个可选的缓冲区是stencil(模板)缓冲区。它协助你把制作区
         域限定到屏幕的一个特定部分。它还用于像影子一类的事物=比方你能够运用stencil缓冲
         区保证影子投射到地板。缺省值是GLKViewDrawableStencilFormatNone,
         意思是没有stencil缓冲区,可是你能够经过设置其值为GLKViewDrawableStencilFormat8
         (仅有的其他选项)使能它
         */
        // view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
        //启用多重采样
        /*
         这是你能够设置的最终一个可选缓冲区,对应的GLKView特点是multisampling。
         假如你曾经尝试过运用OpenGL画线并关注过"锯齿壮线",multisampling就能够协助你处理
         以前对于每个像素,都会调用一次fragment shader(片段着色器),
         drawableMultisample根本上代替了这个作业,它将一个像素分红更小的单元,
         并在更细微的层面上屡次调用fragment shader。之后它将回来的色彩合并,
         生成更润滑的几许边缘效果。
         要小心此操作,因为它需求占用你的app的更多的处理时刻和内存。
         缺省值是GLKViewDrawableMultisampleNone,可是你能够经过设置其值GLKViewDrawableMultisample4X为来使能它
         */
        //view.drawableMultisample = GLKViewDrawableMultisample4X;
        EAGLContext.setCurrent(context)
        glEnable(GLenum(GL_DEPTH_TEST)) //敞开深度测验,便是让离你近的物体能够遮挡离你远的物体。
        glClearColor(1, 0, 0, 1.0)//设置surface的铲除色彩,也便是烘托到屏幕上的背景色。
    }
  • 2.设置上下文
    //2.设置上下文
    func setupContext() {
        //EAGLContext 是苹果在iOS 平台下完成的OpenGLES烘托层,用于烘托成果在方针surface上的更新。
        //1.新建上下文
        context = EAGLContext(api: EAGLRenderingAPI.openGLES2)
        if (context == nil) {
            NSLog("Failed to load context")
        }
        EAGLContext.setCurrent(context)
        //敞开深度测验,便是让离你近的物体能够遮挡离你远的物体。
        glEnable(GLenum(GL_DEPTH_TEST))
        //给glkView上下文赋值
        glkView.context = context
    }
  • 3.设置极点数据(方位,色彩,纹路(烘托图片时用到))
//3.设置极点数据(方位,色彩,纹路(烘托图片时用到))
    func setupVertexData() {
        //第一步:设置极点数组
        //OpenGL ES的国际坐标系是[-1, 1],故而点(0, 0)是在屏幕的正中间。
        //极点数据,3个是极点坐标x,y,z;
        let vertexArray: [GLfloat] = [            0.5, -0.5, 0.0,  //右下            -0.5, 0.5, 0.0,  //左上            -0.5, -0.5, 0.0 //左下        ]
        //第二步:敞开极点缓冲区
        //极点缓存区
        var buffer: GLuint = GLuint()
        //请求一个缓存区标识符
        glGenBuffers(1, &buffer)
        //glBindBuffer把标识符绑定到GL_ARRAY_BUFFER上
        glBindBuffer(GLenum(GL_ARRAY_BUFFER), buffer)
        //glBufferData把极点数据从cpu内存仿制到gpu内存
        glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * vertexArray.count, vertexArray, GLenum(GL_STATIC_DRAW))
        //第三步:设置合适的格局从buffer里边读取数据)
        /*
         默许情况下,出于性能考虑,一切极点着色器的特点(Attribute)变量都是关闭的,意味着数据在着色器端是不可见的,哪怕数据现已上传到GPU,由glEnableVertexAttribArray启用指定特点,才可在极点着色器中拜访逐极点的特点数据。glVertexAttribPointer或VBO仅仅建立CPU和GPU之间的逻辑衔接,从而完成了CPU数据上传至GPU。可是,数据在GPU端是否可见,即,着色器能否读取到数据,由是否启用了对应的特点决定,这便是glEnableVertexAttribArray的功能,允许极点着色器读取GPU(服务器端)数据。
         */
        glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
        //glVertexAttribPointer 运用来上传极点数据到GPU的办法(设置合适的格局从buffer里边读取数据)
        // index: 指定要修改的极点特点的索引值
        // size : 指定每个极点特点的组件数量。必须为1、2、3或许4。初始值为4。(如position是由3个(x,y,z)组成,而色彩是4个(r,g,b,a))
        // type : 指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。
        // normalized : 指定当被拜访时,固定点数据值是否应该被归一化(GL_TRUE)或许直接转换为固定点值(GL_FALSE)
        // stride : 指定接连极点特点之间的偏移量。假如为0,那么极点特点会被了解为:它们是严密摆放在一起的。初始值为0
        // ptr    : 指定一个指针,指向数组中第一个极点特点的第一个组件。初始值为0 这个值受到VBO的影响
        /*
         VBO,极点缓存方针
         在不运用VBO的情况下:作业是这样的,ptr便是一个指针,指向的是需求上传到极点数据指针。通常是数组名的偏移量。
         在运用VBO的情况下:首要要glBindBuffer,今后ptr指向的就不是具体的数据了。因为数据现已缓存在缓冲区了。这儿的ptr指向的是缓冲区数据的偏移量。这儿的偏移量是整型,可是需求强制转换为const GLvoid *类型传入。注意的是,这儿的偏移的意思是数据个数总宽度数值。
         比方说:这儿寄存的数据前面有3个float类型数据,那么这儿的偏移便是,3*sizeof(float).
         最终解释一下,glVertexAttribPointer的作业原理:
         首要,经过index得到着色器对应的变量openGL会把数据仿制给着色器的变量。
         今后,经过size和type知道当时数据什么类型,有几个。openGL会映射到float,vec2, vec3 等等。
         因为每次上传的极点数据不止一个,或许是一次4,5,6极点数据。那么经过stride便是在数组中间隔多少byte字节拿到下个极点此类型数据。
         最终,经过ptr的指针在迭代中获得一切数据。
         那么,最最终openGL怎样知道ptr指向的数组有多长,读取几回呢。是的,openGL不知道。所以在调用制作的时分,需求传入一个count数值,便是告知openGL制作的时分迭代几回glVertexAttribPointer调用。
         */
        //(GLfloat *)NULL + 0 指针,指向数组首地址
        glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 3), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 0))
}
  • 4.设置着色器:极点着色器和片元着色器 (苹果GLKit现已将这两个着色器封装了)
//4.设置着色器:极点着色器和片元着色器 (苹果GLKit现已将这两个着色器封装了)
    func setupEffect() {
        //着色器
        myEffect = GLKBaseEffect()
    }
  • 5.制作烘托(GLKViewDelegate 完成)
//5.制作烘托
    override func glkView(_ view: GLKView, drawIn rect: CGRect) {
        glClearColor(0, 0, 1.0, 1.0)
        //铲除surface内容,康复至初始状况
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
        //发动着色器
        myEffect.prepareToDraw()
        /*
         定义:
         void glDrawArrays(  GLenum mode,    GLint first,    GLsizei count);
         参数:
         mode:
             需求烘托的图元类型,包括 GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN ,GL_TRIANGLES。
         first:
             从数组缓存中的哪一位开端制作,一般为0.
         count:
             数组中极点的数量.
         */
        glDrawArrays(GLenum(GL_TRIANGLES), 0, 6)
    }

iOS视觉-- (02) OpenGL ES之初(从画一个三角形到一张图片)

至此,咱们就完成了从极点数据到三角形的制作了。接下来咱们来看看怎样制作一张图片呢?

华丽的分割线


上面咱们说过:OpenGL 能够制作三种根本元素:点、线、 三角形 那么画一张图片的过程是怎样样的呢?

1.画一个四边形 2.加载纹路(图片)

  • 1.画一个四边形 一个四边形由两个三角形组成,画三角形上面咱们现已讲过,所以画两个就行。 那么极点数据就变成这样:
        //2个三角形构成
        let vertexArray: [GLfloat] = [
            0.5, -0.5, 0.0,    //右下
            0.5, 0.5, 0.0,     //右上
            -0.5, 0.5, 0.0,    //左上
            0.5, -0.5, 0.0,    //右下
            -0.5, 0.5, 0.0,    //左上
            -0.5, -0.5, 0.0,   //左下
        ]

还有制作烘托那里的数据也变一下,极点数据变为:6

glDrawArrays(GLenum(GL_TRIANGLES), 0, 6)

运行成果:会得出一个白色的四边形来。 思考题:怎样得出如下的四边形来呢? 提示:仔细的同学,或许会发现(极点数据能够包括:方位,色彩,纹路(烘托图片时用到))

iOS视觉-- (02) OpenGL ES之初(从画一个三角形到一张图片)

那么极点数据中参加色彩值,还有设置一下色彩特点就行了。如下

let vertexArray: [GLfloat] = [            //方位:(x, y, z)色彩:(r, g, b)            0.5, -0.5, 0.0,    1.0, 0.0, 0.0,    //右下            0.5, 0.5, 0.0,     1.0, 0.0, 0.0,    //右上            -0.5, 0.5, 0.0,    1.0, 0.0, 0.0,    //左上                        0.5, -0.5, 0.0,    0.0, 1.0, 0.0,   //右下            -0.5, 0.5, 0.0,    0.0, 1.0, 0.0,   //左上            -0.5, -0.5, 0.0,   0.0, 1.0, 0.0,   //左下        ]
...
//极点方位,极点个数为:3,起点为:0,步长为:6
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 6), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 0))
//极点色彩,极点个数为:3,起点为:3,步长为:6
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.color.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.color.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 6), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 3))

这就完成了极点和极点色彩的烘托了。那么纹路(图片)的烘托道理也是差不多的,接下来让咱们学习怎样烘托纹路吧。 那么极点的数据就多加上纹路坐标一般为(s, t),那么极点数据就变成这样了:

//第一步:设置极点数组
//OpenGL ES的国际坐标系是[-1, 1],故而点(0, 0)是在屏幕的正中间。
//极点数据,前3个是极点坐标x,y,z;后边2个是纹路坐标。
//纹路坐标系的取值规模是[0, 1],原点是在左下角。故而点(0, 0)在左下角,点(1, 1)在右上角
 //2个三角形构成
let vertexArray: [GLfloat] = [    //方位:(x, y, z)  色彩:(r, g, b)  纹路:(s, t)    0.5, -0.5, 0.0,    1.0, 0.0, 0.0,    1.0, 0.0, //右下    0.5, 0.5, 0.0,     1.0, 0.0, 0.0,    1.0, 1.0, //右上    -0.5, 0.5, 0.0,    1.0, 0.0, 0.0,    0.0, 1.0, //左上                0.5, -0.5, 0.0,    0.0, 1.0, 0.0,    1.0, 0.0, //右下    -0.5, 0.5, 0.0,    0.0, 1.0, 0.0,    0.0, 1.0, //左上    -0.5, -0.5, 0.0,   0.0, 1.0, 0.0,    0.0, 0.0  //左下]
 ...
//极点方位,极点个数为:3,起点为:0,步长为:8
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 6), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 0))
//极点色彩,极点个数为:3,起点为:3,步长为:8
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.color.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.color.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 6), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 3))
//纹路,极点个数为:2,起点为:6,步长为:8
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.texCoord0.rawValue), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 8), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 6))       

有了极点数据有了纹路坐标之后,可是从始至终都没有加载过纹路,所以咱们还需求: 加载纹路

  • 加载纹路
    //加载纹路
    func setupTexture() {
        //第一步,获取纹路图片保存途径
        let filePath = Bundle.main.path(forResource: "cTest", ofType: "jpg")
        //GLKTextureLoaderOriginBottomLeft,纹路坐标是相反的
        let options: [String : NSNumber] = [GLKTextureLoaderOriginBottomLeft : 1]
        let textureInfo: GLKTextureInfo = try! GLKTextureLoader.texture(withContentsOfFile: filePath!, options: options)
        //第一个纹路特点
        myEffect.texture2d0.enabled = GLboolean(GL_TRUE)
        //纹路的姓名
        myEffect.texture2d0.name = textureInfo.name
    }

iOS视觉-- (02) OpenGL ES之初(从画一个三角形到一张图片)

图片被压缩了,怎样办呢?咱们能够把图片按照比例缩放至屏幕上。相当于UIImageView的scaleAspectFit。咱们能够经过:

AVMakeRectWithAspectRatioInsideRect(CGSize aspectRatio, CGRect boundingRect) 这个办法有什么用呢? 这个办法便是计算在一个rect里,假如需求保持一个size比例不变,这个size的真实方位。

那么极点数据就变为这样:

 let image = UIImage(named: "cTest.jpg")!
 let realRect = AVMakeRect(aspectRatio: image.size, insideRect: self.view.bounds)
 let widthRatio: Float = Float(realRect.size.width/self.view.bounds.size.width)
 let heightRatio: Float = Float(realRect.size.height/self.view.bounds.size.height)
 let vertexArray: [GLfloat] = [
    //方位:(x, y, z)  色彩:(r, g, b)  纹路:(s, t)
    widthRatio,  -heightRatio, 0.0,    1.0, 0.0, 0.0,    1.0, 0.0, //右下
    widthRatio,  heightRatio, 0.0,     1.0, 0.0, 0.0,    1.0, 1.0, //右上
    -widthRatio, heightRatio, 0.0,    1.0, 0.0, 0.0,    0.0, 1.0, //左上
    widthRatio,  -heightRatio, 0.0,    0.0, 1.0, 0.0,    1.0, 0.0, //右下
    -widthRatio, heightRatio, 0.0,    0.0, 1.0, 0.0,    0.0, 1.0, //左上
    -widthRatio, -heightRatio, 0.0,   0.0, 1.0, 0.0,    0.0, 0.0  //左下
 ]

iOS视觉-- (02) OpenGL ES之初(从画一个三角形到一张图片)

愉快的学习记载到此就完毕了,下一步咱们将学习不运用GLKit,直接用GLSL进行烘托。