OpenGL ES入门第一篇-OpenGL ES初探

OpenGL ES 简介

      OpenGL ES (OpenGL for Embedded Systems) 是以手持和嵌入式为目标的⾼级3D图形应 ⽤程序编程接口(API). OpenGL ES 是⽬前智能手机中占据统治地位的图形API.⽀持的平台: iOS, Andriod , BlackBerry ,bada ,Linux ,Windows.
git

     另外,做为一名iOS开发者咱们固然要看看Apple 官方文档关于OpenGL ES 的解释。编程

The Open Graphics Library (OpenGL) is used for visualizing 2D and 3D data. It is a multipurpose open- standard graphics library that supports applications for 2D and 3D digital content creation, mechanical and architectural design, virtual prototyping, flight simulation, video games, and more. You use OpenGL to configure a 3D graphics pipeline and submit data to it. Vertices are transformed and lit, assembled into primitives, and rasterized to create a 2D image. OpenGL is designed to translate function calls into graphics commands that can be sent to underlying graphics hardware. Because this underlying hardware is dedicated to processing graphics commands, OpenGL drawing is typically very fast. OpenGL for Embedded Systems (OpenGL ES) is a simplified version of OpenGL that eliminates redundant functionality to provide a library that is both easier to learn and easier to implement in mobile graphics hardware
数组

     开放式图形库(OpenGL)⽤于可视化的⼆维和三维数据。它是一个多功能开放式标准图形库,⽀持2D和3D数字内容建立,机械和建筑设计,虚拟原型设计,⻜行模拟,视频游戏等应⽤用程序。您可使用OpenGL配置3D图形管道并向其提交数据。顶点被变换和点亮,组合成图元,并光栅化以建立2D图像。OpenGL旨在将函数调⽤转换为能够发送到底层图形硬件的图形命令。因为此底层硬件专用于处理图形命令,所以OpenGL绘图一般⾮常快。缓存

     OpenGL for Embedded Systems(OpenGL ES)是OpenGL的简化版本,它消除了冗余功能,提供了⼀个既易于学习⼜更易于在移动图形硬件中实现的库。
bash

     因为OpenGL ES 是OpenGL 的简化版,所以在学习OpenGL ES前有必要去先了解一下基本的OpenGL 的知识,所以推荐你们先去看看前面关于Open GL 的文章服务器

OpenGL ES 渲染流程解析


      如上图所示,整个渲染流程应该是app

  1. 经过客户端将图元数据(顶点坐标,纹理坐标,变换矩阵等等)和图片数据(纹理)经过attribute 或者 uniform等通道传入到顶点着色器
  2. 顶点着色器利用传入的顶点坐标和变换矩阵作各类变换,生成最终的顶点位置,若是有光照还能够经过光照计算公式生成逐顶点光照颜色
  3. 须要注意的是顶点着色器以后并非立刻就进入了片元着色器,而是先通过了图元装配;在这个阶段会执行裁剪、透视分割和 Viewport变换等操做。而后再通过光栅化才到片元着色器
  4.  光栅化就是将图元转化成一组二维片断的过程.而这些转化的片断将由片元着⾊器处理.这些⼆维片断就是屏幕上可绘制的像素. 而片元着色器的任务就是给这些像素填充颜色,能够是经过attribute通道传入的颜色,也能够是经过纹理图片提取的纹素(某个纹理坐标对应的纹理颜色);还能够是本身计算的颜色(好比混合等等)
  5. 最后到达帧缓冲区,进行透明度、模板、深度测试、混合等等。注意这里的混合和片元着色器里的混合有点不同,是指将新生成的⽚段颜⾊与保存在帧缓存区相应位置的颜色值组合起来.


