OpenGL ES

第七节—创建一个最简单的GLKit案例

2020-09-17  本文已影响0人  L_Ares

本文为L_Ares个人写作,包括图片皆为个人亲自操作,如需转载请表明原文出处。

根据前面几节可以获得的知识,下面写一个最简单的GLKit案例,将前面的GLKViewGLKViewControllerGLKBaseEffect练习一下,顺便也清楚一下正常的流程和方法还有属性的功能。

代码其实非常的少,就是一个纹理导入,重要的是里面的注释,因为要了解这些方法和属性都是做什么的,而且这也阐释了一个流程。

效果图贴在最后了。另外创建项目就是正常的创建iOS项目,这个就不多说了,使用了storyboard,在里面吧view的class设置成了GLKView,不设置也可以,自己在代码里面创建一个也一样。

//
//  ViewController.m
//  01简单纹理导入
//
//  Created by EasonLi on 2020/9/14.
//  Copyright © 2020 EasonLi. All rights reserved.
//

#import "ViewController.h"
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>


@interface ViewController ()

{
    //OpenGL ES上下文。用来管理OpenGL ES的状态的。
    EAGLContext *context;
    //效果类
    GLKBaseEffect *baseEffect;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    [self setUpConfig];
    
    [self setUpVertex];
    
    [self setUpTexture];
    
    // Do any additional setup after loading the view.
}

#pragma mark - OpenGL ES设置

/**
 初始化常规的配置信息
 */
- (void)setUpConfig
{
    //初始化上下文
    /**
     EAGLContext是iOS下用来实现OpenGL ES渲染的类
     参数:
     EAGLRenderingAPI是枚举类型,有三个选项,分别对应了OpenGL ES1.0~3.0的API
     kEAGLRenderingAPIOpenGLES1 = 1,
     kEAGLRenderingAPIOpenGLES2 = 2,
     kEAGLRenderingAPIOpenGLES3 = 3,
     */
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    
    //初始化完成上下文后,可以判断一下上下文是否创建成功
    if (!context) {
        NSLog(@"OpenGL ES context init failure");
        return;
    }
    
    //设置当前上下文
    [EAGLContext setCurrentContext:context];
    
    //获取GLKView
    GLKView *view = (GLKView *)self.view;
    //设置GLKView的上下文
    view.context = context;
    
    //配置视图创建的渲染缓存区
    /**
     1.颜色缓存区:
     
     颜色缓存区是OpenGL ES中存储将在屏幕上要显示的颜色的内存区,你可以利用下面的属性设置颜色缓存区中每个
     像素的颜色格式。
     
     drawableColorFormat : 颜色缓存区的颜色数据格式
     可选枚举值如下:
     
     <1>.GLKViewDrawableColorFormatRGBA8888 : 颜色缓存区中,每个像素都是占8位的,每个像素又是RGBA格式,
     也就是4个字节,所以这种格式设置图片,图片会占用32bit内存。
     
     <2>.GLKViewDrawableColorFormatRGB565 : 如果允许更小范围的颜色,可以用这个。性能消耗小,内存占的小。
     
     <3>.GLKViewDrawableColorFormatSRGBA8888 : sRGBA本身就是RGBA的一种特定情况,色域有限,谨慎选择。
     
     2.深度缓存区:
     
     存储像素点的深度值。
     
     drawableDepthFormat : 深度缓存区的格式或者说大小
     
     <1>.GLKViewDrawableDepthFormatNone : 完全没有深度缓存区
     <2>.GLKViewDrawableDepthFormat16 : 16位深度值
     <3>.GLKViewDrawableDepthFormat24 : 24位深度值
     
     <2>比<3>消耗的资源更少。
     */
    view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
    
    //设置背景颜色
    glClearColor(0.3f, 0.3f, 0.3f, 1.f);
    
}

/**
 设置纹理信息
 */
- (void)setUpTexture
{
    //获取图片路径
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"要导入的纹理图" ofType:@"png"];
    
    //设置纹理参数
    //这里是把纹理坐标的原点设置到左下角,因为view默认的坐标原点是左上角
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
    
    //纹理信息获取
    GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
    
    //使用GLKit中的GLKBaseEffect完成着色器工作,包括了顶点着色器和片元着色器工作
    //初始化
    baseEffect = [[GLKBaseEffect alloc] init];
    //开启第一个纹理的使用权限
    baseEffect.texture2d0.enabled = YES;
    //告诉第一个纹理,要使用的是哪个纹理信息
    baseEffect.texture2d0.name = textureInfo.name;
    
    
}

