OpenGL学习- 9.OpenGL ES简介 & GLKit

481 阅读12分钟

9.OpenGL ES简介 & GLKit

OpenGL ES是以手持和嵌入式为目标的高级3D图形应用程序编程接口。OpenGL ES是目前智能手机中占据统治地位的图形API。
支持的平台:iOS、Android、BlackBerry、bada、Linux、Windows。
OpenGL渲染流程

  1. 确定顶点和颜色数据
  2. 顶点着色器处理:可自定义,功能:进行矩阵变换位置、计算光照公式生成逐顶点颜色、生成或变换纹理坐标。总结来说就是可以用于执行自定义计算,实施新的变换、光照或者固定功能所不允许的基于顶点的效果。
  3. 图元装配:OpenGL自动处理,无法自定义操作,将顶点数据计算成一个个图元(点、线、三角形等)。然后对于每一个图元,将顶点着色器的输出值执行裁剪、透视分割和视口变换操作。
  4. 光栅化:OpenGL自动处理,无法自定义操作,光栅化就是将每个图元转化成一组二维片段的过程,这些二维片段就是屏幕上可绘制的像素片元(包括每个像素的屏幕坐标、纹理坐标、颜色等属性),注意:现在只是确定了数据,并没有进行渲染,渲染是片元着色器的工作
  5. 片元着色器处理:可自定义,功能:计算颜色、获取纹理值、向像素点中填充颜色(颜色值/纹理值)。简单来说就是计算颜色然后给每个像素点填充颜色

EGL (Embedded Graphics Library)

OpenGL ES命令需要渲染上下文(存储相关OpenGL ES状态)和绘制表面(用于绘制图元的表面。它指定渲染所需要的缓存区类型,例如颜色缓存区、深度缓存区和模版缓存区)才能完成图形图像的绘制。但是,OpenGL ES API并没有提供如何创建渲染上下文或者上下文如何连接到原生窗口系统的方法。EGL就是OpenGL ES和原声窗口系统之间的接口。注意:iOS平台并不支持EGL,Apple提供了自己的EGL,称为EAGL

OpenGL ES显示器执行动画的应用程序流程

  1. 创建静态资源(create static resources)
  2. 更新动态资源(update dynamic resources)
  3. 执行渲染命令(execute rendering commands)
  4. 回读结果(read back results)
  5. 显示当前显示(present to display)
  6. 释放资源(free up resources)

OpenGL ES错误处理

如果不正确的使用OpenGL ES命令,应用程序就会产生一个错误编码,这个错误编码将被记录,可以使用glGetError查询。在应用程序用glGetError查询第一个错误代码之前,不会记录其他错误代码。一旦查询到错误代码,当前错误代码便复位为GL_NO_ERROR
GLenum glGetError(void)

错误编码意义
GL_NO_ERROR从上次调用glGetError以来没有发生任何错误
GL_INVALID_ENUMGLenum参数超出范围,忽略生成错误命令
GL_INVALID_VALUE数值型参数超出范围,胡咧生成错误命令
GL_INVALID_OPERATION特定命令在当前OpenGL ES状态无法执行
GL_OUT_OF_MEMORY内存不足时执行该命令,如果遇到这个错误,除非修复当前错误代码,否则OpenGL ES管线的状态被认为为定义

GLKit概述

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

GLKTextureInfo纹理信息

@interface GLKTextureInfo : NSObject <NSCopying>
{
@private
    GLuint                      name;
    GLenum                      target;
    GLuint                      width;
    GLuint                      height;
    GLuint                      depth;
    GLKTextureInfoAlphaState    alphaState;
    GLKTextureInfoOrigin        textureOrigin;
    BOOL                        containsMipmaps;
    GLuint                      mimapLevelCount;
    GLuint                      arrayLength;
}