EGL与EAGL

       OpenGL ES 命令须要渲染上下文和绘图表面才能完成图形图像的绘制。其中渲染上下文用来存储相关OpenGL ES的状态;绘制表面则是用来绘制图元的表面,它指定渲染所须要的缓存区类型,好比颜色缓冲区深度缓冲区和模板缓冲区。框架

       然而OpenGL ES 并无提供如何建立渲染上下文或者上下文如何链接到原生窗口系统的API。而EGL是Khronos渲染API【如OpenGL ES】和原生窗口系统之间的接口。由于每一个窗⼝系统都有不一样的定义,因此EGL提供基本的不透明类型—EGLDisplay, 这个类型封装了全部系统相关性,用于和原生窗口系统的接口.可是咱们iOS平台是惟一支持OpenGL ES 缺不支持EGL的平台。Apple提供了本身的EGL API实现,称为EAGL。异步

       因为OpenGL ES 是基于C的API,所以很是方便且受到普遍支持。做为C API,它与Objective-C Cocoa Touch 应用程序无缝集成。可是,OpenGL ES 规范并无定义窗口层;相反,托管操做系统必须提供函数来建立一个接受命令的OpenGL ES 渲染上下文和一个帧缓冲区,其中写入任何绘图命令的结果。所以在iOS上使⽤OpenGL ES须要使用iOS类来设置和呈现绘图表面,并使⽤平台中相应的API来呈现其内容。ide

EAGLContext 

     EAGLContext是苹果本身为Open GL 提供的渲染上下文,该对象管理着利用OpenGL 渲染图形所须要的状态信息、命令、资源等等。在IOS应用程序中,每一个线程都会维护一个当前上下文。当应用程序调用Opengl ES的API时,线程的上下文就会被那个API改变(改变其管理的状态、命令、资源等等)。

      要设置当前上下文,能够经过调用EAGLContext类的setCurrentContext:方法来实现,固然也能够经过EAGLContext类的currentContext方法来获取一个线程的当前上下文。另外,在建立和初始化EAGLContext对象时,能够选择使用哪一个版本的Opengl ES   API。建立Opengl ES 3.0上下文时,以下初始化:

EAGLContext* myContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
复制代码

      若是设备不支持您锁设置的OpenGL ES API,那么 initWithAPI:方法将返回nil。为了支持OpenGL ES API的多个版本,应该首先尝试初始化为最新版本的渲染上下文。若是返回的对象为nil,则初始化旧版本的上下文。如:

EAGLContext* CreateBestEAGLContext()
{
   EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
   if (context == nil) {
      context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
   }
   return context;
}
复制代码

     上下文的API属性指出了上下文支持的OpenGL ES版本。咱们应该首先测试上下文的API属性而后使用它来选择正确的渲染路径。实现此行为的常见模式是为每一个渲染路径建立一个类。应用程序在初始化时测试上下文并建立一次渲染器。

EAGLSharegroup

      尽管上文说到EAGLContext管理着 OpenGL 的状态,但其实OpenGL的状态并非直接由EAGLContext来管理的,而是EAGLContext 借助EAGLContext来管理的。换句话说,OpenGL ES的状态由EAGLSharegroup对象来建立和维护;每一个EAGLContext都包含一个EAGLSharegroup对象,它将OpenGL的状态维护委托给EAGLSharegroup。3者的关系以下图:


       你们都知道在移动端资源是很是匮乏的,在多个上下文中建立一样内容的备份是很是奢侈的操做;若是可以共享通用的资源则能够更好的利用设备的图形资源。这个时候EAGLSharegroup将变得很是有用,当多个上下文关联到同一个EAGLSharegroup时,被任意一个上下文建立的Opengl ES对象在全部的上下文中都是可用的。注意:共享同一个共享组的全部上下文,都必须使用同一个版本的Opengl ES API来初始化上下文

       当共享组是被多个上下文共享时,咱们就有义务要管理Opengl ES对象状态的改变。规则以下:

  1. 当应用程序可能要经过多个上下文进入某个对象的同时,要确保对象没有被同时改变。
  2. 当对象要被发给上下文的命令改变时,对象此时不能被另外的上下文读取或者改变。
  3. 在一个对象被改变时,必须是被绑定对象全部的上下文才能看到这些改变。若是一个上下文在绑定以前就引用它,那么这个对象的内容是没有被定义的

 GLKit框架概述  

       GLKit 框架的设计目标是为了简化基于OpenGL / OpenGL ES的应用开发。它的出现加快了OpenGL / OpenGL ES 应用的开发。GLKit使用数学库,背景纹理加载,预先建立的着色器效果,以及标准视图和视图控制器来实现渲染循环。

     GLKit框架提供了功能和类,能够减小建立新的基于着⾊器的应⽤程序所需的⼯做量,或者⽀持依赖早期版本的OpenGL ES或OpenGL提供的固定管线功能来处理应用程序。

      GLKView 提供绘制场所(View);GLKViewController(扩展于标准的UIKit. ⽤于绘制视图内容的管理与呈现.) 

     另外须要注意的是虽然苹果如今已经弃⽤了OpenGL ES(底层渲染从OpenGL ES改成metal),但因为OpenGL ES 是基于C的API,做为C API,它与Objective-C Cocoa Touch 应用程序无缝集成,所以iOS开发者能够继续使⽤,所以在使用GLKit相关API时虽然会有相应警告可是并不影响渲染结果。 

