OpenGL ES学习笔记之一(创建一个三角形)

我是搞iOS开发的,所以本博客会用到OC的代码作例子。OpenGL ES的接口是用C写的,对于一些矩阵的操作还需要第三方式的库,看到这里是不是整个人都不好了的感觉!我开始学的时候也是这种感觉。但有一个好消息要告诉你苹果对OpenGL ES进行的部分封装出了一个GLKit,你在对矩阵操作的时候不再需要借助第三方也可以实现。下面的讲解会使用两方式,一种是使用GLKit一种是不使用GLKit。

我也是在学习阶段,可能里面会有错误如有错误还请各位大神指出。本博客不能算是基础教程里面介绍会少一点,如果你一点都不了解OpenGL ES可以先看这个博客然后回来再看我这里的。

学习的代码都在我的github仓库欢迎大家学习指教!

不使用GLKit的方法

代码的步骤如下:

  • 创建和设置CAEAGLLayer
  • 创建和设置EAGLContext
  • 申请和绑定渲染缓存
  • 申请和绑定帧缓存
  • 编译链接着色器
  • 创建和设置顶点信息
  • 渲染显示顶点图形
创建项目

首先创建一个空项目,然后导入两个库

导入库.png

在Xcode中创建一个空项目,因为需要用到着色器我们我们要创建2个空文件一个命名为vertexShader.glsl(顶点着色器)一个命名为fragmentShader.glsl(片元着色器)。最后项目如下样子:

创建后项目的样子.png

在着色器文件里要用着色器语言来写代码,这种语言叫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;
}

显示渲染结果:

渲染结果.png

上面是控制器里主要的代码,注释已经加的很详细的就不再解释了。下面看用使用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);
}

显示结果如下:

显示结果.png

对比一下是不是用了GLKit的代码很简单!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容