//OpenGL上下文中纹理名称
@property (readonly) GLuint                     name;
//纹理绑定的目标
@property (readonly) GLenum                     target;
//加载的纹理宽度
@property (readonly) GLuint                     width;
//加载的纹理高度
@property (readonly) GLuint                     height;
//加载的纹理深度
@property (readonly) GLuint                     depth;
//加载的纹理的alpha分量状态
@property (readonly) GLKTextureInfoAlphaState   alphaState;
//加载的纹理的原点状态
@property (readonly) GLKTextureInfoOrigin       textureOrigin;
//加载的纹理是否包含mip贴图
@property (readonly) BOOL                       containsMipmaps;
@property (readonly) GLuint                     mimapLevelCount;
@property (readonly) GLuint                     arrayLength;

@end

GLKTextureLoader纹理加载

GLKTextureLoader提供了从File、bundled、URL、Data、CGImage同步/异步加载纹理的方法

GLKView提供绘制场所,继承于UIView

初始化视图
- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context;
代理
@property (nullable, nonatomic, assign) IBOutlet id <GLKViewDelegate> delegate;

@property (nonatomic, retain) EAGLContext *context;//绘制视图内容时使用的上下文
帧缓存区属性
@property (nonatomic, readonly) NSInteger drawableWidth;//底层缓存区对象的宽度(以像素为单位)
@property (nonatomic, readonly) NSInteger drawableHeight;//底层缓存区对象的高度(以像素为单位)
配置帧缓存区对象
@property (nonatomic) GLKViewDrawableColorFormat drawableColorFormat;//颜色渲染缓存区格式
@property (nonatomic) GLKViewDrawableDepthFormat drawableDepthFormat;//深度渲染缓冲区格式
@property (nonatomic) GLKViewDrawableStencilFormat drawableStencilFormat;//模版渲染缓存区格式
@property (nonatomic) GLKViewDrawableMultisample drawableMultisample;//多重采样缓存区格式
// 将底层FrameBuffer对象绑定到OpenGL ES
- (void)bindDrawable;
//删除视图FrameBuffer对象
- (void)deleteDrawable;
//将绘制视图内容做为新图像返回
@property (readonly, strong) UIImage *snapshot;
//控制视图是否响应setNeedsDisplay
@property (nonatomic) BOOL enableSetNeedsDisplay;
//立即重绘视图内容
- (void)display;

@protocol GLKViewDelegate <NSObject>
@required
绘制视图的内容,必须实现的代理
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;
@end

GLKViewController扩展于标准UIKit,用于绘制视图内容的管理与呈现。

@interface GLKViewController : UIViewController <NSCoding, GLKViewDelegate>
//代理
@property (nullable, nonatomic, assign) IBOutlet id <GLKViewControllerDelegate> delegate;
//用于设置控制器调用视图以及更新视图的速率,默认30帧/s
@property (nonatomic) NSInteger preferredFramesPerSecond;
//视图控制器调用视图以及更新其内容的实际速率
@property (nonatomic, readonly) NSInteger framesPerSecond;
//用于暂停和恢复渲染循环
@property (nonatomic, getter=isPaused) BOOL paused;
//自绘图开始以来显示的帧总数
@property (nonatomic, readonly) NSInteger framesDisplayed;
//自视图控制器第一次恢复发送更新事件以来经过的时间量
@property (nonatomic, readonly) NSTimeInterval timeSinceFirstResume;
//自上次视图控制器恢复发送更新事件以来更新的时间量
@property (nonatomic, readonly) NSTimeInterval timeSinceLastResume;
//自上次视图控制器调用委托方法以及经过的时间量
@property (nonatomic, readonly) NSTimeInterval timeSinceLastUpdate;
//自上次视图控制器调⽤视图display方法以来经过的时间量
@property (nonatomic, readonly) NSTimeInterval timeSinceLastDraw;
//当前程序重新激活活动状态时视图控制器是否自动暂停渲染循环,默认YES
@property (nonatomic) BOOL pauseOnWillResignActive;
//当前程序变为活动状态时视图控制是否自动恢复呈现循环,默认YES
@property (nonatomic) BOOL resumeOnDidBecomeActive;
@end