GLKit渲染图形的基本流程

     使用GLKit渲染图形时首先须要建立一个GLKView做为绘制表面,同时设置其与OpenGL ES 相关基本参数,好比配置渲染缓存区的格式等等;接着咱们须要按照需求调用GLKit相关API设置相关图形参数(好比颜色、纹理、顶点坐标等等)来完成相关渲染,最后将渲染结果呈如今手机屏幕上。文字描述可能有点抽象,具体以下图所示:

                     

        上图中能够看到一个frameBuffer频繁出现,那么什么是frameBuffer(帧缓冲区)呢,其实帧缓冲区是由像素组成的二维数组,每个存储单元对应屏幕上的一个像素,整个帧缓冲对应一帧图像即当前屏幕画面。帧缓冲一般包括:颜色缓冲,深度缓冲,模板缓冲和累积缓冲。这些缓冲区多是在一块内存区域,也可能单独分开,看硬件。而像素数据在进入帧缓冲以前(称为片元)必须经过一系列测试才能写入帧缓冲,若是片元在其中某个测试没有经过,后面的测试或操做都将再也不进行。这些测试或操做流程是:开始(片元)-裁剪测试-alpha测试-模板测试-深度测试-混合-抖动-逻辑操做-结束(写入帧缓冲),这一系列操做都是针对片元着色器的输出(片元的),因此又称之为逐片元操做。

     帧缓冲区能够简单的理解为存储绘制结果的地方;了解了什么是帧缓冲区后,那么理解上面的图就不难了, 首先须要设置一下帧缓冲区的格式,包括颜色缓冲、深度缓冲等等的格式,不一样的格式绘制出的图形视觉效果可能不同;其次就能够用代码来具体实现绘制操做了,绘制的结果会暂时存储在帧缓冲区;最后等一幅图像绘制完毕后就能够交由设备屏幕呈现了。

