OpenGL ES实践

OpenGL ES在iOS上的应用

Posted by Ted on April 2, 2018

一、概念简介

GPU:能够结合几何、颜色、灯光和其他数据而产生一个屏幕图像的硬件组件

渲染:把程序提供的几何数据转换成屏幕上的图像的过程叫做渲染,渲染的结果保存在帧缓存中

像素:计算机上显示的图片都是由矩形的颜色点组成,这些颜色点叫做像素,每个像素都是由3个颜色元素组成的,一个红点、一个绿点和一个蓝点、RGB

缓存

OpenGL ES部分运行在CPU上,部分运行在GPU上,协调两个内存区域之间的数据交换,而OpenGL ES为两个内存区域间的数据交换定义了缓存(buffers)的概念,缓存是指图形处理器能够控制和管理的连续RAM。

OpenGL ES为缓存提供数据的7个步骤:

1、生成glGenBuffers()——请求OpenGL ES为图形处理器控制的缓存生成一个独一无二的标识符。

2、绑定glBindBuffer()——告诉OpenGL ES为接下来的运算使用一个缓存。

3、缓冲数据glBufferData()或glBufferSubData()——让OpenGL ES为当前绑定的缓存分配病初始化足够的连续内存(通常是从CPU控制的内存复制数据到分配的内存)。

4、启用或者禁止glEnableVertexAttribArray()或glDisVertexAttribArray()——告诉OpenGL ES在接下来的渲染中是否使用缓存中的数据。

5、设置指针glVertexAttribPointer()——告诉OpenGL ES在缓存中的数据的类型和所有需要访问的数据的内存偏移值。

6、绘制glDrawArrays()或glDrawElements()——告诉OpenGL ES使用当前绑定并启用的缓冲中的数据渲染整个场景或者某个场景的一部分。

7、删除glDeleteBuffers()——告诉OpenGL ES删除以前生成的缓存病释放相关的资源。

帧缓存

GPU需要知道应该在内存中那个位置存储渲染出来的2D图像像素数据,接受渲染结果的缓存区叫做帧缓存。

屏幕显示像素受到保存在前帧缓存中的像素颜色元素控制,所以程序和操作系统不会直接渲染到前帧缓存中,因为那样会让用户看到还没渲染完成的图像。而是,把渲染结果保存到后帧缓存中,当后帧缓存包含一个完成的图像,前后帧缓存瞬间切换,这样就呈现了新的图像。在iOS系统中,这些操作由系统之家完成,应用不能插手。Core Animation会把多个层(应用的层,系统的层比如状态栏)混合起来并在后帧缓存中产生最终的颜色,然后切换缓存。

二、iOS的图像架构

img

而在iOS 8之后,苹果推出了metal框架用来取代OpenGL

img

关于Core Graphics和OpenGL ES之间的关系:

  • 当图像是要显示到屏幕上的时候,OpenGL ES是Core Graphics的底层,用于连接硬件
  • 而如果是离屏渲染,用于生成PDF和图片文件,Core Graphics则是与OpenGL ES处于并列关系

Yes, on iOS Core Graphics (Quartz) appears to be layered on top of OpenGL ES for drawing that targets the screen, although not in an explicit way that we have access to.

Core Graphics takes vector elements (lines, arcs, etc.) and some raster ones (images) and processes them for display to the screen or for other forms of output (PDF files, printing, etc.). If the target is the screen on iOS, those vector elements will be hosted in a CALayer, either directly or through the backing layer of a UIView.

These Core Animation layers are effectively wrappers around rectangular textures on the GPU, which is how Core Animation can provide the smooth translation, scaling, and rotation of layers on even the original iPhone hardware. I can’t find a reference for it right now, but at least one WWDC presentation states that OpenGL ES is used by Core Animation to communicate with the GPU to perform this hardware acceleration. Something similar can be observed on the new dual-GPU MacBook Pros, where the more powerful GPU kicks in when interacting with an application using Core Animation.

Because Core Graphics rasterizes the vector and raster elements into a CALayer when drawing to the screen, and a CALayer effectively wraps around an OpenGL ES texture, I would place OpenGL ES below Core Graphics on iOS, but only for the case where Core Graphics is rendering to the screen. The reason for the side-by-side placement in the hierarchy you saw may be due to three factors: on the Mac, not all views are layer-backed, so they may not be hardware accelerated in the same way; we can’t really interact with the OpenGL ES backing of standard UI elements, so from a developer’s point of view they are distinct concepts; and Core Graphics can be used to render to offscreen contexts, like PDF files or images.

引自iOS: is Core Graphics implemented on top of OpenGL?

三、HelloWorld

1、渲染整个context的背景色

