上一篇我们用GLKit加载了一张图片,学会了如何用GLKit渲染纹理,现在我们将用GLKit做一些更有趣的事情,比如下面我们来做一个这样的效果:
简单分析一下这个效果,一个正方体6个面都加载有一张纹理,并围绕一个任意角度的轴做旋转,下面具体看一下实现代码:
#import "ViewController.h"
#import <GLKit/GLKit.h>
typedef struct {
GLKVector3 positionCoord;
GLKVector2 textureCoord;
} CMVertex;
@interface ViewController ()<GLKViewDelegate>
@property (nonatomic, strong) EAGLContext *mContext;
@property (nonatomic, strong) GLKBaseEffect *effect;
@property (nonatomic, assign) GLuint angle;
@property (nonatomic, strong) GLKView *glkView ;
@property (nonatomic, assign) GLuint buffer ;
@property (nonatomic, assign) CMVertex *vertices;
@property (nonatomic, strong) CADisplayLink *displayLink;
@end
static NSUInteger vertexCount = 36;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self prepareGlInfo];
[self prepareGlData];
[self setupDisplayLine];
}
- (EAGLContext *)mContext {
if(!_mContext) {
_mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if(!_mContext) {
_mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
}
return _mContext;
}
- (void)prepareGlInfo {
[EAGLContext setCurrentContext:self.mContext];
self.glkView = [[GLKView alloc] initWithFrame:self.view.bounds context:self.mContext];
self.glkView .delegate = self;
self.glkView .drawableDepthFormat = GLKViewDrawableDepthFormat24;
[self.view addSubview:self.glkView ];
glClearColor(1.0, 1.0, 1.0, 1.0);
}
- (void)dealloc
{
if([EAGLContext currentContext] == self.glkView.context) {
[EAGLContext setCurrentContext:nil];
}
if(_buffer) {
glDeleteBuffers(1, &_buffer);
_buffer = 0;
}
if (_vertices) {
free(_vertices);
_vertices = nil;
}
[self.displayLink invalidate];
}
- (void)prepareGlData {
_vertices = malloc(sizeof(CMVertex) * vertexCount);
//前面
_vertices[0] = (CMVertex){{-0.5, 0.5, 0.5}, {0.0, 1.0}};
_vertices[1] = (CMVertex){{-0.5, -0.5, 0.5}, {0.0, 0.0}};
_vertices[2] = (CMVertex){{0.5, -0.5, 0.5}, {1.0, 0.0}};
_vertices[3] = (CMVertex){{0.5, -0.5, 0.5}, {1.0, 0.0}};
_vertices[4] = (CMVertex){{0.5, 0.5, 0.5}, {1.0, 1.0}};
_vertices[5] = (CMVertex){{-0.5, 0.5, 0.5}, {0.0, 1.0}};
//右面
_vertices[6] = (CMVertex){{0.5, 0.5, 0.5}, {0.0, 1.0}};
_vertices[7] = (CMVertex){{0.5, -0.5, 0.5}, {0.0, 0.0}};
_vertices[8] = (CMVertex){{0.5, -0.5, -0.5}, {1.0, 0.0}};
_vertices[9] = (CMVertex){{0.5, -0.5, -0.5}, {1.0, 0.0}};
_vertices[10] = (CMVertex){{0.5, 0.5, -0.5}, {1.0, 1.0}};
_vertices[11] = (CMVertex){{0.5, 0.5, 0.5}, {0.0, 1.0}};
//后面
_vertices[12] = (CMVertex){{0.5, 0.5, -0.5}, {0.0, 1.0}};
_vertices[13] = (CMVertex){{0.5, -0.5, -0.5}, {0.0, 0.0}};
_vertices[14] = (CMVertex){{-0.5, -0.5, -0.5}, {1.0, 0.0}};
_vertices[15] = (CMVertex){{-0.5, -0.5, -0.5}, {1.0, 0.0}};
_vertices[16] = (CMVertex){{-0.5, 0.5, -0.5}, {1.0, 1.0}};
_vertices[17] = (CMVertex){{0.5, 0.5, -0.5}, {0.0, 1.0}};
//左
_vertices[18] = (CMVertex){{-0.5, 0.5, -0.5}, {0.0, 1.0}};
_vertices[19] = (CMVertex){{-0.5, -0.5, -0.5}, {0.0, 0.0}};
_vertices[20] = (CMVertex){{-0.5, -0.5, 0.5}, {1.0, 0.0}};
_vertices[21] = (CMVertex){{-0.5, -0.5, 0.5}, {1.0, 0.0}};
_vertices[22] = (CMVertex){{-0.5, 0.5, 0.5}, {1.0, 1.0}};
_vertices[23] = (CMVertex){{-0.5, 0.5, -0.5}, {0.0, 1.0}};
//上
_vertices[24] = (CMVertex){{-0.5, 0.5, -0.5}, {0.0, 1.0}};
_vertices[25] = (CMVertex){{-0.5, 0.5, 0.5}, {0.0, 0.0}};
_vertices[26] = (CMVertex){{0.5, 0.5, 0.5}, {1.0, 0.0}};
_vertices[27] = (CMVertex){{0.5, 0.5, 0.5}, {1.0, 0.0}};
_vertices[28] = (CMVertex){{0.5, 0.5, -0.5}, {1.0, 1.0}};
_vertices[29] = (CMVertex){{-0.5, 0.5, -0.5}, {0.0, 1.0}};
//下
_vertices[30] = (CMVertex){{-0.5, -0.5, 0.5}, {0.0, 1.0}};
_vertices[31] = (CMVertex){{-0.5, -0.5, -0.5}, {0.0, 0.0}};
_vertices[32] = (CMVertex){{0.5, -0.5, -0.5}, {1.0, 0.0}};
_vertices[33] = (CMVertex){{0.5, -0.5, -0.5}, {1.0, 0.0}};
_vertices[34] = (CMVertex){{0.5, -0.5, 0.5}, {1.0, 1.0}};
_vertices[35] = (CMVertex){{-0.5, -0.5, 0.5}, {0.0, 1.0}};
glGenBuffers(1, &_buffer);
glBindBuffer(GL_ARRAY_BUFFER, _buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(CMVertex) * vertexCount, _vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(CMVertex), NULL + offsetof(CMVertex, positionCoord));
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(CMVertex), NULL + offsetof(CMVertex, textureCoord));
NSString *path = [[NSBundle mainBundle] pathForResource:@"meinv" ofType:@"jpg"];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:path options:@{GLKTextureLoaderOriginBottomLeft:@1} error:nil];
self.effect = [[GLKBaseEffect alloc] init];
self.effect.texture2d0.name = textureInfo.name;
self.effect.texture2d0.target = textureInfo.target;
}
- (void)setupDisplayLine {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAngle)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)updateAngle {
self.angle += 1;
self.angle = self.angle % 360;
self.effect.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(self.angle), 0.3, 0.5, 0.2);
[self.glkView display];
}
# pragma mark -GLKViewDelegate
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
[self.effect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, vertexCount);
}
@end
下面来简单分析一下这段代码,首先我们定义了一个结构体CMVertex用来表示顶点信息,包括顶点坐标(positionCoord)和纹理坐标(textureCoord).
上一篇我们提到过OpenGL ES 需要渲染上下文和绘图表面才能完成图形图像的绘制,所以第一步需要配置好渲染上下文和绘制表面。这里我们用prepareGlInfo方法来完成这个任务。需要注意的是这里我们渲染的是一个立体画面,需要开启深度测试所以需要提前配置好深度缓冲区的格式,这里我们设置为GLKViewDrawableDepthFormat24。
然后我们需要配置好顶点和纹理坐标数据。简单说一下,因为正方体有6个面,每个月由2个三角形组成所以是36个顶点。这里顶点坐标的设置就不说了,大家只需要知道原点在正方体正中心然后想象一下你站在6个方位(上下左右前后)就能知道顶点坐标,纹理坐标大家如果有不清楚的可以翻看前面关于OpenGL的文章。这里需要重点说一下顶点数据(包括顶点坐标和纹理坐标)是如何从cpu到gpu的以及OpenGL如何从GPU中读取这些数据。首先顶点数据存储在_vertices数组里,也就是cpu中,这数组也就是我们常说的VAO(Vertex Array Object);然后利用glGenBuffer,glBindBuffer生成并绑定顶点缓冲区即VBO(Vertex Buffer Object),利用glBufferData将顶点数据拷贝到GPU中;接着利用glEnableVertexAttribArray开启相应通道并利用glVertexAttribPointer设置数据的读取方式,比如这里首先开启了顶点坐标的属性通道,并且告诉GPU从GLKVertexAttribPosition这个属性通道读取数据,第一个顶点数据的开始位置为NULL + offsetof(CMVertex, positionCoord),每次读取3个GLFloat数据,不需要做归一化,读取完一个顶点坐标后下一个顶点坐标的数据和当前顶点坐标数据间隔sizeof(CMVertex)个字节(也就是步长为sizeof(CMVertex)),有人可能会问为什么是从NULL开始读取数据,不应该是某个指针地址吗?然而并不是因为这里已经不是从大家熟悉的CPU内存读取数据了,而是在GPU缓存中。同理纹理数据也是一样的,我们只要确认从哪里来读第一个纹理坐标,每次读几个基础数据(即一个纹理坐标包含几个基础数据),每次度的基础数据的类型,读取完后这些数据是否需要做归一化,以及读取下一个纹理坐标时需要移动多远开始读,就能够明确的告诉GPU纹理坐标的数据如何读取了。至于这些函数的具体参数说明在上一节已经介绍过,有疑惑的同学不妨翻过头再看看。完成这些后最后我们可以利用GLKTextureLoader载入纹理图片,并讲纹理信息配置到GLBaseEffect中。到这里关于OpenGL的准备工作基本就做完了,我们可以开始绘制了,实现GLKViewDelegate的代理方法glkView:(GLKView *)view drawInRect:(CGRect)rect来完成具体的绘制工作,不过不要忘记开启深度测试。
上面已经完成了基本的绘制工作,不过只是一个不会动的静态效果,那么如果让这个正方体旋转起来呢?这里我们利用CADisplayLink定时刷新并重绘画面就行了,重绘的时候注意需要利用GLBaseEffect设置模型视图矩阵来完成旋转操作。设置完模型视图矩阵后不要忘记调用[self.glkView display]重新绘制视图。