GLKit相关API介绍

      这里咱们只介绍一些常见的类和API,基本能知足你们90%以上的开发任务了,对于个别比较冷门用处比较小的API等你们有相关需求的时候再去翻看相关API吧

  1. GLKTextureInfo  OpenGL纹理信息的抽象,里面封装了纹理相关的各类信息

            name : OpenGL上下⽂中到的纹理名称;
            target :纹理绑定的目标(代表纹理是几维的);
            height : 纹理的高度
            width : 纹理的高度
            textureOrigin: 纹理原点的位置
            alphaState : 纹理中alpha分量状态
            containsMipmaps: 布尔值,纹理是否包含mip贴图  

  2. GLKTextureLoader 纹理加载的工具类,可以简化从各类资源文件中加载纹理 

           - initWithSharegroup: 初始化方法,这里须要注意sharegroup这个参数是一个                    EAGLSharegroup类型的参数,sharegroup 对象管理与一个或多个EAGLContext对象关联的      OpenGL ES资源,若不指定或值为NULL则建立新的对象,当资源须要被共享时再使用它。

           + textureWithContentsOfFile:options:errer: 从文件中加载2D纹理图像数据,并利用      数据生成新的纹理 

           - textureWithContentsOfFile:options:queue:completionHandler: 从文件中异步
    加载2D纹理图像数据,并利用这些数据建立新纹理对象 

          - textureWithContentsOfURL:options:error: 从URL中加载2D纹理图像数据,并利            用数据创 建新的纹理对象 
          - textureWithContentsOfURL:options:queue:completionHandler:  从URL中异步加        载2D纹理图像数据,并利用数据建立新的纹理对象 
         + textureWithContentsOfData:options:errer: 从内存中加载2D纹理图像数据,并根
    据数据建立新纹理 
        - textureWithContentsOfData:options:queue:completionHandler:  从内存中异步加        载2D纹理图像数据,并根 据数据建立新纹理 
       - textureWithCGImage:options:error: 从Quartz图像加载2D纹理图像数据并利用数据创
    建新纹理对象
       - textureWithCGImage:options:queue:completionHandler:  从Quartz图像异步加载2D      纹理图像数据并利用数据创 建新纹理对象
       + cabeMapWithContentsOfURL:options:errer:  从单个URL加载立方体贴图纹理
    图像数据,并根据数据建立新纹理
      - cabeMapWithContentsOfURL:options:queue:completionHandler:  从单个URL异步        加载立方体贴图纹理 图像数据,并根据数据建立新纹理
      + cubeMapWithContentsOfFile:options:errer: 从单个文件加载立方体贴图纹理
    图像数据,并从数据中建立新纹理 

      - cubeMapWithContentsOfFile:options:queue:completionHandler: 从单个文件异步        加载立方体贴图纹理图像数据,并从数据中建立新纹理

      + cubeMapWithContentsOfFiles:options:errer: 从一系列⽂件中加载⽴方体贴图 纹理图      像数据,并从数据建立新纹理  

      - cubeMapWithContentsOfFiles:options:options:queue:completionHandler:从一          系列⽂件中异步加载⽴方体贴图纹理图像数据,并从数据中建立新纹理 

三、GLKView 使用OpenGL ES 绘制内容的视图默认实现 

      - initWithFrame:context: 初始化新视图 
      delegate 视图代理
      drawableColorFormat 颜⾊渲染缓冲区格式
      drawableDepthFormat 深度渲染缓冲区格式 
      drawableStencilFormat 模板渲染缓冲区的格式
      drawableMultisample 多重采样缓冲区的格式 
      drawableHeight 底层渲染缓冲区对象的高度(以像素为单位)
      drawableWidth 底层渲染缓冲区对象的宽度(以像素为单位) 
      context 绘制视图内容时使⽤的OpenGL ES上下文
      - bindDrawable 将底层FrameBuffer 对象绑定到OpenGL ES
      enableSetNeedsDisplay 布尔值,控制setNeedsDisplay是否有效
      - display ⽴即重绘视图内容 

      snapshot 绘制视图内容并将其做为新图像对象(UIImage *)返回,这个方法永远不要在               draw 方法里调用,不然会递归死循环

     - deleteDrawable 删除与视图关联的可绘制对象  

四、GLKViewDelegate  GLKView对象的回调接口 

     - glkView:drawInRect: 绘制视图内容 (必须实现代理方法) 