@protocol GLKViewControllerDelegate <NSObject>
@required
//处理更新事件,在每一帧显示之前调用。GLKViewController有默认实现,子类也可以重写。需要注意的是,如果重写了 - (void)update 方法后,该方法就不会调用了
- (void)glkViewControllerUpdate:(GLKViewController *)controller;


@optional
//暂停/恢复渲染循环的通知,在渲染循环暂停/恢复之前调用
- (void)glkViewController:(GLKViewController *)controller willPause:(BOOL)pause;
@end

GLKBaseEffect用于OpenGL渲染的一种简单光照/着色系统

@interface GLKBaseEffect : NSObject <GLKNamedEffect>
//计算光照与材质交互时是否使用颜色顶点属性
@property (nonatomic, assign) GLboolean colorMaterialEnabled;
// 是否为图元的双面计算光照
@property (nonatomic, assign) GLboolean lightModelTwoSided;
// 是否使用常量颜⾊
@property (nonatomic, assign) GLboolean useConstantColor;
// 模型转换,绑定效果时应用于顶点数据的模型视图,投影和纹理变换
@property (nonatomic, readonly) GLKEffectPropertyTransform *transform;
//场景中的几个光照属性,light0,light1,light2分别为第一、二、三个光照属性,GLKBaseEffect最多支持3个光照
@property (nonatomic, readonly) GLKEffectPropertyLight *light0,*light1,*light2;
// 光照策略
@property (nonatomic, assign) GLKLightingType lightingType;
//环境颜色,应用效果渲染的所有图元
@property (nonatomic, assign) GLKVector4 lightModelAmbientColor;// { 0.2, 0.2, 0.2, 1.0 }
//计算渲染图元光照使用的材质属性
@property (nonatomic, readonly) GLKEffectPropertyMaterial *material;
// 纹理,texture2d0、texture2d1分别为第一、二个纹理属性,GLKBaseEffect最多支持2个纹理
@property (nonatomic, readonly) GLKEffectPropertyTexture *texture2d0,*texture2d1;
// 纹理应用于渲染图元的顺序
@property (nullable, nonatomic, copy) NSArray<GLKEffectPropertyTexture*> *textureOrder;
// 不提供每个顶点颜⾊数据时使⽤的常量颜色{ 1.0, 1.0, 1.0, 1.0 }
@property (nonatomic, assign) GLKVector4 constantColor;
// 应用于场景的雾化属性
@property (nonatomic, readonly) GLKEffectPropertyFog *fog;  
// 当前(Effect)效果的命名         
@property (nullable, nonatomic, copy) NSString *label; 
// 准备绘制效果
- (void) prepareToDraw;
@end

Demo --- 01

GLKit绘制必须的步骤

  1. 初始化上下文context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
  2. 设置上下文[EAGLContext setCurrentContext:context];
  3. 获取GLKViewGLKView *view = (GLKView *)self.view; view.context = context;
  4. 绘制视图的内容,必须实现的代理- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect; 15780316348249.jpg 15780316187709.jpg 15780316970556.jpg 15780317066179.jpg

Demo --- 渲染图片

#import "ViewController.h"

@interface ViewController () {
    EAGLContext *context;//上下文
    GLKBaseEffect *cEffect;//渲染效果
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //1.OpenGL ES相关初始化
    [self setUpConfig];
    //2.加载顶点/纹理坐标数据
    [self setUpVertexData];
    //3.加载纹理数据(使用GLBaseEffect)
    [self setUpTexture];

}

- (void)setUpTexture {
    //1.获取纹理图片路径
    NSString *filePath = [[NSBundle mainBundle]pathForResource:@"image_01" ofType:@"jpg"];
    //2.设置纹理参数(图片翻转)
    //纹理坐标原点是左下角,但是图片显示原点应该是左上角.所以图片需要翻转一下
    NSDictionary *options = @{GLKTextureLoaderOriginBottomLeft : @(YES),};//把左下角设为原点(改动的不是原始纹理的坐标,改变的是映射关系)
    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;
}

