iOS图形处理相关UI效果移动端设计研发

OpenGL ES:新手村中的HelloWorld

2016-10-08  本文已影响2028人  神经骚栋

前言


对于OpenGL ES,本人现在还是一个小白,所以我将用小白视角对OpenGL ES进行小白式的讲解.希望能通过如此帮助更多的人.同时我要感谢一个人,那就是落影loyinglin,落影大神关于OpenGL ES方面的知识写的非常的详细,大家可以去参考.好了,开始战斗吧.

OpenGL ES简介


OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计。该API由Khronos集团定义推广,Khronos是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准。

OpenGL ES 是从 OpenGL 裁剪的定制而来的,去除了glBegin/glEnd,四边形(GL_QUADS)、多边形(GL_POLYGONS)等复杂图元等许多非绝对必要的特性。经过多年发展,现在主要有两个版本,OpenGL ES 1.x 针对固定管线硬件的,OpenGL ES 2.x 针对可编程管线硬件。OpenGL ES 1.0 是以 OpenGL 1.3 规范为基础的,OpenGL ES 1.1 是以 OpenGL 1.5 规范为基础的,它们分别又支持 common 和 common lite两种profile。lite profile只支持定点实数,而common profile既支持定点数又支持浮点数。 OpenGL ES 2.0 则是参照 OpenGL 2.0 规范定义的,common profile发布于2005-8,引入了对可编程管线的支持。

那么上面说了这么一些到底是什么意思呢.其实就是说OpenGL ES是移动端处理图像的一个C语言库.

OpenGL ES的显示图像


在iOS中,我们平常要加载一张图片会怎么做呢?一个是使用UIKit框架的UIImage,一个是使用Core Graphics框架直接绘制.那么OpenGL ES是如何展现图像的呢?今天我们就先用OpenGL ES中的GLKBaseEffect来展现图像.实现效果如下所示.


HelloWorld的实现过程


一、 准备工作

</b>
为了简便省时,我决定直接在ViewController中修改代码,首先我们先导入GLKit.h头文件,紧接着那个将ViewController的类型修改为GLKViewController.

然后在Main.storyboard修改ViewController中view的类型为GLKView.如图所示.

上面的准备工作已经是做完了,那么接下来,就是正题部分了,我们现在ViewController.m中声明两个属性.一个是OpenGL ES 上下文属性的EAGLContext对象,一个是矩阵相关的GLKBaseEffect对象.

@interface ViewController ()

@property(nonatomic,strong)EAGLContext *mContext;

@property(nonatomic,strong)GLKBaseEffect *mEffect;

@end

通过官方的API文档,我们知道,EAGLContext对象管理一个OpenGL ES渲染环境状态信息,命令,以及使用OpenGL ES的所需要资源。OpenGL ES执行任何命令之前,都需要通过EAGLContext对象来实现。同时官方文档也提到,绘制一个上下文之前,你必须完成framebuffer对象绑定到上下文。

GLKBaseEffect这个类实现了OpenGL ES 1.0公共(common)的shading行为,简化(从1.0)到OpenGL ES 2.0的转化。它们也提供了让光和纹理(lighting and texturing)工作的简单方法。GLKBaseEffect对象提供了着色器这一功能.其实着色器应该算的上是OpenGL ES的一大特色,但是GLKBaseEffect已经包含了这一功能,相当于封装了着色器.现在只是知道有着色器就行.

</b>
接下来,我们了解两个方法,他们的刷新频率和屏幕的刷新频率是一致的.我们需要在- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect这个方法中进行渲染操作.

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;

-(void)update;

</b>

二、ViewDidLoad的配置工作

</b>
我们接下来在ViewDidLoad中需要做以下几个工作.

1、配置OpenGL ES 上下文信息
    self.mContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
    GLKView *view = (GLKView *)self.view;
    
    view.context = self.mContext;
    
    //颜色缓冲区格式
    view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
    
    [EAGLContext setCurrentContext:self.mContext];