/**
 设置顶点信息
 */
- (void)setUpVertex
{
    
/***************************************设置顶点数据**********************************************/
    //设置顶点的坐标(顶点坐标和纹理坐标)
    /**
     每一行的前三个值是顶点的(x,y,z)坐标,后面的是纹理坐标(s,t)
     
     可以使用两个数组来存储,但是下面开辟顶点缓冲区的时候,就要记得创建两个标识符,copy两个数组都到顶端缓冲区里
     另外,纹理坐标的取值范围是[0,1],而且和view的原点是不同的,view的原点在左上角,纹理坐标的原点则以左下角为准
     
     图片是矩形的,也就是利用两个三角形图元组成的,和OpenGL是一样的。
     */
    GLfloat vertexArr[] =
    {
        //第一个三角形
        0.5f,-0.5f,0.f,   1.f,0.f,   //右下
        0.5f,0.5f,0.f,    1.f,1.f,   //右上
        -0.5f,0.5f,0.f,   0.f,1.f,   //左上

        //第二个三角形
        0.5f,-0.5f,0.f,   1.f,0.f,   //右下
        -0.5f,0.5f,0.f,   0.f,1.f,   //左上
        -0.5f,-0.5f,0.f,  0.f,0.f    //左下
    };
   
    
/***********************************************************************************************/
    
/****************************************开辟顶点缓冲区********************************************/
    
    //开辟顶点缓存区
    /**
     关于为什么要开辟顶点缓存区:
     顶点数组本身是存储在内存当中的,你可以设置函数指针,在绘制的时候,顶点数据是直接从内存中传入
     而顶点缓存区则是提前开辟了一块显存空间,先将顶点数据提前传入显存中,由显存传入顶点数据是比内存快的。
    这块提前开辟的显存空间也就是顶点缓存区
     */
    
    //首先要创建顶点缓存区的标识符bufferID,是无符号整型数据
    GLuint bufferID;
    //根据bufferID分配顶点缓存区
    //参数:
    //(1). 你要几个缓存区
    //(2). 标识符地址
    glGenBuffers(1, &bufferID);
    //绑定顶点缓存区和标识符
    //参数:
    //(1). 这个标识符要绑定到哪个缓存区
    //(2). 缓存区ID
    glBindBuffer(GL_ARRAY_BUFFER, bufferID);
    //将顶点数组的数据copy到顶点缓存区中
    //参数:
    //(1). 要把数据copy到哪个缓存区
    //(2). 顶点数据的大小
    //(3). 顶点数据地址,也就是顶点数组的首地址,也就是你的数组名字
    //(4). 绘制方式标签,静态绘制,图片是拿来绘制的,如果顶点数据不总修改,静态就行。
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexArr), vertexArr, GL_STATIC_DRAW);
    
/***********************************************************************************************/
    
/*************************************打开需要用到的读取通道****************************************/
    
    //顶点坐标
    //打开读取通道
    /**
     在iOS的平台下,所有的顶点着色器的属性通道(Attibute通道)默认都是关闭的,也就是说,你不打开通道的话,这个
     顶点数据在你的服务端(着色器端)都是没办法使用的。
     数据能不能在GPU端(服务端)可见,或者说着色器能不能访问到数据,都是由是否启用了对应的通道决定的,不开启就
     找不到对应的数据。
     所以,我们必须自己打开通道,指定访问属性,才可以让顶点着色器能够访问从CPU中copy到GPU的顶点数据。
     打开通道就是用glEnableVertexAttribArray来完成。
     而且,要传的是顶点坐标的话,就要用GLKVertexAttribPosition,如果是GLSL,你随便取名,但是GLKit传顶点
     坐标的通道就是叫GLKVertexAttribPosition
     */
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    
    //设置合适的方式从缓存区里面读取顶点数据,因为你顶点数组里面数据乱七八糟的,GLKit要知道哪些数据是一起来确定一个
    //顶点属性的。
    //参数:
    //(1). index: 指定要修改的顶点属性的索引值
    //(2). size:  每个顶点属性的组成数量,比如顶点坐标是(x,y,z)组成的,也即是3个值决定它的位置。
    //            颜色是rgba组成的,就是4个,纹理是st组成的,也就是2个
    //(3). type:  顶点属性的数据类型是什么,初始值是GL_FLOAT。常见的:GL_BYTE, GL_UNSIGNED_BYTE,
    //            GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED
    //(4). normalized: 顶点数据被访问时,固定点的数据值是否要归一化。就是要不要变成标准化向量,GL_FALSE就是
    //                 直接用固定点值的方式就可以。
    //(5). stride: 连续顶点属性之间的偏移量,就是连续两个相同的顶点属性之间,差多少位数据。
    //             默认是0,就是默认是顶点属性值都连接在一起的。
    //(6). *ptr:  指针要指向顶点数组中第一个顶点属性的第一个组件,默认是0
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
    
    //纹理坐标
    //打开纹理坐标的属性通道
    //就一张图,就选1,打开两个的话就把1也打开,超过2个就自己去写,用不了GLKit了
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    //设置合适的从纹理通道读取缓存区纹理顶点数据的方式
    //也是间隔5位才到第二个连续的纹理顶点数据,头一个纹理组件是在x,y,z后面,所以要+3
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
    
}

