用GLKit写出旋转的效果
GLKit
GLKit 框架的设计⽬标是为了简化基于OpenGL / OpenGL ES 的应⽤开发 。它的出现加快OpenGL ES或OpenGL应⽤程序开发。 使⽤数学库,背景纹理加载,预先创建的着⾊器效果,以及标准视图和视图控制器来实现渲染循环。
GLKit框架提供了功能和类,可以减少创建新的基于着⾊器的应⽤程序所需的⼯作量,或者⽀持依赖早期版本的OpenGL ES或OpenGL提供的固定函数顶点或⽚段处理的现有应⽤程序。
- GLKView 提供绘制场所(View)
- GLKViewController(扩展于标准的UIKit 设计模式. ⽤于绘制视图内容的管理与呈现)
接下来,我们用一个小Demo来认识一下GLKit。首先看下效果:
屏幕录制2020-07-27下午8.57.33.gif
流程
下图为绘制一个图形的流程
- 首先初始化上下文
- 设置顶点坐标,并放入顶点缓冲区
- 设置纹理
-
最后绘制
流程图.png
准备工作
- 导包
//GLKit 库
#import <GLKit/GLKit.h>
//OpenGL库
#import <OpenGLES/ES3/glext.h>
#import <OpenGLES/ES3/gl.h>
- 定义变量
//上下文
EAGLContext *context;
//⼀种简单光照/着⾊系统,⽤于基于着⾊器OpenGL 渲染
GLKBaseEffect *cEffect;
//旋转角度
int angle;
- 设置控制器为GLKViewController
@interface ViewController : GLKViewController
viewDidLoad
viewDidLoad方法如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//1.初始化GLKView和上下文
[self setUpConfig];
//2.初始化顶点
[self setUpVertexData];
//3.渲染
[self setUpTexture];
}
setUpConfig
- 初始化EAGLContext
context = [[EAGLContext alloc] initWithAPI:(kEAGLRenderingAPIOpenGLES3)];
if (!context) {
NSLog(@"创建失败!");
}
其中kEAGLRenderingAPIOpenGLES3
是一个枚举值,有
typedef NS_ENUM(NSUInteger, EAGLRenderingAPI)
{
kEAGLRenderingAPIOpenGLES1 = 1,
kEAGLRenderingAPIOpenGLES2 = 2,
kEAGLRenderingAPIOpenGLES3 = 3,
};
这3种类型,大家可以任意选择。
- 绑定当前上下文,在一个项目中,我们可以有多个上下文,但是我们需要设置当前的上下文。
//设置当前上下文
[EAGLContext setCurrentContext:context];
-
设置GLKView
由于我们项目中用的StoryBoard,所以我们将view设置为GLKView,如图:
设置GLKView.png
代码如下:
//设置GLKView,将self.view转为GLKView
GLKView *view = (GLKView *)self.view;
//将上下文存入view.context
view.context = context;
//设置颜色缓冲区
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
//设置深度缓冲区
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
glClearColor(0, 0, 0, 1);
- 颜色缓冲区有3个值
GLKViewDrawableColorFormatRGBA8888
,GLKViewDrawableColorFormatRGB565
,GLKViewDrawableColorFormatSRGBA8888
- 深度缓冲区有3个值
GLKViewDrawableDepthFormatNone
,GLKViewDrawableDepthFormat16
,GLKViewDrawableDepthFormat24
,数字越大,表示精度越大,越精确,但是也意味着更消耗性能。
setUpConfig整体代码如下:
//1.初始化
-(void)setUpConfig{
//初始化上下文
context = [[EAGLContext alloc] initWithAPI:(kEAGLRenderingAPIOpenGLES3)];
if (!context) {
NSLog(@"创建失败!");
}
//设置当前上下文
[EAGLContext setCurrentContext:context];
//设置GLKView
GLKView *view = (GLKView *)self.view;
view.context = context;
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
glClearColor(0, 0, 0, 1);
}
setUpVertexData
- 设置顶点
我们用一个金字塔形状的顶点。
GLfloat vertexData[] = {
//金字塔前面
0.0f, 1.0f, 0.0f, 0.5f, 1.0f,
-1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 1.0f, 0.0f,
//金字塔左边
0.0f, 1.0f, 0.0f, 0.5f, 1.0f,
-1.0f, -1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 1.0f, 0.0f, 0.0f,
//金字塔右边
0.0f, 1.0f, 0.0f, 0.5f, 1.0f,
1.0f, -1.0f, 1.0f, 1.0f, 0.0f,
1.0f, -1.0f, -1.0f, 0.0f, 0.0f,
//金字塔后边
0.0f, 1.0f, 0.0f, 0.5f, 1.0f,
1.0f, -1.0f, -1.0f , 0.0f, 0.0f,
-1.0f, -1.0f, -1.0f,1.0f, 0.0f
};
可以看到,我们一行有5条数据,这是为什么呢?
因为,我们顶点数组不仅仅包括顶点数据,还包括了纹理数据,所以一般情况下,我们的顶点数据和纹理坐标都用一个数组来包括。每行前面3条数据为顶点数据,后面2条为纹理坐标。
- 创建顶点缓冲区
//创建顶点缓冲区
GLuint bufferId;
//其中1表示纹理的个数
glGenBuffers(1, &bufferId);
- 绑定顶点缓冲区
//绑定顶点缓冲区(存数组)
//GL_ARRAY_BUFFER表示数据以数组的形式绑定
glBindBuffer(GL_ARRAY_BUFFER, bufferId);
- 将顶点数据copy到顶点缓冲区
//将顶点数据copy到顶点缓冲区 GL_STATIC_DRAW表示用来绘制
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
- 开启通道
//3.打开读取通道.
/*
(1)在iOS中, 默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的.
意味着,顶点数据在着色器端(服务端)是不可用的. 即使你已经使用glBufferData方法,将顶点数据从内存拷贝到顶点缓存区中(GPU显存中).
所以, 必须由glEnableVertexAttribArray 方法打开通道.指定访问属性.才能让顶点着色器能够访问到从CPU复制到GPU的数据.
注意: 数据在GPU端是否可见,即,着色器能否读取到数据,由是否启用了对应的属性决定,这就是glEnableVertexAttribArray的功能,允许顶点着色器读取GPU(服务器端)数据。
(2)方法简介
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个.)
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
*/
//vertexData 有2种数据 1个是顶点数据,2个是纹理数据
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);
在iOS中, 默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的,所以我们要打开通道glEnableVertexAttribArray
,GLKVertexAttribPosition
表示顶点数据,GLKVertexAttribTexCoord0
表示纹理数据
//参数1表示是设置顶点数据还是纹理数据
//参数2表示每次读取的数量,由于是顶点数据,所以用3
//参数3表示数据的类型,我们用GL_FLOAT
//参数4表示指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE),我们一般用GL_FALSE
//参数5指定连续顶点属性之间的偏移量。因为,在上面的数组中,包括了顶点坐标和纹理坐标,所以第一次读完后,下一次要读新的顶点要移动5次,所以用sizeof(GLfloat) * 5
//参数6表示读取的起始位。
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
纹理坐标的读取方式和顶点坐标一致,看上面的代码即可。
setUpVertexData完整代码如下:
//2.
-(void)setUpVertexData{
//1.设置顶点数组(顶点坐标,纹理坐标)
/*
纹理坐标系取值范围[0,1];原点是左下角(0,0);
故而(0,0)是纹理图像的左下角, 点(1,1)是右上角.
*/
//前面3个是顶点坐标,后面2个是纹理坐标
GLfloat vertexData[] = {
//金字塔前面
0.0f, 1.0f, 0.0f, 0.5f, 1.0f, //右下
-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, //右上
1.0f, -1.0f, 1.0f, 1.0f, 0.0f, //左上
//金字塔左边
0.0f, 1.0f, 0.0f, 0.5f, 1.0f, //右下
-1.0f, -1.0f, -1.0f, 1.0f, 0.0f, //左上
-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, //左下
//金字塔右边
0.0f, 1.0f, 0.0f, 0.5f, 1.0f, //右下
1.0f, -1.0f, 1.0f, 1.0f, 0.0f, //左上
1.0f, -1.0f, -1.0f, 0.0f, 0.0f, //左下
//金字塔后边
0.0f, 1.0f, 0.0f, 0.5f, 1.0f,
1.0f, -1.0f, -1.0f , 0.0f, 0.0f,
-1.0f, -1.0f, -1.0f,1.0f, 0.0f
};
// GLfloat vertexData[] = {
//
//
// //面向我们第一面:
// -0.5, -0.5, 0.5, 0.0,0.0,
// 0.5, -0.5, 0.5, 1.0,0.0,
// 0.5, 0.5, 0.5, 1.0,1.0,
// 0.5, 0.5, 0.5, 1.0,1.0,
// -0.5, 0.5, 0.5, 0.0,1.0,
// -0.5, -0.5, 0.5, 0.0,0.0,
//
// //背后的那面
// 0.5, -0.5, -0.5, 0.0,0.0,
// -0.5, -0.5, -0.5, 1.0,0.0,
// -0.5, 0.5, -0.5, 1.0,1.0,
// -0.5, 0.5, -0.5, 1.0,1.0,
// 0.5, 0.5, -0.5, 0.0,1.0,
// 0.5, -0.5, -0.5, 0.0,0.0,
//
// //左边的那面
// -0.5, -0.5, -0.5, 0.0,0.0,
// -0.5, -0.5, 0.5, 1.0,0.0,
// -0.5, 0.5, 0.5, 1.0,1.0,
// -0.5, 0.5, 0.5, 1.0,1.0,
// -0.5, 0.5, -0.5, 0.0,1.0,
// -0.5, -0.5,-0.5, 0.0,0.0,
//
// //右边那面
// 0.5, -0.5, 0.5, 0.0,0.0,
// 0.5, -0.5, -0.5, 1.0,0.0,
// 0.5, 0.5, -0.5, 1.0,1.0,
// 0.5, 0.5, -0.5, 1.0,1.0,
// 0.5, 0.5, 0.5, 0.0,1.0,
// 0.5, -0.5,0.5, 0.0,0.0,
//
// //顶上那面
// -0.5, 0.5, 0.5, 0.0,0.0,
// 0.5, 0.5, 0.5, 1.0,0.0,
// 0.5, 0.5, -0.5, 1.0,1.0,
// 0.5, 0.5, -0.5, 1.0,1.0,
// -0.5, 0.5, -0.5, 0.0,1.0,
// -0.5, 0.5, 0.5, 0.0,0.0,
//
// //底下那面
// -0.5, -0.5, -0.5, 0.0,0.0,
// 0.5, -0.5, -0.5, 1.0,0.0,
// 0.5, -0.5, 0.5, 1.0,1.0,
// 0.5, -0.5, 0.5, 1.0,1.0,
// -0.5, -0.5, 0.5, 0.0,1.0,
// -0.5, -0.5, -0.5, 0.0,0.0,
//
//
// };
//创建顶点缓冲区
GLuint bufferId;
glGenBuffers(1, &bufferId);
//绑定顶点缓冲区(存数组)
glBindBuffer(GL_ARRAY_BUFFER, bufferId);
//将顶点数据copy到顶点缓冲区 GL_STATIC_DRAW绘制
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
//3.打开读取通道.
/*
(1)在iOS中, 默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的.
意味着,顶点数据在着色器端(服务端)是不可用的. 即使你已经使用glBufferData方法,将顶点数据从内存拷贝到顶点缓存区中(GPU显存中).
所以, 必须由glEnableVertexAttribArray 方法打开通道.指定访问属性.才能让顶点着色器能够访问到从CPU复制到GPU的数据.
注意: 数据在GPU端是否可见,即,着色器能否读取到数据,由是否启用了对应的属性决定,这就是glEnableVertexAttribArray的功能,允许顶点着色器读取GPU(服务器端)数据。
(2)方法简介
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个.)
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
*/
//vertexData 有2种数据 1个是顶点数据,2个是纹理数据
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);
}
setUpTexture
//读取文件路径
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"jay" ofType:@"jpg"];
//设置纹理的参数(由于纹理坐标原点是左下角,而图片显示原点是左上角,需要需要进行转换)
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
//初始化textureInfo
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
//初始化GLKBaseEffect
cEffect = [[GLKBaseEffect alloc] init];
cEffect.texture2d0.enabled = GL_TRUE;
//将textureInfo存入到cEffect
cEffect.texture2d0.name = textureInfo.name;
由于是一个立体图形,所以我们要使用透视投影
//设置透视投影矩阵
CGFloat aspect = fabs(self.view.bounds.size.width / self.view.bounds.size.height);
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0), aspect, 0.1, 100.0);
cEffect.transform.projectionMatrix = projectionMatrix;
setUpTexture完整代码如下:
-(void)setUpTexture{
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"jay" ofType:@"jpg"];
//设置纹理的参数
//纹理坐标原点:左下角 图片显示原点:左上角
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
cEffect = [[GLKBaseEffect alloc] init];
cEffect.texture2d0.enabled = GL_TRUE;
cEffect.texture2d0.name = textureInfo.name;
//设置透视投影矩阵
CGFloat aspect = fabs(self.view.bounds.size.width / self.view.bounds.size.height);
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0), aspect, 0.1, 100.0);
cEffect.transform.projectionMatrix = projectionMatrix;
}
GLKViewDelegate
最后使用代理进行绘制,GLKView必须要实现drawInRect
这个代理方法。
#pragma -mark GLKViewDelegate
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
//开启深度测试
glEnable(GL_DEPTH_TEST);
//清除颜色和深度缓冲区的缓存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 更新旋转
[self updateAngle];
//准备绘制
[cEffect prepareToDraw];
//开始绘制,所有顶点以GL_TRIANGLES的方式连接
glDrawArrays(GL_TRIANGLES, 0, 12);
}
- (void)updateAngle {
angle = (angle + 2) % 360;
GLKMatrix4 modelviewMatrix = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -4.0);
modelviewMatrix = GLKMatrix4Rotate(modelviewMatrix, GLKMathDegreesToRadians(angle), 0.3, 0.5, 0.7);
c