- (void)viewDidLoad {
    [super viewDidLoad];
    GLKView *glkView = (GLKView *)self.view;
    glkView.context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
    glkView.drawableDepthFormat = GLKViewDrawableDepthFormat24;
   //将此“EAGLContext”实例设置为OpenGL的“当前激活”的“Context”。这样,以后所有“GL”的指令均作用在这个“Context”上。
    [EAGLContext setCurrentContext:glkView.context];
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    // 指定刷新整个context颜色缓冲区时所用的颜色,RGBA
    glClearColor(0.3f, 0.6f, 1.0f, 1.0f);
    
    // 刷新缓存区
    // GL_COLOR_BUFFER_BIT:    当前可写的颜色缓冲
    // GL_DEPTH_BUFFER_BIT:    深度缓冲
    // GL_ACCUM_BUFFER_BIT:    累积缓冲
    // GL_STENCIL_BUFFER_BIT:  模板缓冲
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

如图所示,可以将整个背景渲染成蓝色

img

解释

drawableColorFormat

你的OpenGL上下文有一个缓冲区,它用以存储将在屏幕中显示的颜色。你可以使用其属性来设置缓冲区中每个像素的颜色格式。缺省值是GLKViewDrawableColorFormatRGBA8888,即缓冲区的每个像素的最小组成部分(-个像素有四个元素组成 RGBA)使用8个bit(如R使用8个bit)(所以每个像素4个字节 既 4*8 个bit)。这非常好,因为它给了你提供了最广泛的颜色范围,让你的app看起来更好。但是如果你的app允许更小范围的颜色,你可以设置为GLKViewDrawableColorFormatRGB565,从而使你的app消耗更少的资源(内存和处理时间)。

drawableDepthFormat

你的OpenGL上下文还可以(可选地)有另一个缓冲区,称为深度缓冲区。这帮助我们确保更接近观察者的对象显示在远一些的对象的前面(意思就是离观察者近一些的对象会挡住在它后面的对象)。其缺省的工作方式是:OpenGL把接近观察者的对象的所有像素存储到深度缓冲区,当开始绘制一个像素时,它(OpenGL)首先检查深度缓冲区,看是否已经绘制了更接近观察者的什么东西,如果是则忽略它(要绘制的像素,就是说,在绘制一个像素之前,看看前面有没有挡着它的东西,如果有那就不用绘制了)。否则,把它增加到深度缓冲区和颜色缓冲区。你可以设置这个属性,以选择深度缓冲区的格式。缺省值是GLKViewDrawableDepthFormatNone,意味着完全没有深度缓冲区。但是如果你要使用这个属性(一般用于3D游戏),你应该选择GLKViewDrawableDepthFormat16或GLKViewDrawableDepthFormat24。这里的差别是使用GLKViewDrawableDepthFormat16将消耗更少的资源,但是当对象非常接近彼此时,你可能存在渲染问题。

2、渲染某个色块

OpenGL ES的坐标系与Core Graphics的坐标系不一样,起点是在屏幕的中点,到两边是1

img

    // 声明顶点数据
    GLfloat vertexData[] =
    {
        0.5, -0.5, 0.0f,
        0.5, 0.5, -0.0f,
        -0.5, 0.5, 0.0f,
    };
    //顶点数据缓存
    //这几行代码表示的含义是:声明一个缓冲区的标识(GLuint类型)让OpenGL自动分配一个缓冲区并且返回这个标识的值绑定这个缓冲区到当前“Context”最后,将我们前面预先定义的顶点数据“vertexData”复制进这个缓冲区中。参数“GL_STATIC_DRAW”,它表示此缓冲区内容只能被修改一次,但可以无限次读取。
    GLuint buffer;
    glGenBuffers(1, &buffer);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
    
    // 激活顶点属性(默认它的关闭的)
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    // 填充顶点数据,顶点属性索引(这里是位置)、3个分量的矢量、类型是浮点(GL_FLOAT)、填充时不需要单位化(GL_FALSE)、在数据数组中每行的跨度是12个字节(4*3=12。从预定义的数组中可看出,每行有3个GL_FLOAT浮点值,而GL_FLOAT占4个字节,因此每一行的跨度是4*3),最后一个参数是一个偏移量的指针,用来确定“第一个数据”将从内存数据块的什么地方开始。
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, (GLfloat *)NULL + 0);

    // 着色器
    self.mEffect = [[GLKBaseEffect alloc]init];
    // 着色器的颜色
    self.mEffect.constantColor = GLKVector4Make(0.5f, 0.2f, 1.0f, 1.0f);
    
 - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    // 指定刷新整个context颜色缓冲区时所用的颜色,RGBA
    glClearColor(0.3f, 0.6f, 1.0f, 1.0f);
    
    // 刷新缓存区
    // GL_COLOR_BUFFER_BIT:    当前可写的颜色缓冲
    // GL_DEPTH_BUFFER_BIT:    深度缓冲
    // GL_ACCUM_BUFFER_BIT:    累积缓冲
    // GL_STENCIL_BUFFER_BIT:  模板缓冲
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
   //启动着色器
    [self.mEffect prepareToDraw];
   // 画出三角形
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

img