OpenGL ES 基本知识

OpenGL ES(OpenGL for Embedded Systems)是以手持和嵌入式为目标的高级3D图形API。支持跨平台 iOS 安卓 BlackBerry(黑莓手机是指由加拿大Research In Motion Ltd公司推出的一种无线手持邮件解决终端设备)、bada(韩国三星电子自行开发的智能手机平台,底层为Linux核心)、Linux、Windows。

开放式图形库用于可视化的二维和三维数据。它是一个多功能开放标准图形库,支持2D和3D数字内容创建、机械和建筑设计、虚拟原型设计 、飞行模拟、视频游戏等应用程序。


OpenGL ES 3.0 图形管线.001.jpeg
  • 顶点着色器输入:
    着色器程序:描述顶点上执行操作的顶点着色器程序源代码/可执行文件
    顶点着色器输入(属性):用顶点数组提供每个顶点的数据
    统一变量(Uniform):顶点/片元着色器使用的不变数据
    采样器:代表顶点着色器使用纹理的特殊统一变量类型
  • 顶点着色器的任务:
  1. 矩阵变换位置
  2. 计算光照公式生成逐顶点颜色
  3. 生成/变换纹理坐标
    它可以用于执行自定义计算,实施新的变换,照明或者传统的固定功能所不允许的基于顶点的效果
attribute vec4  position ;
attribute vec2 textCoordinate;
uniform mat4 rotateMatrix;
varying lowp vec2 varyTextCoord;
void main(){
  varyTextCoord = textCoordinate;
  vec4 vPos = position;
  vPos = vPos * rotateMatrix;
  gl_Position = vPos;
}
  • 图元装配
    图元(primitive): 点、线、三角形等
    图元装配: 将顶点数据计算成一个个图元。在这个阶段会执行裁剪、透视分割和viewport变换操作
    图元类型和顶点所确定将被渲染的单元。对于每个单独图元及其对应的顶点,图元装配阶段执行的操作包括:将顶点着色器输出的值进行裁剪、透视分割、视口变换后进入光栅化阶段。
  • 光栅化
    在这个阶段绘制对应的图元(点/线/三角形)光栅化就是将图元转化成一组二维片段的过程。而这些转化的片段将有片元着色器处理。这些二维片段就是屏幕上可绘制的像素。


    光栅化.001.jpeg
  • 片元着色器/片段着色器
    着色器程序: 描述片段上执行操作的片元着色器源代码/可执行文件
    输入变量:光栅化单元用插值为每个片段生成顶点着色器输出
    统一变量:顶点/片元着色器使用的不变数据
    采样器:代表片元着色器使用纹理的特殊统一变量类型
  • 片元着色器作用:
    1.计算颜色
    2.获取纹理值
    3.往像素点钟填充颜色值(纹理值、颜色值)
    总结:它可以用于图片、视频、图形中每个像素的颜色填充(比如给视频添加滤镜,实际上就是将视频中每个图片的像素点颜色填充进行修改)
    案例:
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
    gl_FragColor = texture2D(colorMap,varyTextCoord);
}
逐片段操作.001.jpeg
  • EGL(Embedded Graphics Library)
    OpenGL ES命令需要渲染上下文和绘制表面才能完成图形图像的绘制
    渲染上下文: 存储相关OpenGL ES状态
    绘制表面:用于绘制图元的表面。它指定渲染所需要的缓存区类型;例如颜色缓存区、深度缓存区和模板缓存区
    OpenGL ES API并没有提供如何创建渲染上下文或者上下文如何连接到原生窗口系统。EGL是Khronos渲染API和原生窗口系统之间的接口。(Khronos Group(OpenGL也是他们搞的)新推出Vulkan,旨在替代OpenGL,其前身是AMD的Mantle。Android7开始支持Vulkan,唯一支持OpenGL ES却不支持EGL的平台是iOS。Apple提供自己的EGL API的iOS实现,成为EAGL)
    因为每个窗口系统都有不同的定义,所以EGL提供基本的不同命类型-EGLDisplay,这个类型封装了所有系统的相关性,用于和原生系统接口。

  • 由于OpenGL ES是基于C的API,因此它⾮非常便便携且受到⼴广泛⽀支持。作为C API,它与
    Objective-C Cocoa Touch应⽤用程序⽆无缝集成。OpenGL ES规范没有定义窗⼝口层; 相
    反,托管操作系统必须提供函数来创建⼀一个接受命令的OpenGL ES 渲染上下⽂文和⼀一个帧
    缓冲区,其中写⼊入任何绘图命令的结果。在iOS上使⽤用OpenGL ES需要使⽤用iOS类来设置
    和呈现绘图表⾯面,并使⽤用平台中⽴立的API来呈现其内容