五、GLKViewController 管理OpenGL ES渲染循环的视图控制器

      paused 布尔值,渲染循环是否已暂停
     pausedOnWillResignActive 布尔值,当前程序即将推出活动状态时视图控制器是 否自动暂    停渲染循环
     resumeOnDidBecomeActive 布尔值,当前程序变为活动状态时视图控制是否自动 恢复渲染循环  
     framesDisplayed 视图控制器自建立以来发送的帧更新数(即绘制了多少帧)
    timeSinceFirstResume 自视图控制器第⼀次恢复发送更新事件以来通过的时间量
    timeSinceLastResume ⾃上次视图控制器恢复发送更新事件以来通过的时间量
    timeSinceLastUpdate 自上次视图控制器调用委托⽅法( glkViewControllerUpdate: )后通过的时间量
     timeSinceLastDraw 自上次视图控制器调用视图display方法以来通过的时间量.
    preferredFramesPerSecond 视图控制器调用视图以及更新视图内容的速率(理想状况下的每秒的更新帧数)  

    framesPerSencond 视图控制器调用视图以及更新其内容的实际速率(这是个只读属性,由于实际帧率不会彻底由开发者经过preferredFramesPerSecond设置的值来决定,还受到屏幕刷新率等等的影响,所以framesPerSencond只能在不超过屏幕刷新率的基础上尽可能接近preferredFramesPerSecond) 。

六、GLKViewControllerDelegate 渲染循环回调⽅法

   - glkViewControllerUpdate: 在显示每一个帧以前调用 
  - glkViewController:willPause: 在渲染循环暂停或恢复以前调用. 

七、GLKBaseEffect  一种简单光照/着⾊系统,⽤于基于着⾊器的OpenGL渲染 

   label 给Effect(效果)命名
   transform 绑定效果时应⽤于顶点数据的模型视图,投影和纹理变换  
   lightingType ⽤于计算每一个⽚段的光照策略,值为GLKLightingType枚举( GLKLightingTypePerVertex 表示在三⻆形中每一个顶点执行光照计算,而后在三角形⾏插值; GLKLightingTypePerPixel 表示光照计算的输入在三⻆形内插入,而且在每一个⽚段执行光照计算 ) 简单点说就是这两个枚举值一个表示的是在顶点着色器计算光照,一个在片断着色器计算光照。
    lightModelTwoSided 布尔值,表示为图元的两侧计算光照
    material 计算光照时使用的材质属性
    lightModelAmbientColor 环境颜色,应⽤在所渲染的全部图元. 
    l ight0 场景中第⼀个光照属性 
   light1 场景中第二个光照属性 
    light2 场景中第三个光照属性 
   texture2d0 第一个纹理属性 
   texture2d1 第⼆个纹理属性 

   textureOrder 纹理应用于渲染图元的顺序
   colorMaterialEnable 布尔值,表示计算光照与材质交互时是否使用顶点颜色属性 
   useConstantColor 布尔值,指示是否使用常量颜⾊
   constantColor 不提供每一个顶点颜色数据时使用的常量颜⾊
   - prepareToDraw 准备渲染效果 

案例-利用GLKit加载一张图片

      在OpenGL 中图片其实就是纹理  所以加载图片其实就是加载一张纹理,下面咱们就用上文讲到的GLKit来加载纹理并绘制到屏幕上。

     由于OpenGL ES 命令须要渲染上下文和绘图表面才能完成图形图像的绘制,所以利用GLKit绘制图形第一步就是配置好EAGLContext 和 GLKView。具体代码以下:

- (EAGLContext *)bestCreateEAGLContext
{
    EAGLContext *temContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if(!temContext) {
        temContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    }    
    return temContext;
}
- (void)setupConfig 
{
    context = [self bestCreateEAGLContext];
    [EAGLContext setCurrentContext:context];
    GLKView *temView = [[GLKView alloc] initWithFrame:self.view.bounds context:context];
    temView.delegate = self;
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    [self.view addSubview:temView];
}复制代码

     在这里准备好了渲染上下文和绘制表面,并设置了清屏颜色。在复杂案例中若是须要设置缓存区的格式,也能够在这里利用GLKView直接配置,好比加上:

