OpenGL 九 - 初识 OpenGL ES + 纹理立方体案例
一、OpenGL ES 初识
1、OpenGL ES 简介
OpenGL ES (OpenGL for Embedded Systems) 是以手持和嵌⼊式为⽬标的⾼级3D图形应用程序编程接⼝口(API)。OpenGL ES 是⽬前智能⼿机中占据统治地位的图形API。⽀持的平台:iOS、Andriod、 BlackBerry、bada、Linux、Windows。
OpenGL ES 允许应⽤程序利用底层图形处理器的强⼤功能。iOS 设备上的 GPU 可以执⾏复杂的2D和3D绘图,以及最终图像中每个像素的复杂着⾊计算。
苹果官方文档 OpenGL ES Design Guidelines 中 OpenGL ES 的构建流程有详细介绍。
2、主要流程点介绍
2.1)顶点着色器:
1、着⾊器程序:描述顶点上执行操作的顶点着⾊器程序源代码/可执⾏⽂件
2、顶点着⾊器输⼊(属性): ⽤顶点数组提供每个顶点的数据
3、统⼀变量(uniform):顶点/⽚元着⾊器使⽤的不变数据
4、采样器:代表顶点着⾊器使⽤纹理的特殊统一变量类型
gl_Position:内建变量 --> 顶点数据(每一个),经过处理后得到的值
顶点着⾊器业务:
1、矩阵变换位置
2、计算光照公式⽣成逐顶点颜色
3、⽣成/变换纹理坐标
总结:它可以⽤于执⾏⾃定义计算、实施新的变换、照明或者传统的固定功能所不允许的基于顶点操作的效果。
2.2)图元装配
顶点着⾊器之后的下一个阶段是 图元装配。
图元(Primitive):点、线、三⻆形等。
图元装配:将顶点数据计算成⼀个个图元。在这个阶段会执⾏裁剪、透视分割和 Viewport 变换操作。
图元类型和顶点索 确定将被渲染的单独图元。对于每个单独图元及其对应的顶点,图元装配阶段执⾏的操作包括:将顶点着⾊器的输出值执⾏裁剪、透视分割、视⼝变换 后进⼊光栅化阶段。
2.3)光栅化
在这个阶段,绘制对应的图元(点/线/三⻆角形)。
光栅化就是将图元转化成⼀组⼆维⽚段 的过程。而这些转化的⽚段将由⽚元着⾊器处理。这些⼆维⽚段就是屏幕上可绘制的像素。
--> 二维:屏幕2维嘛 像素点 --> 每个像素 100个像素点 片元着色器要处理100次 --> 性能?并不会导致性能问题,因为片元着色器是在GPU处理中其实是可以真正的实现高并发,并不会出现 CPU 上出现的性能问题。
2.4)片元着色器(片段着⾊器)
1、着⾊器程序:描述⽚段上执⾏操作的⽚元着⾊器程序源代码/可执⾏⽂件
2、输⼊变量:光栅化单元⽤插值为每个⽚段⽣成的顶点着色器输出
3、统⼀变量(uniform):顶点/⽚元着⾊器使用的不变数据
4、采样器:代表⽚元着⾊器使⽤纹理的特殊统一变量类型
gl_FragColor:内建变量 --> 每一个像素点,经过片元着色器处理后得到的值
⽚元着⾊器业务:
1、计算颜色
2、获取纹理值
3、往像素点中填充颜⾊值(纹理值/颜色值)
总结:它可以⽤于图⽚/视频/图形中每个像素的颜色填充
⽐如给视频添加滤镜,实际上就是将视频中 每一帧的 每个图⽚的像素点颜色填充进⾏修改 --> 每一帧图片 --> 每一个像素点处理 --> 新值 --> 帧缓冲区 --> 显示
2.5)逐片段操作
像素归属测试:确定帧缓存区中位置(Xw,Yw)的像素⽬前是不是归属于OpenGL ES所 有。例如,一个显示OpenGL ES 帧缓存区 View 被另外一个View 所遮蔽,则窗⼝系统可以确定被遮蔽的像素不属于OpenGL ES上下文,从而不全显示这些像素。⽽像素归属测试是 OpenGL ES 的一部分,它不由开发者开⼈为控制,⽽是由 OpenGL ES 内部进行。
裁剪测试:裁剪测试确定(Xw,Yw)是否 位于 作为 OpenGL ES 状态的一部分裁剪矩形范围内。如果该⽚段位于裁剪区域之外,则被抛弃。
深度测试:输⼊⽚段的深度值进行比较,确定⽚段是否拒绝测试。
混合:将新生成的⽚段颜色与保存在帧缓存的位置的颜色值组合起来。
抖动:可⽤于 最小化 因为使用有限精度在帧缓存区中保存颜色值⽽产生的 伪像。
2.6)EGL (Embedded Graphics Library )
OpenGL ES 命令需要渲染上下⽂和绘制表面才能完成图形、图像的绘制。
渲染上下文:存储相关OpenGL ES 状态。
绘制表⾯:⽤于绘制图元的表面,它指定渲染所需要的缓存区类型,例如颜⾊缓存区,深度缓冲区和模板缓存区。
OpenGL ES API 并没有提供如何创建渲染上下文或者上下⽂如何连接到原生窗⼝系统。EGL 是 Khronos 渲染API(如OpenGL ES) 和原⽣窗口系统之间的接口。唯⼀⽀持 OpenGL ES 却不⽀持 EGL 的平台是 iOS。Apple 提供了⾃己的 EGL API 用来 iOS 实现,称为 EAGL。
因每个窗口系统都有不同的定义,所以 EGL 提供基本的不透明类型 --> EGLDisplay,这个类型封装了所有系统相关性⽤于和原生窗⼝系统接口。
由于 OpenGL ES 是基于 C 的 API,因此它⾮常便携且受到⼴泛⽀持。作为 C API,它与 Objective-C Cocoa Touch 应⽤程序⽆缝集成。OpenGL ES 规范没有定义窗⼝层,相 反,托管操作系统必须提供函数来创建一个接受命令的 OpenGL ES 渲染上下⽂和一个帧 缓冲区,其中写⼊任何绘图命令的结果。在iOS上使⽤ OpenGL ES 需要使⽤ iOS 类来设置 和呈现绘图表面,并使用平台中⽴的 API 来呈现其内容。
2.7)计算机对于动画的实现
应用程序使用 OpenGL ES 执行动画显示的流程:
二、GLKit 框架
GLKit 框架的设计⽬标是为了简化基于 OpenGL / OpenGL ES 的应⽤开发。它的出现加快了 OpenGL ES 或OpenGL 应⽤程序开发。使⽤数学库、背景纹理理加载、预先创建的着色器效果、以及标准视图GLKView和视图控制器GLKViewController来实现渲染循环。 GLKit 框架提供了功能和类,可以减少创建新的基于着⾊器的应⽤程序所需的⼯作量,或者⽀持依赖早期版本的 OpenGL ES 或 OpenGL 提供的固定函数顶点或⽚段处理的现有应⽤程序。
GLKView:提供绘制场所 --> View
GLKViewController:扩展于标准的 UIKit 设计模式。⽤于绘制视图内容的管理与呈。
1、GLKit
1)加载纹理
2)提供高性能的数学运算
3)提供常见的着色器
4)提供视图和视图控制器
GLKit 代码:
// 官方文档中代码 - (void)viewDidLoad { [super viewDidLoad]; // Create an OpenGL ES context and assign it to the view loaded from storyboard GLKView *view = (GLKView *)self.view; view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; // Configure renderbuffers created by the view view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; view.drawableDepthFormat = GLKViewDrawableDepthFormat24; view.drawableStencilFormat = GLKViewDrawableStencilFormat8; // Enable multisampling view.drawableMultisample = GLKViewDrawableMultisample4X; } // 必须实现的 - (void)drawRect:(CGRect)rect { // Clear the framebuffer glClearColor(0.0f, 0.0f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Draw using previously configured texture, shader, uniforms, and vertex array glBindTexture(GL_TEXTURE_2D, _planetTexture); glUseProgram(_diffuseShading); glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0, _modelViewProjectionMatrix.m); glBindVertexArrayOES(_planetMesh); glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT); }
2、案例
案例一效果(背景色+纹理):
案例二效果:
1)案例一 .m 代码
1 // 2 // ViewController.m 3 // GLKit_Demo001 4 // 5 // Created by Domy on 2020/7/26. 6 // Copyright © 2020 Domy. All rights reserved. 7 // 8 9 #import "ViewController.h" 10 11 // 导入 OpenGL ES 12 #import <OpenGLES/ES3/gl.h> 13 #import <OpenGLES/ES3/glext.h> 14 15 @interface ViewController () { 16 // 创建上下文 17 EAGLContext *context; 18 // 着色器 19 GLKBaseEffect *myEffect; 20 } 21 22 @end 23 24 @implementation ViewController 25 26 - (void)viewDidLoad { 27 [super viewDidLoad]; 28 // Do any additional setup after loading the view. 29 30 // 初始化 31 [self setUpConfig]; 32 33 // 设置 顶点/纹理 坐标数据 34 [self configVertex]; 35 36 // 设置纹理 37 [self configTexture]; 38 } 39 40 - (void)configTexture { 41 42 // 1. 图片路径 43 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"cat" ofType:@"jpg"]; 44 45 // 2. 设置纹理参数 46 // 纹理坐标原点是左下角,图片显示原点应该是左上角 47 NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];// @(1):true 48 // 纹理数据 49 GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil]; 50 51 // 3. 使用 苹果封好的 着色器 GLKBaseEffect 包含了 顶点和片元着色器 52 myEffect = [[GLKBaseEffect alloc] init]; 53 myEffect.texture2d0.enabled = GL_TRUE; 54 myEffect.texture2d0.name = textureInfo.name; 55 56 } 57 58 - (void)configVertex { 59 60 // 1. 顶点数组 数据 61 // 顶点坐标 前3(x,yxz) 纹理坐标 后2(s,t) 62 GLfloat vertexDataArr[] = { 63 1.0, -0.5, 0.0f, 1.0f, 0.0f, //右下 64 1.0, 0.5, 0.0f, 1.0f, 1.0f, //右上 65 -1.0, 0.5, 0.0f, 0.0f, 1.0f, //左上 66 67 1.0, -0.5, 0.0f, 1.0f, 0.0f, //右下 68 -1.0, 0.5, 0.0f, 0.0f, 1.0f, //左上 69 -1.0, -0.5, 0.0f, 0.0f, 0.0f, //左下 70 };// --> 在内存中 71 72 73 /* 74 顶点数组: 开发者可以选择设定函数指针,在调用绘制方法的时候,直接由内存传入顶点数据,也就是说这部分数据之前是存储在内存当中的,被称为顶点数组 75 76 顶点缓存区: 性能更高的做法是,提前分配一块显存,将顶点数据预先传入到显存当中。这部分的显存,就被称为顶点缓冲区 77 */ 78 79 80 // 2. 顶点数据copy到顶点缓冲区 81 // 2.1 创建顶点缓冲区 82 GLuint bufferID; 83 glGenBuffers(1, &bufferID);// 1个缓冲区 &bufferID:ID --> 顶点缓冲区存在GPU中 84 // 2.2 绑定顶点缓冲区 85 // 类型 GL_ARRAY_BUFFER: 存数组所以 array_buffer 86 glBindBuffer(GL_ARRAY_BUFFER, bufferID); 87 // 2.3 顶点数据copy到顶点缓冲区 88 glBufferData(GL_ARRAY_BUFFER, sizeof(vertexDataArr), vertexDataArr, GL_STATIC_DRAW); 89 90 91 // 3. 开启读取通道 -- 不开启即使数据copy到了缓冲区也不能用 92 /* 93 (1)在iOS中, 默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的. 94 意味着,顶点数据在着色器端(服务端)是不可用的. 即使你已经使用glBufferData方法,将顶点数据从内存拷贝到顶点缓存区中(GPU显存中). 95 所以, 必须由glEnableVertexAttribArray 方法打开通道.指定访问属性.才能让顶点着色器能够访问到从CPU复制到GPU的数据. 96 注意: 数据在GPU端是否可见,即,着色器能否读取到数据,由是否启用了对应的属性决定,这就是glEnableVertexAttribArray的功能,允许顶点着色器读取GPU(服务器端)数据。 97 98 (2)方法简介 99 /// 功能: 上传顶点数据到显存的方法(设置合适的方式从buffer里面读取数据) 100 // 参数列表: 101 index, 指定要修改的顶点属性的索引值,例如: GLKVertexAttribPosition 、 GLKVertexAttribTexCoord0 102 size, 每次读取数量。(如position是由3个(x,y,z)组成,而颜色是4个(r,g,b,a),纹理则是2个.) 103 type, 指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。 104 normalized, 指定当被访问时,固定点 数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE) 105 stride, 指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0 106 ptr指定一个指针,指向数组中第一个顶点属性的第一个组件。初始值为0 107 glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr); 108 109 */ 110 // vertexDataArr 数组中存了 顶点坐标和纹理坐标,要分别开启顶点和纹理通道 111 // 3.1 顶点数据 112 glEnableVertexAttribArray(GLKVertexAttribPosition); 113 // 从数组中读取数据:数据传给谁?顶点 GLKVertexAttribPosition -> 每次读取 3 个元素 -> 元素类型是 float,不归一化处理 -> 读取下一个数据的间隔 5 -> 从第 0 位开始读 114 glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0); 115 // 3.2 纹理数据 116 // GLKVertexAttribTexCoord0 GLKVertexAttribTexCoord1 GLKit只支持2个纹理 117 glEnableVertexAttribArray(GLKVertexAttribTexCoord0); 118 // 从数组中读取数据:数据传给谁?纹理 GLKVertexAttribTexCoord0 -> 每次读取 2 个元素 -> 元素类型是 float,不归一化处理 -> 读取下一个数据的间隔 5 -> 从第 3 位开始读 119 glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3); 120 121 } 122 123 - (void)setUpConfig { 124 // 1.初始化上下文 125 context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; 126 if (!context) { 127 NSLog(@"create content failed"); 128 } 129 130 // 2. 设置当前上下文 131 [EAGLContext setCurrentContext:context]; 132 133 // 3. 创建 GLKView 134 GLKView *view = (GLKView *)self.view; 135 view.context = context; 136 137 // 颜色缓冲区 深度缓冲区 138 view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; 139 view.drawableDepthFormat = GLKViewDrawableDepthFormat16; 140 141 // 4. 设置背景色 142 glClearColor(0, 0, 1, 1); 143 } 144 145 // 5. 调用 drawInRect 方法 146 - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { 147 148 glClear(GL_COLOR_BUFFER_BIT);// 清空背景颜色 149 150 // 准备绘制 151 [myEffect prepareToDraw]; 152 // 开始绘制 153 // mode:GL_TRIANGLES -- 图元链接方式 154 // 0: 从第 0 个顶点开始 155 // 6: 共6个顶点 156 glDrawArrays(GL_TRIANGLES, 0, 6); 157 } 158 159 160 @end
2)案例二 .m 代码
1 // 2 // ViewController.m 3 // GLKit_Demo001 4 // 5 // Created by Domy on 2020/7/26. 6 // Copyright © 2020 Domy. All rights reserved. 7 // 8 9 #import "ViewController.h" 10 11 // 导入 OpenGL ES 12 #import <OpenGLES/ES3/gl.h> 13 #import <OpenGLES/ES3/glext.h> 14 15 @interface ViewController () { 16 // 创建上下文 17 EAGLContext *context; 18 // 着色器 19 GLKBaseEffect *myEffect; 20 21 // 立方体旋转 22 int rotation; 23 GLvoid *vertexData; 24 } 25 26 @end 27 28 @implementation ViewController 29 30 - (void)viewDidLoad { 31 [super viewDidLoad]; 32 // Do any additional setup after loading the view. 33 34 // 初始化 35 [self setUpConfig]; 36 // 顶点/纹理 坐标 37 [self configVertex]; 38 // 设置纹理 39 [self configTexture]; 40 } 41 42 // 纹理 43 - (void)configTexture { 44 45 // 1. 图片路径 46 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"cat" ofType:@"jpg"]; 47 48 // 2. 设置纹理参数 49 NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(YES),GLKTextureLoaderOriginBottomLeft, nil]; 50 // 纹理数据 51 GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil]; 52 53 // 3. 着色器 GLKBaseEffect 包含了 顶点和片元着色器 54 myEffect = [[GLKBaseEffect alloc] init]; 55 myEffect.texture2d0.enabled = GL_TRUE; 56 myEffect.texture2d0.name = textureInfo.name; 57 58 59 // 4. 投影矩阵 60 CGFloat aspect = fabs(self.view.bounds.size.width / self.view.bounds.size.height); 61 GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(90.0), aspect, 0.01, 100.0); 62 myEffect.transform.projectionMatrix = projectionMatrix;// 设置投影矩阵 63 } 64 65 // xy 平面上的2个面 66 - (void)configVertex { 67 68 // 1. 69 GLfloat vertexDataArr[] = { 70 // 前 71 0.25, -0.25, 0.0f, 1.0f, 0.0f, //右下 72 0.25, 0.25, 0.0f, 1.0f, 1.0f, //右上 73 -0.25, 0.25, 0.0f, 0.0f, 1.0f, //左上 74 75 0.25, -0.25, 0.0f, 1.0f, 0.0f, //右下 76 -0.25, 0.25, 0.0f, 0.0f, 1.0f, //左上 77 -0.25, -0.25, 0.0f, 0.0f, 0.0f, //左下 78 79 // 后 80 0.25, -0.25, 0.5f, 1.0f, 0.0f, //右下 81 0.25, 0.25, 0.5f, 1.0f, 1.0f, //右上 82 -0.25, 0.25, 0.5f, 0.0f, 1.0f, //左上 83 84 0.25, -0.25, 0.5f, 1.0f, 0.0f, //右下 85 -0.25, 0.25, 0.5f, 0.0f, 1.0f, //左上 86 -0.25, -0.25, 0.5f, 0.0f, 0.0f, //左下 87 88 // 右 89 0.25, -0.25, 0.5f, 1.0f, 0.0f, //右下 90 0.25, 0.25, 0.5f, 1.0f, 1.0f, //右上 91 0.25, 0.25, 0.0f, 0.0f, 1.0f, //左上 92 93 0.25, -0.25, 0.5f, 1.0f, 0.0f, //右下 94 0.25, 0.25, 0.0f, 0.0f, 1.0f, //左上 95 0.25, -0.25, 0.0f, 0.0f, 0.0f, //左下 96 97 // 左 98 -0.25, -0.25, 0.5f, 1.0f, 0.0f, //右下 99 -0.25, 0.25, 0.5f, 1.0f, 1.0f, //右上 100 -0.25, 0.25, 0.0f, 0.0f, 1.0f, //左上 101 102 -0.25, -0.25, 0.5f, 1.0f, 0.0f, //右下 103 -0.25, 0.25, 0.0f, 0.0f, 1.0f, //左上 104 -0.25, -0.25, 0.0f, 0.0f, 0.0f, //左下 105 106 // 上 107 0.25, 0.25, 0.0f, 1.0f, 0.0f, //右下 108 0.25, 0.25, 0.5f, 1.0f, 1.0f, //右上 109 -0.25, 0.25, 0.5f, 0.0f, 1.0f, //左上 110 111 0.25, 0.25, 0.0f, 1.0f, 0.0f, //右下 112 -0.25, 0.25, 0.5f, 0.0f, 1.0f, //左上 113 -0.25, 0.25, 0.0f, 0.0f, 0.0f, //左下 114 115 // 下 116 0.25, -0.25, 0.0f, 1.0f, 0.0f, //右下 117 0.25, -0.25, 0.5f, 1.0f, 1.0f, //右上 118 -0.25, -0.25, 0.5f, 0.0f, 1.0f, //左上 119 120 0.25, -0.25, 0.0f, 1.0f, 0.0f, //右下 121 -0.25, -0.25, 0.5f, 0.0f, 1.0f, //左上 122 -0.25, -0.25, 0.0f, 0.0f, 0.0f, //左下 123 124 }; 125 126 // 2. 顶点数据copy到顶点缓冲区 127 // 2.1 创建顶点缓冲区 128 GLuint bufferID; 129 glGenBuffers(1, &bufferID);// 1个缓冲区 &bufferID:ID --> 顶点缓冲区存在GPU中 130 // 2.2 绑定顶点缓冲区 131 glBindBuffer(GL_ARRAY_BUFFER, bufferID); 132 // 2.3 顶点数据copy到顶点缓冲区 133 glBufferData(GL_ARRAY_BUFFER, sizeof(vertexDataArr), vertexDataArr, GL_STATIC_DRAW); 134 // 3.1 顶点数据 135 glEnableVertexAttribArray(GLKVertexAttribPosition); 136 // 从数组中读取数据:数据传给谁?顶点 GLKVertexAttribPosition -> 每次读取 3 个元素 -> 元素类型是 float,不归一化处理 -> 读取下一个数据的间隔 5 -> 从第 0 位开始读 137 glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0); 138 // 3.2 纹理数据 139 glEnableVertexAttribArray(GLKVertexAttribTexCoord0); 140 glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3); 141 142 } 143 144 - (void)setUpConfig { 145 146 // 147 // 148 /// 149 150 // 1.初始化上下文 151 context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; 152 if (!context) { 153 NSLog(@"create context failed"); 154 } 155 156 // 2. 设置当前上下文 157 [EAGLContext setCurrentContext:context]; 158 159 // 3. 创建 GLKView 160 GLKView *view = (GLKView *)self.view; 161 view.context = context; 162 163 // 颜色缓冲区 深度缓冲区 164 view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888; 165 view.drawableDepthFormat = GLKViewDrawableDepthFormat24; 166 167 // 4. 设置背景色 168 glClearColor(0.7, 0.7, 0.7, 1); 169 } 170 171 // 旋转 172 - (void)update { 173 // Set up transform matrices for the rotating planet 174 175 // Z轴上向里移动 3 176 GLKMatrix4 modelviewMatrix = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -3.0); 177 178 // 旋转度数 179 rotation = (rotation + 2) % 360; 180 // 参数: 矩阵 弧度 x y z 181 modelviewMatrix = GLKMatrix4Rotate(modelviewMatrix, GLKMathDegreesToRadians(rotation), 0.5, 0.5, 0.5); 182 183 // set 模型视图矩阵 184 myEffect.transform.modelviewMatrix = modelviewMatrix; 185 } 186 187 - (void)drawCovers { 188 189 [self update]; 190 // 准备绘制 191 [myEffect prepareToDraw]; 192 // 开始绘制 193 glDrawArrays(GL_TRIANGLES, 0, 36); 194 } 195 196 // 5. 调用 drawInRect 方法 197 - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { 198 199 // 开启深度测试 200 glEnable(GL_DEPTH_TEST); 201 202 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// 清空 颜色 深度缓冲 203 204 [self drawCovers];// 绘制面 205 } 206 207 208 @end