- (void)setUpVertexData {
    // 1.设置顶点数组{x,y,z, s,t}顶点坐标x,y,z 纹理坐标s,t
    GLfloat vertexData[] = {
        0.5, -0.5, 0.0f,    1.0f, 0.0f, //右下
        0.5, 0.5, -0.0f,    1.0f, 1.0f, //右上
        -0.5, 0.5, 0.0f,    0.0f, 1.0f, //左上

        0.5, -0.5, 0.0f,    1.0f, 0.0f, //右下
        -0.5, 0.5, 0.0f,    0.0f, 1.0f, //左上
        -0.5, -0.5, 0.0f,   0.0f, 0.0f, //左下
    };
    // 2.开辟顶点缓存区
    //2.1.创建顶点缓存区标识符ID
    GLuint bufferID;
    glGenBuffers(1, &bufferID);//参数1表示顶点缓存区数量
    //2.2.绑定顶点缓存区.(明确作用),存储数组
    glBindBuffer(GL_ARRAY_BUFFER, bufferID);
    //2.3.将顶点数组的数据copy到顶点缓存区中(GPU显存中)
    /*参数1:存储数据类型 GL_ARRAY_BUFFER,存储数组类型数据
     参数2:数据长度
     参数3:数据源
     参数4:作用,GL_STATIC_DRAW:静态绘制
     */
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
    // 3.打开读取通道
  //在iOS中,默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的.意味着,顶点数据在着色器端(服务端)是不可用的.即使你已经使用glBufferData方法,将顶点数据从内存拷贝到顶点缓存区中(GPU显存中). 所以, 必须由glEnableVertexAttribArray 方法打开通道.指定访问属性.才能让顶点着色器能够访问到从CPU复制到GPU的数据.
    /*
     glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
     功能: 上传顶点数据到显存的方法(设置合适的方式从buffer里面读取数据)
     参数:
     index,指定要修改的顶点属性的索引值
     size, 每次读取数量。(如position是由3个(x,y,z)组成,而颜色是4个(r,g,b,a),二维纹理则是2个(s,t).)
     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
     */
    //打开顶点属性通道
    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(GLfloat) * 5, (GLfloat *)NULL + 3);
    /*
     定义好的属性通道:
     GLKVertexAttribPosition,//顶点
     GLKVertexAttribNormal,//法线
     GLKVertexAttribColor,//颜色
     GLKVertexAttribTexCoord0,//纹理1
     GLKVertexAttribTexCoord1//纹理2
     */
}

- (void)setUpConfig {
    // 1.初始化上下文
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (!context) {
        NSLog(@"error");
        return ;
    }
    // 2.设置上下文
    [EAGLContext setCurrentContext:context];
    // 3.获取GLKView
    GLKView *view = (GLKView *)self.view;
    view.context = context;
    // 设置渲染缓冲区
    view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
    // 设置清屏颜色
    glClearColor(0.7, 0.7, 0.7, 1.0);
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    //1.清理颜色缓冲区
    glClear(GL_COLOR_BUFFER_BIT);
    //2.准备绘制
    [cEffect prepareToDraw];
    //3.开始绘制
    glDrawArrays(GL_TRIANGLES, 0, 6);
}


@end

15780378704099.jpg

Demo --- 旋转正方体

#import "ViewController.h"
#import <GLKit/GLKit.h>

typedef struct {
    GLKVector3 vertexCoord;//顶点坐标
    GLKVector2 textureCoord;//纹理坐标
} VertexData;

// 顶点数 6个面,每个面2个三角形 每个三角形3个顶点
static NSInteger const VertexCount = 36;

@interface ViewController ()<GLKViewDelegate>

