图片渲染
- 新建一个ViewController继承自GLKViewController
- 把这个控制器设置为根视图,view改成基础GLKView
- 导入相应头文件
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>
setUpConfig
初始化上下文&设置当前上下文
- 使用
OpenGL
做任何事前,你都需要先创建一个EAGLContext
。 -
iOS
使用OpenG
L进行绘制时需要一些信息,这些信息都由EAGLContext
管理。这和你使用一个Core Graphics
上下文差不多。 - 当你创建一个上下文时,你需要定义要使用的
API
版本。
EAGLContext
是苹果iOS
平台下实现OpenGLES
渲染层.
1. `kEAGLRenderingAPIOpenGLES1` = 1, 固定管线
2. `kEAGLRenderingAPIOpenGLES2` = 2,
3. `kEAGLRenderingAPIOpenGLES3` = 3,
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
设置当前上下文
[EAGLContext setCurrentContext:context];
获取GLKView
& 设置context
- 这创建了一个新的
GLKView
的实例,并使它和整个window
一样大。 - 当你创建一个
GLKView
的时候,你需要告诉它要使用的OpenGL
上下文,所以配置成我们刚刚创建的context
。
GLKView *view = (GLKView *)self.view; view.context = context;
配置视图创建的渲染缓存区
- drawableColorFormat: 颜色缓存区格式
- 简介:
OpenGL ES
有一个缓存区,它用以存储将在屏幕中显示的颜色。你可以使用其属性来设置缓冲区中的每个像素的颜色格式。 -
GLKViewDrawableColorFormatRGBA8888
:此值为默认值,缓存区的每个像素的最小组成部分(RGBA)使用8个bit,(所以每个像素4个字节,4*8个bit)。 -
GLKViewDrawableColorFormatRGB565
:如果你的APP允许更小范围的颜色,即可设置这个。会让你的APP
消耗更小的资源(内存和处理时间)
-
drawableDepthFormat
(深度缓存区格式):如果你要使用这个属性(一般用于3D
游戏),你应该选择GLKViewDrawableDepthFormat16
或GLKViewDrawableDepthFormat24
。这里的差别是使用GLKViewDrawableDepthFormat16
将消耗更少的资源。
-
GLKViewDrawableDepthFormatNone
= 0:意味着完全没有深度缓冲区 GLKViewDrawableDepthFormat16
GLKViewDrawableDepthFormat24
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
设置背景颜色
定义用来清理屏幕的RGB颜色
和alpha
(透明度)值。这里我们设置红色
。
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
setUpVertexData
准备数据
四边形是有两个三角形组成.
GLfloat vertexData[] = {
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
0.5, 0.5, 0.0f, 1.0f, 1.0f, //右上
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
0.5, -0.5, 0.0f, 1.0f, 0.0f, //右下
-0.5, 0.5, 0.0f, 0.0f, 1.0f, //左上
-0.5, -0.5, 0.0f, 0.0f, 0.0f, //左下
};
绑定数据
创建顶点缓存区标识符ID
函数原型:
void glGenBuffers(GLsizei n,GLuint * buffers)
;
第一个参数
:要生成的缓冲对象的数量
第二个参数
:要输入用来存储缓冲对象名称的数组
如果使用数组保存
GLuint bufferID[3];
glGenBuffers(3,bufferID);
- 单个
GLuint
数据
GLuint bufferID;
glGenTextures(1, &bufferID);
绑定顶点缓存区(明确作用)
函数原型:
void glBindBuffer(GLenum target,GLuint buffer)
;
第一个参数
:缓冲对象的类型
第二个参数
:要绑定的缓冲对象的名称
- 使用该函数将缓冲对象绑定到
OpenGL
上下文环境中以便使用。 - 如果把
target
绑定到一个已经创建好的缓冲对象,那么这个缓冲对象将为当前target
的激活对象。 - 但是如果绑定的
buffer
值为0
,那么OpenGL
将不再对当前target
使用任何缓存对象。
glBindTexture(GL_ARRAY_BUFFER, bufferID);
OpenGL
允许我们同时绑定多个缓冲类型,只要这些缓冲类型是不同的,换句话说,同一时间,不能绑定两个相同类型的缓冲对象。也可以理解为对于一个类型来说,同一时间只能“激活”一个类型,否则就会发生“矛盾”
。
第二个参数虽然是GLuint
类型的,但是你万万不能直接指定个常量比如说0
,否则会出现GL_INVALID_VALUE
的错误,具体如下:
GL_INVALID_VALUE is generated if buffer is not a name previously returned form a call to glGenBuffers
将顶点数组的数据copy
到顶点缓存区中(GPU
显存中)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
插槽重识(打开读取通道)
GLKit
目标就是简化, 里面有很多对OpenGLES
的封装, 我们也可以不用进行shader
的编辑, 不用在写glsl
就能完成shader
的绘制
GLKVertexAttribPosition
就是position
位置插槽,
GLKVertexAttribColor
就是color
位置插槽,
GLKVertexAttribTexCoord0
就是texcoord0
位置插槽,
-
glEnableVertexAttribArray
:在iOS
中, 默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute
)变量都是关闭的。
意味着,顶点数据在着色器端(服务端)是不可用的。即使你已经使用
glBufferData
方法,将顶点数据从内存拷贝到顶点缓存区中(GPU
显存中)。
所以, 必须由
glEnableVertexAttribArray
方法打开通道。指定访问属性,才能让顶点着色器能够访问到从CPU
复制到GPU
的数据。
注意: 数据在
GPU
端是否可见,即,着色器能否读取到数据,由是否启用了对应的属性决定,这就是glEnableVertexAttribArray
的功能,允许顶点着色器读取GPU
(服务器端)数据。
- glVertexAttribPointer
函数原型:
glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)
功能:上传顶点数据到显存的方法(设置合适的方式从buffer里面读取数据)
参数列表:
-
index
:指定要修改的顶点属性的索引值 -
size
:每次读取数量。(如position
是由3个(x,y,z
)组成,而颜色是4个(r,g,b,a
),纹理则是2个.) -
type
:指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和GL_FLOAT,初始值为GL_FLOAT
。
4.normalized
:指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE
)或者直接转换为固定点值(GL_FALSE
) -
stride
:指定连续顶点属性之间的偏移量。如果为0
,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0
-
ptr
:指定一个指针,指向数组中第一个顶点属性的第一个组件。初始值为0
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
- (GLfloat *)NULL null值包装为0
- (GLfloat *)NULL + 3 起始位置
- 乘以5直接间隔
setUpTexture
纹理矩阵
- 获取纹理图片路径
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"test" ofType:@"jpg"];
- 设置纹理参数
由于纹理坐标系是跟手机显示的Quartz 2D坐标系的y轴正好相反,纹理坐标系使用左下角为原点,往
上为y轴的正值
,往右是x轴的正值
,所以需要设置一下GLKTextureLoaderOriginBottomLeft
。
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
- 使用苹果
GLKit
提供GLKBaseEffect
完成着色器工作(顶点/片元)
eEffect = [[GLKBaseEffect alloc] init];
eEffect.texture2d0.enabled = GL_TRUE;
eEffect.texture2d0.name = textureInfo.name;
- 绘制
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClear(GL_COLOR_BUFFER_BIT);
[eEffect prepareToDraw];
glDrawArrays(GL_TRIANGLES, 0, 6);
}
效果图
下来做一个立方体的贴图,并旋转.
准备工作
立方体和单个四边形区别,多了
5
个面,定点数也有原来的6
个变成36个.
- 首先创建需要的类已经一个
OPVertex
来存放顶点和纹理顶点.
ypedef struct OPVertex{
GLKVector3 positionCoord;//顶点坐标
GLKMatrix2 texturecoord; //纹理
}OPVertex;
//总共顶点数
static int const kCoordCount = 36;
EAGLContext *context; //上下文
GLKBaseEffect *cEffect;// 着色器
NSInteger angle; //记录旋转的角度
GLKView *glkView; //glkView
@property(nonatomic, assign) OPVertex *vertices;
绘制
- 初始化上下文&设置当前上下文,设置渲染区和背景颜色
这一步和之前绘制没有变化
-(void)setUpConfig
{
context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
//判断context是否创建成功
if (!context) {
NSLog(@"Create ES context Failed");
}
//设置当前上下文
[EAGLContext setCurrentContext:context];
//2.获取GLKView & 设置context
GLKView *view =(GLKView *) self.view;
view.context = context;
//3.配置视图创建的渲染缓存区.
view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
glkView = view;
//4.设置背景颜色
glClearColor(1, 0, 0, 1.0);
}
- 加载顶点/纹理坐标数据
- 使用
malloc
分配内存
self.vertices = malloc(sizeof(OPVertex) * kCoordCount);
- 6个面对应的顶点和纹理坐标
// 前面
self.vertices[0] = (OPVertex){{-0.5, 0.5, 0.5}, {0, 1}};
self.vertices[1] = (OPVertex){{-0.5, -0.5, 0.5}, {0, 0}};
self.vertices[2] = (OPVertex){{0.5, 0.5, 0.5}, {1, 1}};
self.vertices[3] = (OPVertex){{-0.5, -0.5, 0.5}, {0, 0}};
self.vertices[4] = (OPVertex){{0.5, 0.5, 0.5}, {1, 1}};
self.vertices[5] = (OPVertex){{0.5, -0.5, 0.5}, {1, 0}};
// 上面
self.vertices[6] = (OPVertex){{0.5, 0.5, 0.5}, {1, 1}};
self.vertices[7] = (OPVertex){{-0.5, 0.5, 0.5}, {0, 1}};
self.vertices[8] = (OPVertex){{0.5, 0.5, -0.5}, {1, 0}};
self.vertices[9] = (OPVertex){{-0.5, 0.5, 0.5}, {0, 1}};
self.vertices[10] = (OPVertex){{0.5, 0.5, -0.5}, {1, 0}};
self.vertices[11] = (OPVertex){{-0.5, 0.5, -0.5}, {0, 0}};
// 下面
self.vertices[12] = (OPVertex){{0.5, -0.5, 0.5}, {1, 1}};
self.vertices[13] = (OPVertex){{-0.5, -0.5, 0.5}, {0, 1}};
self.vertices[14] = (OPVertex){{0.5, -0.5, -0.5}, {1, 0}};
self.vertices[15] = (OPVertex){{-0.5, -0.5, 0.5}, {0, 1}};
self.vertices[16] = (OPVertex){{0.5, -0.5, -0.5}, {1, 0}};
self.vertices[17] = (OPVertex){{-0.5, -0.5, -0.5}, {0, 0}};
// 左面
self.vertices[18] = (OPVertex){{-0.5, 0.5, 0.5}, {1, 1}};
self.vertices[19] = (OPVertex){{-0.5, -0.5, 0.5}, {0, 1}};
self.vertices[20] = (OPVertex){{-0.5, 0.5, -0.5}, {1, 0}};
self.vertices[21] = (OPVertex){{-0.5, -0.5, 0.5}, {0, 1}};
self.vertices[22] = (OPVertex){{-0.5, 0.5, -0.5}, {1, 0}};
self.vertices[23] = (OPVertex){{-0.5, -0.5, -0.5}, {0, 0}};
// 右面
self.vertices[24] = (OPVertex){{0.5, 0.5, 0.5}, {1, 1}};
self.vertices[25] = (OPVertex){{0.5, -0.5, 0.5}, {0, 1}};
self.vertices[26] = (OPVertex){{0.5, 0.5, -0.5}, {1, 0}};
self.vertices[27] = (OPVertex){{0.5, -0.5, 0.5}, {0, 1}};
self.vertices[28] = (OPVertex){{0.5, 0.5, -0.5}, {1, 0}};
self.vertices[29] = (OPVertex){{0.5, -0.5, -0.5}, {0, 0}};
// 后面
self.vertices[30] = (OPVertex){{-0.5, 0.5, -0.5}, {0, 1}};
self.vertices[31] = (OPVertex){{-0.5, -0.5, -0.5}, {0, 0}};
self.vertices[32] = (OPVertex){{0.5, 0.5, -0.5}, {1, 1}};
self.vertices[33] = (OPVertex){{-0.5, -0.5, -0.5}, {0, 0}};
self.vertices[34] = (OPVertex){{0.5, 0.5, -0.5}, {1, 1}};
self.vertices[35] = (OPVertex){{0.5, -0.5, -0.5}, {1, 0}};
- 开辟顶点缓存区
GLuint bufferID;
glGenBuffers(1, &bufferID);
//(2).绑定顶点缓存区.(明确作用)
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
//(3).将顶点数组的数据copy到顶点缓存区中(GPU显存中)
glBufferData(GL_ARRAY_BUFFER, sizeof(OPVertex) * kCoordCount, self.vertices, GL_STATIC_DRAW);
// 顶点坐标数据
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(OPVertex) , offsetof(OPVertex, positionCoord) + NULL);
//纹理坐标数据
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(OPVertex), offsetof(OPVertex, texturecoord) + NULL);
sizeof(OPVertex) * kCoordCount
传入内容的大小
offsetof(OPVertex, positionCoord) + NULL
,对应顶点或者纹理起始点的内存偏移值
- 加载纹理数据
{
//1.获取纹理图片路径
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"test" ofType:@"jpg"];
//2.设置纹理参数
//纹理坐标原点是左下角,但是图片显示原点应该是左上角.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
//3.使用苹果GLKit 提供GLKBaseEffect 完成着色器工作(顶点/片元)
cEffect = [[GLKBaseEffect alloc]init];
cEffect.texture2d0.enabled = GL_TRUE;
cEffect.texture2d0.name = textureInfo.name;
cEffect.texture2d0.target = textureInfo.target;
[self setTimer];
}
cEffect.texture2d0.target = textureInfo.target;
将目标对象交给cEffect
;
[self setTimer];
开启一个定时器
- 开始定时器
- (void)setTimer{
NSTimer* timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
[self transforms];
}];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)transforms{
angle = (angle + 10) % 360;
//使用模型矩阵
cEffect.transform.modelviewMatrix = GLKMatrix4MakeRotation(GLKMathDegreesToRadians(angle), 0.3, 1, -1.7);
//重新绘制
[glkView display];
}
- 绘制
#pragma mark -- GLKViewDelegate
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
//1.清屏
glClear(GL_COLOR_BUFFER_BIT);
//2.准备绘制
[cEffect prepareToDraw];
//3.开始绘制
glDrawArrays(GL_TRIANGLES, 0, 36);
}
这里
36
绘制的定点数.
效果图
:
总结
-
OpenGL ES 相关初始化
- 初始化上下文&设置当前上下文
- 获取GLKView & 设置context
- 配置视图创建的渲染缓存区
- 设置背景颜色
-
加载顶点/纹理坐标数据
- 设置顶点数组(顶点坐标,纹理坐标)
- 开辟顶点缓存区
- 创建顶点缓存区标识符ID
- 绑定顶点缓存区.(明确作用)
- 将顶点数组的数据copy到顶点缓存区中(GPU显存中)
- 打开读取通道.
- 上传顶点数据到显存的方法
-
加载纹理数据(使用GLBaseEffect)
- 获取纹理图片路径
- 设置纹理参数
- 使用苹果GLKit 提供GLKBaseEffect 完成着色器工作(顶点/片元)
-
绘制视图的内容(GLKViewDelegate)
- 清屏
- 准备绘制
- 开始绘制
GLKBaseEffect使用小结:
- 创建一个
GLKBaseEffect
。通常当你创建OpenGL
上下文的时候,你都会创建一个。对于不同的几何图形,你可以(或者说应该)重用相同的GLKBaseEffect
,只是要重新设置其属性。在后台,GLKBaseEffect
只会向它的着色器传播刚刚变化的属性。 - 设置
GLKBaseEffect
的属性。这里你可以配置光,转化,和其他GLKBaseEffect
的着色器用来渲染几何图形的属性。 - 调用G
LKBaseEffect
的prepareToDraw
方法。任何时候如果你改变了GLKBaseEffect
的一个属性,你都要先调用prepareToDraw
方法让着色器得到正确的配置。这也让GLKBaseEffect
的着色器作为“当前”的着色器程序(就是让GLKBaseEffect
的着色器起作用)。 - 使能预定义属性。通常当你使用自定义着色器时,他们会使用称为属性的参数,你要写代码获得它们的
ID
。对于GLKBaseEffect
的内建着色器,这些属性已经用常量值预先定义好了,比如GLKVertexAttribPosition
或者GLKVertexAttribColor
。所以你要使能所有需要传给着色器的参数,并保证他们指向了数据。 - 绘制你的几何图形。一旦你东西要配置,你都可以使用普通的
OpenGL
绘图指令,比如glDrawArrays
或者glDrawElements
,它会使用你刚刚配置好的效果进行渲染!
GLKBaseEffect
的好处是如果你使用它们,你根本就不需要去写任何的着色器,