代码讲解:第一行代码是对控制器自身的EAGLContext对象使用- (instancetype) initWithAPI:(EAGLRenderingAPI) api;进行初始化.EAGLRenderingAPI是一个枚举类型.包含了三个值,分别代表着1.0、2.0和3.0的OpenGL ES,我们现在使用的OpenGL ES2.0,所以选择的是kEAGLRenderingAPIOpenGLES2;

typedef NS_ENUM(NSUInteger, EAGLRenderingAPI)
{
    kEAGLRenderingAPIOpenGLES1 = 1,
    kEAGLRenderingAPIOpenGLES2 = 2,
    kEAGLRenderingAPIOpenGLES3 = 3,
};

第二行和第三行代码则是把当前控制器的View的context设置为self.mContext.

第四行代码则是设置页面的颜色缓冲区格式,默认的也是GLKViewDrawableColorFormatRGBA8888,所以不做设置也可以.

第五行代码则是将此“EAGLContext”实例设置为OpenGL的“当前激活”的“Context”。这样,以后所有“GL”的指令均作用在这个“Context”上。

2、配置绘制矩阵数组信息

OpenGL ES的坐标系是和iOS常用的Quartz 2D坐标系是不一样的.OpenGL ES是左手坐标系(待议~),Quartz 2D坐标系则是右手坐标系.OpenGL ES的坐标系是以中心为原点,原点到屏幕的边缘为单位1(不管屏幕尺寸如何变化,都是单位1).OpenGL ES的坐标系如下所示.

OpenGL ES的坐标系

接下来,我们创建顶点数组,数组中的元素类型为GLfloat类型.数组中包含了两个坐标一个是顶点坐标(x,y,z轴信息),一个是纹理坐标(x,y信息),注意,顶点要与纹理的点一一对应.关于纹理相关只是可以参考我在SpriteKit的文集中的SpriteKit框架之SKTextureAtlas第一部分内容.代码如下所示.

(PS:问什么要这么创建数组呢?难道是系统规定的?回答:并不是,数组的形式规则定好之后,我们可以按照一定的规律取出对应的数据元素.然后进行操作.)

        //顶点数据,前三个是顶点坐标,后面两个是纹理坐标
    GLfloat squareVertexData[] =
    {
        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, //左下
        0.5, 0.5, -0.0f,    1.0f, 1.0f, //右上
    };

上面的顶点坐标组成看似是一个正方形,但是实际上真的如此吗?NONONO,事实上,由于屏幕的宽高不相同的原因.所选择区域会是下面的这个样子的.

</b>
对于初学者还有个容易忽视的技术点,那就是 在OpenGL ES只能绘制三角形,不能绘制多边形,但是在OpenGL中确实可以直接绘制多边形.
那么我们改如何绘制一个矩形呢?我们可以认为一个矩形是两个三角形拼接而成的.这时候,我们就需要整出另外一个东西:那就是顶点索引数组.有了它就可以规定绘制的顺序.如下所示.

  //顶点索引
    GLuint indices[] ={ 
      0, 1, 2,
      1, 3, 0
     };

绘制过程如图所示.先是做下三角形,再是右上三角形.


绘制顺序图示
3.将顶点数据和顶点索引数据写入通用的顶点属性存储区 (重点核心部分❗️❗️❗️)

其实我一直没有理解"通用"这个词(写这篇博客写到最后竟然理解了,因为顶点属性集中包含五种属性:位置、法线、颜色、纹理0,纹理1,所以只能用"通用"这个词了).那么把顶点数据和顶点索引数据写入通用的顶点属性存储区,是怎么样的过程呢?首先将顶点数据和顶点索引数据保存进GUP的一个缓冲区中,然后再按一定规则,将数据取出,复制到各个通用顶点属性中.

那么接下来,我们先将顶点数组保存进GPU缓冲区中.代码如下所示.

    GLuint buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(squareVertexData), squareVertexData, GL_STATIC_DRAW);

然后就是把顶点索引数组写进GPU缓冲区中.

    GLuint index;
    glGenBuffers(1, &index);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    