temView.drawableDepthFormat = GLKViewDrawableDepthFormat16;
temView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;复制代码

     固然在本例中这些配置都是没有必要的,由于咱们仅仅只是想加载一张图片,不涉及深度相关的配置,而颜色缓存区格式默认就是GLKViewDrawableColorFormatRGBA8888 因此也能够不写。

     设置好了渲染上下文、渲染表面等基本条件后,第二步就能够设置渲染须要的参数了,好比顶点坐标,纹理坐标等等,具体代码以下:

- (void)setupVertexData
{    
    GLfloat vertexData[] = {
        0.5f,-0.5f,0.0f,  1.0f, 0.0f,
        0.5f,0.5f,0.0f,   1.0f, 1.0f,
        -0.5f,0.5f,0.0f,  0.0f, 1.0f, 
        -0.5f,0.5f,0.0f,  0.0f, 1.0f,
        -0.5f,-0.5f,0.0f, 0.0f,0.0f,
         0.5f,-0.5f,0.0f, 1.0f, 0.0f,
    };
    GLuint bufferId;
    glGenBuffers(1, &bufferId);
    glBindBuffer(GL_ARRAY_BUFFER, bufferId);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GL_FLOAT) * 5, (GLfloat *)NULL + 3);
}复制代码

      这里顶点坐标和纹理坐标的设置请参考前一个专题OpenGL相关的文章,须要注意的是vertexData数组是存储在内存中的,因此首先要将内存数据拷贝到显存,而在显存中存储这些数据须要先生成相应的缓冲区;所以咱们调用了glGenBuffers和glBindBuffer。那么,这两个函数有什么做用呢?

     void glGenBuffers(GLsizei n,GLuint * buffers);官方的解释为generate buffer object names;意思是生成缓冲区对象的名字,没错仅仅只是个名字,这个函数接收2个参数,第一个表示生成缓冲区名字的个数;第二个参数是用来存储缓冲对象名称的数组。

      我的理解为(若是不对请大神指点)glGenBuffers()函数仅仅是生成一个缓冲对象的名称,这个缓冲对象并不具有任何意义,它仅仅是个缓冲对象,还不是一个顶点数组缓冲,它相似于C语言中的一个指针变量,咱们能够分配内存对象而且用它的名称来引用这个内存对象。OpenGL有不少缓冲对象类型,那么这个缓冲对象究竟是什么类型,就要用到下面的glBindBuffer()函数了。

     void glBindBuffer(GLenum target,GLuint buffer);官方解释:bind a named buffer object。其第一个参数就是缓冲对象的类型,第二个参数为要绑定的缓冲对象的名称,也就是咱们在上一个函数里生成的名称,使用该函数将缓冲对象绑定到OpenGL上下文环境中以便使用。若是把target绑定到一个已经建立好的缓冲对象,那么这个缓冲对象将为当前target的激活对象;可是若是绑定的buffer值为0,那么OpenGL将再也不对当前target使用任何缓存对象。 在OpenGL红宝书中有这样一个比喻:绑定对象的过程就像设置铁路的道岔开关,每个缓冲类型中的各个对象就像不一样的轨道同样,咱们将开关设置为其中一个状态,那么以后的列车都会驶入这条轨道。 切记:官方文档指出,GL_INVALID_VALUE is generated if buffer is not a name previously returned from a call to glGenBuffers。换句话说,这个名称虽然是GLuint类型的,可是你万万不能直接指定个常量好比说0, 若是你这样作,就会出现GL_INVALID_VALUE的错误。

      绑定缓冲类型后,全部该缓冲类型的函数调用都是用来配置该目标缓冲类型,好比咱们这里的顶点缓冲类型GL_ARRAY_BUFFER,glBufferData是经过指定目标缓冲类型来进行数据传输的,而每个目标缓冲类型再使用前要提早绑定一个缓冲对象,从而赋予这个缓冲对象一个类型的意义,须要注意的是OpenGL容许咱们同时绑定多个缓冲类型,只要这些缓冲类型是不一样的,换句话说,同一时间,不能绑定两个相同类型的缓冲对象也能够理解为对于一个类型来讲,同一时间只能激活一个类型,不然就会发生错误。

      好比绑定了两个相同类型的目标缓冲,数据的配置确定就会出错。你能够想象一下,假如咱们要把数据存入顶点缓冲区,可是顶点缓冲区能够有不少缓冲对象,我须要传入哪一个呢,因而我就要提早绑定一个,以后,我只要向顶点缓冲区内传入数据,这个数据就会自动进入被绑定的那个对象里面了,在本例中就是将vertexData数据传入名字为bufferId的缓冲对象中,完成将内存数据拷贝到显存的操做。

       前面已经说过glBufferData的做用将数据从内存拷贝到显存,其函数原型为:void glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);第一个参数表示缓冲区类型;第二个参数表示数据字节大小;第三个参数表示须要拷贝的数据;第4个参数表示静态拷贝仍是动态拷贝,这里的静态动态之分后面其余的文章会讲到。

       另外,在iOS中默认状况下出于性能考虑,全部顶点着色器的属性(Attribute)变量都是关闭的.这意味着顶点数据在着色器端(服务端)是不可用的. 即便你已经使用glBufferData方法,将顶点数据从内存拷贝到顶点缓存区中(GPU显存中). 因此, 必须由glEnableVertexAttribArray 方法打开通道.指定访问属性.才能让顶点着色器可以访问到从CPU复制到GPU的数据. 注意: 数据在GPU端是否可见,即着色器可否读取到数据是由是否启用了对应的属性决定,这就是glEnableVertexAttribArray的功能,容许顶点着色器读取GPU(服务器端)数据。打开属性通道之后,数据在GPU端可见,可是对于这些数据怎么读取呢,怎么区分哪些是顶点坐标?哪些是纹理坐标呢?这就要说到glVertexAttribPointer函数了。

      先来看一下函数原型:glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)。该函数的功能为明确数据的读取方式。参数index指定要修改的顶点属性的索引值;参数size表示每次读取数量;参数type,指定数组中每一个组件的数据类型;参数normalized表示是否须要归一化操做;参数stride,指定连续顶点属性之间的偏移量,若是为0,那么顶点属性会被理解为它们是紧密排列在一块儿的,好比本例中两个顶点直接的偏移量是5个浮点数因此为sizeof(float) *5;参数ptr指定一个指针,指向数组中第一个顶点属性的第一个组件,好比本例中顶点坐标的第一个顶点属性的第一个组件就是第一个浮点数是因此是(GLfloat *)NULL + 0,又好比本例中纹理坐标的第一个顶点属性的第一个组件就是第4个浮点数是因此是(GLfloat *)NULL + 3。总之利用该函数能够指定出每次读几个数,2个数之间偏移多少 和第一个数的起始位置,这样天然就能肯定清楚数据的读取方式。

      好了,上面是参数的配置是本篇的重点因此啰嗦了一点,配置完这些参数后咱们就能够进入主题开始第三步加载纹理了。直接上代码:

-(void)setUpTexture

{    //1.获取纹理图片路径
    NSString *filePath = [[NSBundle mainBundle]pathForResource:@"bui" ofType:@"jpg"];
    //2.设置纹理参数    //纹理坐标原点是左下角,可是图片显示原点应该是左上角. 
     NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
    GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
    //3.使用苹果GLKit 提供GLKBaseEffect 完成着色器工做(顶点/片元)
    cEffect = [[GLKBaseEffect alloc]init];
    cEffect.texture2d0.enabled = GL_TRUE;
    cEffect.texture2d0.name = textureInfo.name;
}复制代码

     这一部分的代码比较简单 你们直接看上文代码 和注释就好了,到了这里咱们基本的准备工做都作完了能够开始咱们最后的工做绘制了。实现GLKViewDelegate的代理方法-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect,来完成最后的绘制工做,代码以下:

-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect 
{
    glClear(GL_COLOR_BUFFER_BIT);
    [mEffect prepareToDraw];
    glDrawArrays(GL_TRIANGLES, 0, 6);
}复制代码


相关文章
相关标签/搜索