// 效果
@property (nonatomic, strong) GLKBaseEffect *baseEffect;
// glkView
@property (nonatomic, strong) GLKView *glkView;
// 开辟的顶点数据区域首地址指针
@property (nonatomic, assign) VertexData *vertices;
// 定时器
@property (nonatomic, strong) CADisplayLink *displayLink;
// 旋转z角度
@property (nonatomic, assign) NSInteger angle;
// 顶点缓存区标识
@property (nonatomic, assign) GLuint vertexBuffer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    // OpenGL ES 相关配置
    [self setupOpenGLES];
    // 设置顶点、纹理数据
    [self setupVertexData];
    // 添加CADisplayLink
    [self addCADisplayLink];
}

// OpenGL ES 相关配置
- (void)setupOpenGLES {
    // 创建context
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    // 设置context
    [EAGLContext setCurrentContext:context];
    CGRect rect = CGRectMake(0, (self.view.frame.size.height - self.view.frame.size.width)/2, self.view.frame.size.width, self.view.frame.size.width);
    // 创建GLKView、设置代理
    self.glkView = [[GLKView alloc] initWithFrame:rect context:context];
    self.glkView.delegate = self;//代理
    self.glkView.drawableDepthFormat = GLKViewDrawableDepthFormat24;//深底缓存
    glClearColor(0.7, 0.7, 0.7, 1.0);
    // 默认是(0, 1),这里用于翻转 z 轴,使正方形朝屏幕外
    //glDepthRangef(1, 0);
    [self.view addSubview:self.glkView];
    /// 加载图片纹理
    NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"kkk" ofType:@"png"];
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:imagePath];
    NSDictionary *options = @{GLKTextureLoaderOriginBottomLeft : @(YES)};
    GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithCGImage:image.CGImage options:options error:nil];
    // 设置baseEffect
    self.baseEffect = [[GLKBaseEffect alloc] init];
    self.baseEffect.texture2d0.name = textureInfo.name;
    self.baseEffect.texture2d0.target = textureInfo.target;
}