GLKit框架
  • GLKit框架的设计目标是为了简化OpenGL / OpenGL ES的应用开发。它的出现加快了OpenGL ES或OpenGL 应用程序开发。使用数学库,背景纹理加载,预先创建的着色器效果,以及标准视图和视图控制器来实现渲染循环。
  • GLKit框架提供了功能和类,可以减少创建新的基于着色器的应用程序所需的工作量,或者支持依赖早期版本的OpenGL ES或OpenGL 提供的固定函数顶点或片段处理的现有应用程序。
  • GLKView 提供绘制场所(View)
  • GLKViewController(扩展于标准的UIKit设计模式)用于绘制视图内容的管理与呈现
  • (注意:苹果虽然弃用OpenGL ES,但是可以继续使用)
    GLKit的功能
  • 加载纹理
  • 提供高性能的数学运算
  • 提供常见的着色器
  • 提供视图以及视图控制器
    GLKit 的相关代码配置
 ///创建OpenGL ES上下文并将其分配给从故事版加载的视图
    GLKView *view = (GLKView *)self.view;
    view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
    //配置视图创建的渲染缓冲区
    view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
    
    //启动多重采样
    view.drawableMultisample = GLKViewDrawableMultisample4X;
-(void)drawRect:(CGRect)rect
    {
        //清除帧缓冲区
        glClearColor(0.0f,0.0f,0.1f,1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        //使⽤用先前配置的纹理,着⾊色器器和顶点数组绘制
        glBindTexture(GL_TEXTURE_2D,_planetTexture);
        glUseProgram(_diffuseShading);
        glUniformMatrix4fv(_uniformModelViewProjectionMatrix,1,0,
        _modelViewProjectionMatrix.m);
        glBindVertexArrayOES(_planetMesh);
        glDrawElements(GL_TRIANGLE_STRIP,256,GL_UNSIGNED_SHORT);
    }
  • GLKTextureInfo 创建OpenGL纹理信息
@interface GLKTextureInfo : NSObject <NSCopying>
{
@private
    GLuint                      name;  //OpenGL上下文中纹理名称
    GLenum                      target;  //纹理绑定的目标
    GLuint                      width;  //加载纹理的高度
    GLuint                      height;  //加载的纹理宽度
    GLuint                      depth;
    GLKTextureInfoAlphaState    alphaState;  //加载纹理中alpha分量状态
    GLKTextureInfoOrigin        textureOrigin; //加载纹理中的原点位置
    BOOL                        containsMipmaps;  //加载的纹理是否包含mip贴图
    GLuint                      mimapLevelCount;
    GLuint                      arrayLength;
}

  • GLKView使用OpenGL ES绘制内容的视图模式实现
    视图的初始化

- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context;

视图的代理

@protocol GLKViewDelegate <NSObject>

@required
/*
 Required method for implementing GLKViewDelegate. This draw method variant should be used when not subclassing GLKView.
 This method will not be called if the GLKView object has been subclassed and implements -(void)drawRect:(CGRect)rect.
 */
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;

配置帧缓冲区的对象和属性

@property (nonatomic, readonly) NSInteger drawableWidth; //底层缓存区对象的高度(以像素为单位)
@property (nonatomic, readonly) NSInteger drawableHeight;//底层缓存区对象的宽度(以像素为单位)

@property (nonatomic) GLKViewDrawableColorFormat drawableColorFormat; //颜色缓存区设置格式
@property (nonatomic) GLKViewDrawableDepthFormat drawableDepthFormat;//深度缓存区设置格式
@property (nonatomic) GLKViewDrawableStencilFormat drawableStencilFormat;//模板缓存区设置格式
@property (nonatomic) GLKViewDrawableMultisample drawableMultisample;//多重采样缓存区设置格式
  • 绘制视图的内容
1.context  绘制视图内容时使用的OpenGL ES上下文
2.bindDrawable 将底层FrameBuffer 对象绑定到OpenGL ES
3.enableSetNeedsDisplay 布尔值,指定视图是否影响应使得视图内容无效的消息
4.display 立即重绘视图内容
5.snapshot  绘制视图内容并将其作为新视图对象返回
  • 删除视图FrameBuffer对象
deleteDrawable 删除与视图关联的可绘制对象
  • 绘制视图内容
glkView:drawInRect: 绘制视图内容(必须实现代理)
  • GLKViewController 管理OpenGL ES 渲染循环的视图控制器
  • 更新视图内容
@protocol GLKViewControllerDelegate <NSObject>

@required
/*
 Required method for implementing GLKViewControllerDelegate. This update method variant should be used
 when not subclassing GLKViewController. This method will not be called if the GLKViewController object
 has been subclassed and implements -(void)update.
 */
- (void)glkViewControllerUpdate:(GLKViewController *)controller;
  • 配置帧速率
/*
 For setting the desired frames per second at which the update and drawing will take place.
 The default is 30.
 */
@property (nonatomic) NSInteger preferredFramesPerSecond;  视图控制器调用视图以及更新视图内容的速率
/*
 The actual frames per second that was decided upon given the value for preferredFramesPerSecond
 and the screen for which the GLKView resides. The value chosen will be as close to
 preferredFramesPerSecond as possible, without exceeding the screen's refresh rate. This value
 does not account for dropped frames, so it is not a measurement of your statistical frames per
 second. It is the static value for which updates will take place.
 */
@property (nonatomic, readonly) NSInteger framesPerSecond; //视图控制器调用视图以及更新其内容的实际速率
  • 控制帧更新:
@property (nonatomic, getter=isPaused) BOOL paused; //渲染是否已暂停
@property (nonatomic) BOOL pauseOnWillResignActive;  // 当前程序重新激活活动状态时视图控制器是否自动暂停渲染循环
@property (nonatomic) BOOL resumeOnDidBecomeActive; //程序变化为活跃状态时,是否自动恢复呈现循环

  • 预览效果


    GLKit加载图片
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>

@interface ViewController ()
{
    EAGLContext *context;
    GLKBaseEffect *cEffect;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
 
    //1.OpengGL ES 相关初始化
    [self setUpConfig];
    //2.加载顶点/纹理坐标数据
    [self setUpVertexData];
    
    //3.加载纹理数据(使用GLBaseEffect)
    [self setUpTexture];
}

#pragma mark -- OpenGL ES setUp

-(void)setUpTexture
{
   //1.获取纹理图片路径
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"girl" ofType:@"png"];
    //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;
    
}

