我是搞iOS开发的,所以本博客会用到OC的代码作例子。OpenGL ES的接口是用C写的,对于一些矩阵的操作还需要第三方式的库,看到这里是不是整个人都不好了的感觉!我开始学的时候也是这种感觉。但有一个好消息要告诉你苹果对OpenGL ES进行的部分封装出了一个GLKit,你在对矩阵操作的时候不再需要借助第三方也可以实现。下面的讲解会使用两方式,一种是使用GLKit一种是不使用GLKit。
我也是在学习阶段,可能里面会有错误如有错误还请各位大神指出。本博客不能算是基础教程里面介绍会少一点,如果你一点都不了解OpenGL ES可以先看这个博客然后回来再看我这里的。
学习的代码都在我的github仓库欢迎大家学习指教!
不使用GLKit的方法
代码的步骤如下:
- 创建和设置CAEAGLLayer
- 创建和设置EAGLContext
- 申请和绑定渲染缓存
- 申请和绑定帧缓存
- 编译链接着色器
- 创建和设置顶点信息
- 渲染显示顶点图形
创建项目
首先创建一个空项目,然后导入两个库
在Xcode中创建一个空项目,因为需要用到着色器我们我们要创建2个空文件一个命名为vertexShader.glsl(顶点着色器)一个命名为fragmentShader.glsl(片元着色器)。最后项目如下样子:
在着色器文件里要用着色器语言来写代码,这种语言叫OpenGL Shading Language简称glsl,其语法有点像C,如果你会C就很容易明白代码的含意不过我会尽量在代码里添加注释。我们在vertexShader.glsl文件里写入好下代码:
attribute vec4 myPosition; // 声明一个变量
void main()
{
gl_Position = myPosition; // 设置顶点位置信息
}
在fragmentShader.glsl语言里添加如下代码:
precision mediump float; // 声明精度,在顶点着色器里可以省略精度但是片元着色器里不能省略
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 为了简单这里将颜色设成固定值
}
在控制器里导入OpenGL ES的头文件:
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
然后声明几个变量
CAEAGLLayer * _myLayer; // 用于显示的layer
EAGLContext * _myContext; // 用于管理状态的context
GLuint _myFrameBuffer; // 帧缓存
GLuint _myRenderBuffer; // 渲染缓存
GLuint _myPrograme; // 用于链接着色器的程序
GLuint _myPositionSlot; // 用于向着色器传递顶点数据的槽
变量的用途上面的注释已经写清楚了。下面就是完整的代码了:
- (void)viewDidLoad {
[super viewDidLoad];
/*** 创建显示的layer ***/
_myLayer = [[CAEAGLLayer alloc] init];
_myLayer.frame = self.view.frame;
_myLayer.opaque = YES; // 设置为不透明
// 设置kEAGLDrawablePropertyRetainedBacking为NO表示不维持上一次绘制的内容
// kEAGLColorFormatRGBA8表示设置色彩空间为RGBA8
_myLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@(NO), kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
// 将创建的layer添加到视图
[self.view.layer addSublayer:_myLayer];
/*** 创建和设置context ***/
// 创建的context为OpenGL ES 2.0,因为2.0开始支持可编辑管线且大多数苹果设备都支持
_myContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (nil == _myContext) {
NSLog(@"context 创建失败");
}
// 将创建的context设置为当前context
if (![EAGLContext setCurrentContext:_myContext]) {
NSLog(@"context 设置失败");
}
/*** 申请和绑定渲染缓存和帧缓存 ***/
// 为renderbuffer申请一个id
glGenRenderbuffers(1, &_myRenderBuffer);
// 设置当前的renderbuffer为刚申请的
glBindRenderbuffer(GL_RENDERBUFFER, _myRenderBuffer);
// 为renderbuffer分配存储空间
[_myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:_myLayer];
// 为framebuffer申请一个id
glGenFramebuffers(1, &_myFrameBuffer);
// 设置当前的framebuffer为刚申请的
glBindFramebuffer(GL_FRAMEBUFFER, _myFrameBuffer);
// 将renderbuffer关联到framebuffer上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _myRenderBuffer);
/*** 编译链接着色器 ***/
[self setupPrograme];
/*** 设置顶点信息 ***/
// 创建三角形的顶点
GLfloat vertexes[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
// 设置顶点数据的指针信息
glVertexAttribPointer(_myPositionSlot, // 传入对应数据槽的位置
3, // 一组有多少数据,即3个一组
GL_FLOAT, // 数据类型
GL_FALSE, // 是否是正交视图
sizeof(GLfloat) * 3, // 数据跨度
vertexes // 顶点数据
);
// 启用顶点数据
glEnableVertexAttribArray(_myPositionSlot);
/*** 渲染显示三角形 ***/
// 设置显示区域
glViewport(0, 0, self.view.frame.size.width, self.view.frame.size.height);
// 设置清屏颜色
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
// 绘制三角开
glDrawArrays(GL_TRIANGLES, 0, 3);
// 显示三角形
[_myContext presentRenderbuffer:GL_RENDERBUFFER];
}
/**
编译和链接程序
*/
- (void)setupPrograme{
GLuint vertexShader = [self loadShader:GL_VERTEX_SHADER withFileName:@"vertexShader.glsl"];
GLuint fragmentShader = [self loadShader:GL_FRAGMENT_SHADER withFileName:@"fragmentShader.glsl"];
// 创建一个程序
_myPrograme = glCreateProgram();
if (!_myPrograme) {
NSLog(@"创建programe失败");
}
// 添加着色器到程序中并链接
glAttachShader(_myPrograme, vertexShader);
glAttachShader(_myPrograme, fragmentShader);
glLinkProgram(_myPrograme);
GLint success = 0;
// 获取程序信息
glGetProgramiv(_myPrograme, GL_LINK_STATUS, &success);
if (success == GL_FALSE) { // 程序链接失败
GLint infoLen = 0;
// 获取错误信息长度
glGetProgramiv(_myPrograme, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 0) {
// 申请内存存放错误信息
GLchar * info = malloc(sizeof(GLchar) * infoLen);
glGetProgramInfoLog(_myPrograme, sizeof(GLchar) * infoLen, &infoLen, info);
NSLog(@"%s", info);
}
// 删除链接失败的程序
glDeleteProgram(_myPrograme);
_myPrograme = 0;
return;
}
// 启用程序
glUseProgram(_myPrograme);
// 获取顶点着色器myPosition的内存地址
_myPositionSlot = glGetAttribLocation(_myPrograme, "myPosition");
}
/**
创建和编译着色器
@param type 着色器类型
@param fileName 着色器文件
@return 返回创建好的着色器,创建失败则返回0
*/
- (GLuint)loadShader:(GLenum)type withFileName:(NSString *)fileName{
// 获取文件路径
NSString * path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
NSError * error;
NSString * shaderString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
// 转成C字符串
const GLchar * shaderStringUTF8 = shaderString.UTF8String;
if (error) {
NSLog(@"文件读取错误:%@",error);
}
// 创建着色器程序
GLuint shader = glCreateShader(type);
// 给着色器程序传递着色器字符串
glShaderSource(shader, 1, &shaderStringUTF8, NULL);
// 编译
glCompileShader(shader);
// 查看编译情况
GLint compiled = 0;
// 获取着色器信息
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (compiled == GL_FALSE) {
GLint infoLen = 0;
// 获取错误信息的长度
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 0) {
GLchar * info = malloc(sizeof(GLchar) * infoLen);
glGetShaderInfoLog(shader, // 对应的着色器
sizeof(GLchar) * infoLen, // buffer的大小
&infoLen, // 传入错误长度
info); // 存放错误信息的内存
NSLog(@"着色器错误:%s", info);
free(info); // 释放内存
}
// 移除创建失败的着色器
glDeleteShader(shader);
return 0;
}
return shader;
}
显示渲染结果:
上面是控制器里主要的代码,注释已经加的很详细的就不再解释了。下面看用使用GLKit后代码的样子。
使用GLKit的方法
首先创建一个空项目和上面一样先导入2个库,然后让ViewController继承自GLKViewController。再到Main.storyboard里添加一个GLKViewController控制器并设成初始化控制器。别忘了与ViewController绑定。
接下来是代码时间,先在ViewController里声明一个属性:
@property (nonatomic,strong)GLKBaseEffect * effect;
然后我们在全局区声明一个顶点数组:
GLfloat vertexes[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
接下来就是全部主要代码了:
- (void)viewDidLoad {
[super viewDidLoad];
// 获取控制器的view
GLKView * glView = (GLKView *)self.view;
// 设置当前的context
EAGLContext * context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (nil == context) {
NSLog(@"context create failed");
}
glView.context = context;
if (![EAGLContext setCurrentContext:context]) {
NSLog(@"set currentContext failed");
}
// 创建GLKBaseEffect
self.effect = [[GLKBaseEffect alloc] init];
// 设置使用的颜色
self.effect.useConstantColor = GL_TRUE;
self.effect.constantColor = GLKVector4Make(1.0f, 0.0f, 0.0f, 1.0f);
// 设置清屏颜色
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
// 开启顶点
glEnableVertexAttribArray(GLKVertexAttribPosition);
// 设置数据指针
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, vertexes);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClear(GL_COLOR_BUFFER_BIT);
[self.effect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 3);
}
显示结果如下:
对比一下是不是用了GLKit的代码很简单!