// 设置顶点、纹理数据
- (void)setupVertexData {
    // 分配内存
    self.vertices = malloc(sizeof(VertexData) * VertexCount);
    // 设置数据
    // 前面
    self.vertices[0] = (VertexData){{-0.5, 0.5, 0.5},  {0, 1}};
    self.vertices[1] = (VertexData){{-0.5, -0.5, 0.5}, {0, 0}};
    self.vertices[2] = (VertexData){{0.5, 0.5, 0.5},   {1, 1}};

    self.vertices[3] = (VertexData){{-0.5, -0.5, 0.5}, {0, 0}};
    self.vertices[4] = (VertexData){{0.5, 0.5, 0.5},   {1, 1}};
    self.vertices[5] = (VertexData){{0.5, -0.5, 0.5},  {1, 0}};

    // 上面
    self.vertices[6] = (VertexData){{0.5, 0.5, 0.5},    {1, 1}};
    self.vertices[7] = (VertexData){{-0.5, 0.5, 0.5},   {0, 1}};
    self.vertices[8] = (VertexData){{0.5, 0.5, -0.5},   {1, 0}};
    self.vertices[9] = (VertexData){{-0.5, 0.5, 0.5},   {0, 1}};
    self.vertices[10] = (VertexData){{0.5, 0.5, -0.5},  {1, 0}};
    self.vertices[11] = (VertexData){{-0.5, 0.5, -0.5}, {0, 0}};

    // 下面
    self.vertices[12] = (VertexData){{0.5, -0.5, 0.5},    {1, 1}};
    self.vertices[13] = (VertexData){{-0.5, -0.5, 0.5},   {0, 1}};
    self.vertices[14] = (VertexData){{0.5, -0.5, -0.5},   {1, 0}};
    self.vertices[15] = (VertexData){{-0.5, -0.5, 0.5},   {0, 1}};
    self.vertices[16] = (VertexData){{0.5, -0.5, -0.5},   {1, 0}};
    self.vertices[17] = (VertexData){{-0.5, -0.5, -0.5},  {0, 0}};

    // 左面
    self.vertices[18] = (VertexData){{-0.5, 0.5, 0.5},    {1, 1}};
    self.vertices[19] = (VertexData){{-0.5, -0.5, 0.5},   {0, 1}};
    self.vertices[20] = (VertexData){{-0.5, 0.5, -0.5},   {1, 0}};
    self.vertices[21] = (VertexData){{-0.5, -0.5, 0.5},   {0, 1}};
    self.vertices[22] = (VertexData){{-0.5, 0.5, -0.5},   {1, 0}};
    self.vertices[23] = (VertexData){{-0.5, -0.5, -0.5},  {0, 0}};

    // 右面
    self.vertices[24] = (VertexData){{0.5, 0.5, 0.5},    {1, 1}};
    self.vertices[25] = (VertexData){{0.5, -0.5, 0.5},   {0, 1}};
    self.vertices[26] = (VertexData){{0.5, 0.5, -0.5},   {1, 0}};
    self.vertices[27] = (VertexData){{0.5, -0.5, 0.5},   {0, 1}};
    self.vertices[28] = (VertexData){{0.5, 0.5, -0.5},   {1, 0}};
    self.vertices[29] = (VertexData){{0.5, -0.5, -0.5},  {0, 0}};

    // 后面
    self.vertices[30] = (VertexData){{-0.5, 0.5, -0.5},   {0, 1}};
    self.vertices[31] = (VertexData){{-0.5, -0.5, -0.5},  {0, 0}};
    self.vertices[32] = (VertexData){{0.5, 0.5, -0.5},    {1, 1}};
    self.vertices[33] = (VertexData){{-0.5, -0.5, -0.5},  {0, 0}};
    self.vertices[34] = (VertexData){{0.5, 0.5, -0.5},    {1, 1}};
    self.vertices[35] = (VertexData){{0.5, -0.5, -0.5},   {1, 0}};

    //开辟缓存区 VBO
    glGenBuffers(1, &_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    GLsizeiptr bufferSizeBytes = sizeof(VertexData) * VertexCount;
    glBufferData(GL_ARRAY_BUFFER, bufferSizeBytes, self.vertices, GL_STATIC_DRAW);

    //顶点数据
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), NULL + offsetof(VertexData, vertexCoord));
    // offsetof(VertexData, vertexCoord) 获取VertexData结构体中,vertexCoord的偏移量
    //纹理数据
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), NULL + offsetof(VertexData, textureCoord));
}

// 添加CADisplayLink
-(void)addCADisplayLink {
    self.angle = 0.0;
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
    self.displayLink.frameInterval = 2;
    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)update {
    //计算旋转度数
    self.angle = (self.angle + 1) % 360;
    //修改模型视图矩阵
    self.baseEffect.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(self.angle), 0.2, 0.5, 0.3);
    //重新渲染
    [self.glkView display];
}

#pragma mark - GLKViewDelegate
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    // 开启深度测试
    glEnable(GL_DEPTH_TEST);
    // 清除颜色缓存区&深度缓存区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // 准备绘制
    [self.baseEffect prepareToDraw];
    // 绘图
    glDrawArrays(GL_TRIANGLES, 0, VertexCount);
}

#pragma mark - dealloc
- (void)dealloc {
    // 关闭定时器
    [self.displayLink invalidate];
    // 清除上下文
    if ([EAGLContext currentContext] == self.glkView.context) {
        [EAGLContext setCurrentContext:nil];
    }
    // 释放申请的内存
    if (_vertices) {
        free(_vertices);
        _vertices = nil;
    }
    // 释放开辟的数据缓存区
    if (_vertexBuffer) {
        glDeleteBuffers(1, &_vertexBuffer);
        _vertexBuffer = 0;
    }
}


@end

15787316897740.jpg

代码资源见:Github