-(void)setUpVertexData
{
    /*设置顶点数组(顶点坐标、纹理坐标)
     纹理坐标系取值范围【0,1】;
     原点是左下角(0,0)
     (1,1)右上角
     */
    //一个正方形两个三角形 6个顶点
    GLfloat verTexData[] = {
        0.5,-0.5,0.0,   1.0f,0.0,//右下
        0.5,0.5,0.0f,   1.0f,1.0f,//右上
        -0.5,0.5,0.0,   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,//左下
        
    };
    /*
     顶点数组:开发者可以选择设定函数指针,在调用绘制方法的时候,直接右内存传入顶点数据,也就是说这部分数据之前是存储在内存当中的,被称为顶点数组
     顶点缓存区:性能更高的做法是,提前 分配一块显存,将顶点数据预先传入到显存中,这部分显存就被称为顶点缓冲区
     */
    
    //2.开辟顶点缓存区
    //(1)创建顶点缓存区标识符
    GLuint bufferID;
    glGenBuffers(1, &bufferID);
    //(2).绑定顶点缓存区(明确作用)
    glBindBuffer(GL_ARRAY_BUFFER, bufferID);
    //(3)将顶点数组的数据copy到顶点焕存区中(GPU显存中)
    glBufferData(GL_ARRAY_BUFFER, sizeof(verTexData), verTexData, GL_STATIC_DRAW);
    
    /*打开读取通道
     1.在iOS中,默认情况下,出于性能考虑,所有顶点着色器的属性(Atrribute)变量都是关闭的。
     顶点数据在着色器端(服务端)是不可用的,即使已经使用glBufferData方法,将顶点数据从内存拷贝到顶点缓存区(GPU显存中)
     所以必须由glEanbleVertexAttribArray 方法打开通道,指定访问属性。才能让顶点着色器能够访问到从CPUd复制到GPU的数据
     注意:数据在GPU端是否可见,即着色器能否读取到数据,由是否启用了对应的属性决定。这就是glEnableVertexAttribArray的功能,允许顶点着色器读取GPU(服务端)数据
     2.方法简介
     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。
     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);
}

-(void)setUpConfig
{
    ///创建OpenGL ES上下文并将其分配给从故事版加载的视图
    GLKView *view = (GLKView *)self.view;
    //初始化上下文
    view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    
    /*配置视图创建的渲染缓冲区
    GLKViewDrawableColorFormatRGBA8888  = 0 默认缓存区的每个像素的最小组成部分(RGBA)使用8bit(所以每个像素4个字节,4*8bit)
    GLKViewDrawableColorFormatRGB565,
   如果你的APP允许更小范围的颜色,即可设置这个。会让你的APP消耗更小的资源(内存和处理时间)
    */
        view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;// 默认
        view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    /*
     GLKViewDrawableDepthFormatNone = 0 完全没有深度缓冲区
     GLKViewDrawableDepthFormat16
     如果你要使用这个属性(一般用于3D游戏),你应该选择
     或GLKViewDrawableDepthFormat24。这里的差别是使用GLKViewDrawableDepthFormat16 将消耗更少的资源
     view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
     */
    
    //启动多重采样
    //    view.drawableMultisample = GLKViewDrawableMultisample4X;
    //设置当前上下文
    [EAGLContext setCurrentContext:view.context];
    //    GLKView *view = (GLKView *)self.view;
    //    view.context = context;
    //设置背景颜色
    glClearColor(1.0, 1.0, 0, 1.0);
    
}

#pragma mark -- GLKViewDelegate
//绘制视图的内容
/*
 GLKView对象使其OpenGL ES上下文成为当前上下文,并将其framebuffer绑定为OpenGL ES呈现命令的目标。然后,委托方法应该绘制视图的内容。
*/
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    //清楚帧缓冲区
//    glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT );
 
    //2.绘制准备
    [cEffect prepareToDraw];
    
    //3.开始绘制
    glDrawArrays(GL_TRIANGLES, 0, 6);
    
}