函数说明:


</b>
然后,将GPU缓冲区的顶点数据复制进通用顶点属性中.注意:索引数据不需要复制到通用顶点属性中.具体代码如下.

    //顶点数据
    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);
函数说明:

typedef NS_ENUM(GLint, GLKVertexAttrib)
{
    GLKVertexAttribPosition,
    GLKVertexAttribNormal,
    GLKVertexAttribColor,
    GLKVertexAttribTexCoord0,
    GLKVertexAttribTexCoord1
} NS_ENUM_AVAILABLE(10_8, 5_0);

</b>


4.将图片纹理赋值给GLKBaseEffect对象

本文的前面我就提到了图片纹理和纹理集,纹理集最好的好处就是节省内存,具体看我以前写的博客,上面有提到,这里就不啰嗦了.在OpenGL ES也是有纹理(GLKTextureInfo)这一概念,应该说Sprite Kit框架就是封装的OpenGL ES😆.下面我们先把图片加载到纹理对象中.

    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"jpg"];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:@(1),GLKTextureLoaderOriginBottomLeft, nil];
    
    GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil ];

然后创建GLKBaseEffect对象并且开启它的可编辑状态,然后把纹理赋值给GLKBaseEffect对象.

    self.mEffect = [[GLKBaseEffect alloc]init];
    
    self.mEffect.texture2d0.enabled = GL_TRUE;

    self.mEffect.texture2d0.name = textureInfo.name;

</b>

三、渲染场景

</b>
可能没接触过Sprite Kit的童靴不太了解场景(Scene),你可以理解为是绘制图层,当然了,这个绘制的频率是跟屏幕刷新频率是一致的(默认的).在GLKit框架中,GLKView对象是完全不需要做任何操作的,只要在控制器中执行- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect-(void)update这两个方法就可以在GLKView对象上显示图像了.一般我们把渲染代码写在- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect.如下所示.

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    glClearColor(0.3f, 0.6f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //启动着色器
    [self.mEffect prepareToDraw];
    glDrawElements(GL_TRIANGLES, self.mCount, GL_UNSIGNED_INT, 0);
    
}

函数说明:

GL_COLOR_BUFFER_BIT: 当前可写的颜色缓冲

GL_DEPTH_BUFFER_BIT: 深度缓冲

GL_ACCUM_BUFFER_BIT: 累积缓冲

GL_STENCIL_BUFFER_BIT: 模板缓冲

[self.mEffect prepareToDraw];这个就是启动当前GLKBaseEffect对象.

GL_POINTS:  单独的将顶点画出来。

GL_LINES:  单独地将直线画出来。

GL_LINE_STRIP:  连贯地将直线画出来。

GL_LINE_LOOP:  连贯地将直线画出来。行为和GL_LINE_STRIP类似,但是会自动将最后一个顶点和第一个顶点通过直线连接起来。

GL_TRIANGLES:这个参数意味着OpenGL使用三个顶点来组成图形。所以,在开始的三个顶点,将用顶点1,顶点2,顶点3来组成一个三角形。完成后,在用下一组的三个顶点(顶点4,5,6)来组成三角形,直到数组结束。

GL_TRIANGLE_STRIP:  OpenGL的使用将最开始的两个顶点出发,然后遍历每个顶点,这些顶点将使用前2个顶点一起组成一个三角形。

GL_TRIANGLE_FAN:  在跳过开始的2个顶点,然后遍历每个顶点,让OpenGL将这些顶点于它们前一个,以及数组的第一个顶点一起组成一个三角形。

HelloWorld之路的结束


如果没有太大的问题的话,那么我们就会出现一开始的模拟器效果了.想想一张图片展示底层显示代码是这么的多,想哭有木有😭.其实这只是OpenGL ES的冰山一角.接下来,我将对着色器相关的部分进行研究讲解,如果有任何疑问,可以在评论区回复,共同讨论进步.最后附上HelloWorld的实现Demo.

OpenGLES相关学习Demo

上一篇下一篇

猜你喜欢

热点阅读