第七节—创建一个最简单的GLKit案例
本文为L_Ares个人写作,包括图片皆为个人亲自操作,如需转载请表明原文出处。
根据前面几节可以获得的知识,下面写一个最简单的GLKit案例,将前面的GLKView
、GLKViewController
、GLKBaseEffect
练习一下,顺便也清楚一下正常的流程和方法还有属性的功能。
代码其实非常的少,就是一个纹理导入,重要的是里面的注释,因为要了解这些方法和属性都是做什么的,而且这也阐释了一个流程。
效果图贴在最后了。另外创建项目就是正常的创建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所示:
![](https://img.haomeiwen.com/i16702189/ad6ea5ef475d3296.png)
总结一下创建一个最基本的使用GLKit的案例的流程:
-
要创建一个上下文,没有上下文保存OpenGL ES的状态的话,你的绘制都是无效的。
-
设置为当前的上下文。
-
把上下文设置到GLKView对象中,因为在GLKView中我们才能完成我们想要绘制的图形的绘制与显示。
-
根据实际需求来设置缓存区和缓存区数据格式。(背景颜色的设置看你的需求。不单独作为一步,建议设置清屏颜色)。
-
初始化顶点数据。顶点数据是一定的,无论你做了什么样子的操作和封装,顶点的数据都是一定存在且需要的。
这里我们将顶点数据和纹理坐标数据放在了一起,主要是缩小一下创建缓存区时候的代码量,如果设置成两个数组分别存储,那么在开辟缓存区的时候,就要开辟两次,为顶点数据和纹理坐标数据分别创建缓存区。
除了采用数组,也可以使用结构体来存储顶点数据。
这里顺带说一下,顶点数组有人会直接写VAO,顶点缓存区直接写VBO,就是个缩写。
-
开辟顶点缓存区。
(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的代理。也就是完成绘制,并且显示出来。清理缓冲区、准备绘制、开始绘制。