#pragma mark - GLKView Delegate
/**
 GLKView对象自己的上下文属性设置成OpenGL ES的上下文了,并且也把framebuffer(缓存区)绑定成了OpenGL ES呈现代码命令
 所要绘制效果的目标画布,所以要实现GLKView的代理来完成绘制。
 */
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    
    //绘制前要清空缓冲区
    glClear(GL_COLOR_BUFFER_BIT);
    
    //一定要做这步准备绘制
    [baseEffect prepareToDraw];
    
    //设置绘制方式
    //参数:
    //(1). 用哪种图元进行绘制
    //(2). 先从哪个顶点开始绘制
    //(3). 要绘制几个顶点
    glDrawArrays(GL_TRIANGLES, 0, 6);
    
}


@end

效果图如图1.1所示:

1.1.png

总结一下创建一个最基本的使用GLKit的案例的流程:

  1. 要创建一个上下文,没有上下文保存OpenGL ES的状态的话,你的绘制都是无效的。

  2. 设置为当前的上下文。

  3. 把上下文设置到GLKView对象中,因为在GLKView中我们才能完成我们想要绘制的图形的绘制与显示。

  4. 根据实际需求来设置缓存区和缓存区数据格式。(背景颜色的设置看你的需求。不单独作为一步,建议设置清屏颜色)。

  5. 初始化顶点数据。顶点数据是一定的,无论你做了什么样子的操作和封装,顶点的数据都是一定存在且需要的。

这里我们将顶点数据和纹理坐标数据放在了一起,主要是缩小一下创建缓存区时候的代码量,如果设置成两个数组分别存储,那么在开辟缓存区的时候,就要开辟两次,为顶点数据和纹理坐标数据分别创建缓存区。

除了采用数组,也可以使用结构体来存储顶点数据。

这里顺带说一下,顶点数组有人会直接写VAO,顶点缓存区直接写VBO,就是个缩写。

  1. 开辟顶点缓存区。
    (1). 创建一个顶点缓存区的标识符ID。代表着这个顶点缓存区的身份信息。

    (2). 你需要几个缓存区,并且把标识符写入,让系统知道你要为哪些缓存区设置空间。

    (3). 把缓存区和标识符ID绑定起来,每个ID对应好自己的显存空间。

    (4). 把数组中的全部数据都copy到顶点缓存区里面,数据就由内存存储变为了多了一份显存存储,显存可以更高效的访问到顶点数据。

    (5). 打开属性通道。因为iOS平台为了更好的性能,默认关闭了所有的Attribute(属性)通道,如果不开启的话,你没办法通过Attribute访问到需要经过属性通道传递的数据。
    这个通道不是我们来定义的,是由GLKit框架定义好的枚举值,根据自身的需求开启对应的通道即可。

    (6). 设置OpenGL ES在读取顶点数据的时候,是以怎样的方式读取的。

    (7). 设置OpenGL ES在读取纹理坐标的时候,是以怎样的方式读取的。

    (8). 获取对应的纹理路径。

    (9). 解决纹理翻转问题。

    (10). 把从路径上获取到的纹理转换为GLTextureInfo

    (11). 利用GLBaseEffect完成顶点和片元着色器的工作。

    (12). 实现GLKView的代理。也就是完成绘制,并且显示出来。清理缓冲区、准备绘制、开始绘制。

上一篇 下一篇

猜你喜欢

热点阅读