前言

最近在学习OpenGL ES,跟着教程做了一个Demo,在模拟器下的运行效果如下:

demo

Demo代码在这里

OpenGL和OpenGL ES

OpenGL ES是OpenGL的子集,它主要运行在手机上。推荐一个超棒的OpenGL入门教程:Learn OpenGL ;当然也有热心的国人将他翻译过来了,在 这里。通过这些教程的学习,我也算是入了OpenGL的门。

关于将OpenGL运用于iOS上面,也就是使用OpenGL ES,可以看看Apple的官方文档 OpenGL ES Programming Guide for iOS

Demo实现

demo的实现可以分成2个部分:

  1. 运用OpenGL ES显示静态的场景
  2. 添加手势操作,让场景动起来

OpenGL ES 渲染

关于在iOS上使用OpenGL ES渲染,可以借助iOS的GLKit框架。他大大简化了OpenGL ES的使用难度。而渲染的主要步骤分成以下几步:

使用GLKit初始化

创建GLKViewController和GLKView,OpenGL ES渲染的结果将会显示在GLKView的layer上面。而将GLKViewController设置成GLKView的delegate,他会控制整个的渲染流程,比如每秒渲染多少次,何时开始渲染等等

设置GLKView的context属性

OpenGL ES渲染的时候需要一个上下文,也就是GLKView的context,对于他的设置如下:

1
2
view.context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
[EAGLContext setCurrentContext:view.context];

这里创建context是使用OpenGL ES的基石,就相当于你使用GLFW和GLEW对整个环境进行初始化,定义viewport之类的

设置OpenGL ES

设置OpenGL ES的代码和OpenGL的基本相似。

创建shader程序

也就是对你写的glsl文件进行编译链接,最终持有一个Shader programe。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// load glsl file
NSString *vertexFile = [[NSBundle mainBundle] pathForResource:vertexPath ofType:@"glsl"];
NSString *fragmentFile = [[NSBundle mainBundle] pathForResource:fragmentPath ofType:@"glsl"];

NSError *error;

NSString *vertexString = [NSString stringWithContentsOfFile:vertexFile encoding:NSUTF8StringEncoding error:&error];
if (!vertexString) {
NSLog(@"vanney code log : error loading vertex shader : %@", error.localizedDescription);
exit(1);
}

NSString *fragmentString = [NSString stringWithContentsOfFile:fragmentFile encoding:NSUTF8StringEncoding error:&error];
if (!fragmentString) {
NSLog(@"vanney code log : error loading fragment shader : %@", error.localizedDescription);
exit(1);
}

// compiler
GLuint vertexShader, fragmentShader;
GLint compilerSuccess, linkSuccess;
GLchar messages[512];

vertexShader = glCreateShader(GL_VERTEX_SHADER);
const GLchar *vertexStringUTF8 = [vertexString UTF8String];
glShaderSource(vertexShader, 1, &vertexStringUTF8, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &compilerSuccess);
if (compilerSuccess == GL_FALSE) {
glGetShaderInfoLog(vertexShader, sizeof(messages), NULL, messages);
NSLog(@"vanney code log : compile vertex shader error : %@", [NSString stringWithUTF8String:messages]);
exit(1);
}

fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
const GLchar *fragmentStringUTF8 = [fragmentString UTF8String];
glShaderSource(fragmentShader, 1, &fragmentStringUTF8, NULL);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &compilerSuccess);
if (compilerSuccess == GL_FALSE) {
glGetShaderInfoLog(fragmentShader, sizeof(messages), NULL, messages);
NSLog(@"vanney code log : compile fragment shader error : %@", [NSString stringWithUTF8String:messages]);
exit(1);
}

// link
self.program = glCreateProgram();
glAttachShader(self.program, vertexShader);
glAttachShader(self.program, fragmentShader);
glLinkProgram(self.program);
glGetProgramiv(self.program, GL_LINK_STATUS, &linkSuccess);
if (linkSuccess == GL_FALSE) {
glGetProgramInfoLog(self.program, sizeof(messages), NULL, messages);
NSLog(@"vanney code log : link shader error : %@", [NSString stringWithUTF8String:messages]);
exit(1);
}

glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
设置各种和顶点相关的缓冲

在缓冲中写入顶点数据,以及定义好GPU如何对这些顶点数据进行读取等等,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// initialize vertex data
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
//glGenBuffers(1, &VEO);

glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.VEO);
//glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid *) 0);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid *) (3 * sizeof(GLfloat)));
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glBindVertexArray(0);
设置各种纹理缓冲

注意这里要使用 GLKit的GLKTextureLoaderGLKTextureInfo ,代码如下:

1
2
3
4
5
NSError *error;
NSString *containerPath = [[NSBundle mainBundle] pathForResource:@"container" ofType:@"jpg"];
self.containerTexture = [GLKTextureLoader textureWithContentsOfFile:containerPath options:nil error:&error];
glActiveTexture(GL_TEXTURE0);
glBindTexture(self.containerTexture.target, self.containerTexture.name);
开始渲染

只需实现GLKView的delegate (void)glkView:(GLKView *)view drawInRect:(CGRect)rect; 就可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glEnable(GL_DEPTH_TEST);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//glClear(GL_COLOR_BUFFER_BIT);

[self.shader use];

GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(M_PI_4, self.view.bounds.size.width / self.view.bounds.size.height, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(self.shader.program, "projection"), 1, GL_FALSE, projectionMatrix.m);
GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt(0.0f, 0.0f, 3.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
glUniformMatrix4fv(glGetUniformLocation(self.shader.program, "view"), 1, GL_FALSE, viewMatrix.m);

// bind texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(self.containerTexture.target, self.containerTexture.name);
glUniform1i(glGetUniformLocation(self.shader.program, "ourTexture1"), 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(self.faceTexture.target, self.faceTexture.name);
glUniform1i(glGetUniformLocation(self.shader.program, "ourTexture2"), 1);

glBindVertexArray(VAO);
GLKMatrix4 transModel = [self.transformation getModelViewMatrix];
for (int i = 0; i < 10; ++i) {
//GLKMatrix4 modelMatrix = GLKMatrix4TranslateWithVector3(GLKMatrix4Identity, cubePositions[i]);
GLKMatrix4 modelMatrix = GLKMatrix4TranslateWithVector3(transModel, cubePositions[i]);
GLfloat radian = (GLfloat) (20.0f * i / 180 * M_PI);
modelMatrix = GLKMatrix4RotateWithVector3(modelMatrix, radian, GLKVector3Make(1.0f, 0.3f, 0.5f));
glUniformMatrix4fv(glGetUniformLocation(self.shader.program, "model"), 1, GL_FALSE, modelMatrix.m);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
glBindVertexArray(0);
}

添加手势

参看这篇教程 OpenGL ES Transformations with Gestures

他的中心思想就是:向整个GLKView添加各种手势,通过手势传递的信息来调整物体的model的matrix,也就是从局部坐标到世界坐标转换的matrix。

具体的代码可以参考Github上的Transformation类

参考