OpenGL ES 框架详细解析(五) —— 使用OpenGL ES和GLKit进行绘制

版本记录

版本号 时间
V1.0 2017.09.28

前言

OpenGL ES是一个强大的图形库,是跨平台的图形API,属于OpenGL的一个简化版本。iOS系统可以利用OpenGL ES将图像数据直接送入到GPU进行渲染,这样避免了从CPU进行计算再送到显卡渲染带来的性能的高消耗,能带来来更好的视频效果和用户体验。接下来几篇就介绍下iOS 系统的 OpenGL ES框架。感兴趣的可以看上面几篇。
1. OpenGL ES 框架详细解析(一) —— 基本概览
2. OpenGL ES 框架详细解析(二) —— 关于OpenGL ES
3. OpenGL ES 框架详细解析(三) —— 构建用于iOS的OpenGL ES应用程序的清单
4. OpenGL ES 框架详细解析(四) —— 配置OpenGL ES的上下文

Drawing with OpenGL ES and GLKit - 使用OpenGL ES和GLKit进行绘制

GLKit框架提供了视图和视图控制器类,可以消除绘图和动画化OpenGL ES内容所需的设置和维护代码。 GLKView类管理OpenGL ES基础结构为您的绘图代码提供了一个地方,GLKViewController类为GLKit视图中OpenGL ES内容的平滑动画提供了一个渲染循环。 这些类扩展了用于绘制视图内容和管理视图呈现的标准UIKit设计模式。 因此,您可以将重点放在OpenGL ES渲染代码上,并使您的应用快速运行。 GLKit框架还提供了其他功能来简化OpenGL ES 2.0和3.0开发。


A GLKit View Draws OpenGL ES Content on Demand - GLKit视图根据需要绘制OpenGL ES内容

GLKView类提供与标准UIView绘图周期相当的OpenGL ES。 UIView实例自动配置其图形上下文,以便您的drawRect:实现只需要执行Quartz 2D绘图命令,并且GLKView实例自动配置,以便您的绘图方法只需要执行OpenGL ES绘图命令。 GLKView类通过维护保存OpenGL ES绘图命令结果的framebuffer对象提供此功能,然后在绘图方法返回后自动将其显示给Core Animation。

像标准的UIKit视图一样,GLKit视图根据需要呈现其内容。 当您的视图第一次显示时,它会调用您的绘图方法 - Core Animation会缓存渲染的输出,并在显示视图时显示它。 当您想要更改视图的内容时,请调用其setNeedsDisplay方法,然后视图再次调用绘图方法,缓存生成的图像,并将其显示在屏幕上。 当用于渲染图像的数据不经常更改或仅响应于用户操作时,此方法非常有用。 通过仅在需要时才提供新的视图内容,您可以节省设备上的电池电量,并为设备执行其他操作留出更多时间。

Rendering OpenGL ES content with a GLKit view

1. Creating and Configuring a GLKit View - 创建和配置GLKit视图

您可以以编程方式或使用Interface Builder创建和配置GLKView对象。 在将其用于绘图之前,必须将其与EAGLContext对象相关联(请参阅Configuring OpenGL ES Contexts)。

  • 以编程方式创建视图时,首先创建上下文,然后将其传递给视图的initWithFrame:context:method
  • 从sb中加载视图后,创建一个上下文并将其设置为视图的上下文属性的值。

GLKit视图会自动创建和配置自己的OpenGL ES framebuffer对象和renderbuffers。 您可以使用视图的可绘制属性来控制这些对象的属性,如下面代码所示。 如果您更改GLKit视图的大小,比例因子或可绘制属性,则会在下次绘制内容时自动删除并重新创建相应的framebuffer对象和renderbuffers。

// Configuring a GLKit view

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Create an OpenGL ES context and assign it to the view loaded from storyboard
    GLKView *view = (GLKView *)self.view;
    view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
 
    // Configure renderbuffers created by the view
    view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
 
    // Enable multisampling
    view.drawableMultisample = GLKViewDrawableMultisample4X;
}

您可以使用其drawableMultisample属性为GLKView实例启用多采样。 多采样是一种抗锯齿形式,可以平滑锯齿状边缘,以更多的内存和片段处理时间为代价改善大多数3D应用程序的图像质量 - 如果启用多采样,则始终测试应用程序的性能,以确保其仍然可以接受。

2. Drawing With a GLKit View - 利用GLKit视图进行绘制

上面的代码概述了绘制OpenGL ES内容的三个步骤:准备OpenGL ES基础设施,发布绘图命令,并将呈现的内容呈现给Core Animation进行显示。 GLKView类实现了第一和第三步。 对于第二步,您将实现一个绘图方法,如下面的代码所示。

// Example drawing method for a GLKit view
 
- (void)drawRect:(CGRect)rect
{
    // Clear the framebuffer
    glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
    // Draw using previously configured texture, shader, uniforms, and vertex array
    glBindTexture(GL_TEXTURE_2D, _planetTexture);
    glUseProgram(_diffuseShading);
    glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0, _modelViewProjectionMatrix.m);
    glBindVertexArrayOES(_planetMesh);
    glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT);
}

注意:glClear函数提示OpenGL ES可以丢弃任何现有的帧缓冲区内容,避免了昂贵的内存操作将以前的内容加载到内存中。 为确保最佳性能,您应该在绘制之前始终调用此函数。

GLKView类能够为OpenGL ES绘图提供一个简单的界面,因为它可以管理OpenGL ES渲染过程的标准部分:

  • 在调用绘图方法之前,该视图:

    • 使其EAGLContext对象成为当前上下文
    • 根据当前大小,比例因子和可绘制属性(如果需要)创 建一个framebuffer对象和renderbuffers
    • 将framebuffer对象绑定为绘制命令的当前目标
    • 设置OpenGL ES视口以匹配帧缓冲区大小
  • 在您的绘图方法返回后,视图:

    • 解决多采样缓冲区(如果启用多重采样)
    • 丢弃其内容不再需要的renderbuffers
    • 向Core Animation呈现renderbuffer内容进行缓存和显示

Rendering Using a Delegate Object - 使用代理对象进行渲染

许多OpenGL ES应用程序在自定义类中实现渲染代码。 这种方法的一个优点是它允许您通过为每个渲染算法定义一个不同的渲染器类来轻松支持多个渲染算法。 共享通用功能的渲染算法可以从超类继承。 例如,您可以使用不同的渲染器类来支持OpenGL ES 2.0和3.0(请Configuring OpenGL ES Contexts)。 或者您可以使用它们来定制渲染,以获得更强大硬件的设备上的更好的图像质量。

GLKit非常适合这种方法 - 您可以使您的渲染器对象成为标准GLKView实例的委托。 您的渲染器类不是将GLKView子类化并实现drawRect:方法,而是使用GLKViewDelegate协议并实现glkView:drawInRect:方法。 下面代码演示了在应用启动时基于硬件功能选择渲染器类。

// Choosing a renderer class based on hardware features

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

// Create a context so we can test for features

EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

[EAGLContext setCurrentContext:context];

// Choose a rendering class based on device features

GLint maxTextureSize;

glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);

if (maxTextureSize > 2048)

self.renderer = [[MyBigTextureRenderer alloc] initWithContext:context];

else

self.renderer = [[MyRenderer alloc] initWithContext:context];

// Make the renderer the delegate for the view loaded from the main storyboard

GLKView *view = (GLKView *)self.window.rootViewController.view;

view.delegate = self.renderer;

// Give the OpenGL ES context to the view so it can draw

view.context = context;

return YES;

}

A GLKit View Controller Animates OpenGL ES Content - GLKit视图控制器动画化OpenGL ES内容

默认情况下,GLKView对象根据需要呈现其内容。 也就是说,使用OpenGL ES绘制的一个关键优点是它能够使用图形处理硬件来连续制作复杂的场景 - 诸如游戏和模拟的应用程序很少呈现静态图像。 对于这些情况,GLKit框架提供了一个视图控制器类,它为其管理的GLKView对象维护一个动画循环。 该循环遵循游戏和模拟中常见的设计模式,分为两个阶段:更新和显示。 下图显示了一个动画循环的简化示例。

The animation loop

1. Understanding the Animation Loop - 了解动画循环

对于更新阶段,视图控制器调用其自己的更新方法(或其委托的glkViewControllerUpdate:方法)。 在这种方法中,你应该准备绘制下一个帧。 例如,游戏可能会使用这种方法根据自最后一帧以来接收到的输入事件来确定玩家和敌人角色的位置,科学可视化可能会使用此方法来运行其模拟步骤。 如果您需要时间信息来确定您的应用的下一帧的状态,请使用其中一个视图控制器的时序属性。

对于显示阶段,视图控制器调用其视图的显示方法,该方法又调用您的绘图方法。 在绘图方法中,您可以向GPU提交OpenGL ES绘图命令以呈现内容。 为了获得最佳性能,应用程序应在开始渲染新框架时修改OpenGL ES对象,然后提交绘图命令。 在上图中,显示阶段将着色器程序中的统一变量设置为在更新阶段计算的矩阵,然后提交绘图命令以呈现新内容。

动画循环按照视图控制器的framePerSecond属性指示的速率在这两个阶段之间进行交替。 您可以使用preferredFramesPerSecond属性设置所需的帧速率,以优化当前显示硬件的性能,视图控制器会自动选择接近您的首选值的最佳帧速率。

重要提示:为获得最佳效果,请选择应用程序可以始终如一地实现的帧率 平滑,一致的帧率产生比不定期变化的帧速率更愉快的用户体验。

2. Using a GLKit View Controller - 使用GLKit控制器

下面代码演示了使用GLKViewController子类和GLKView实例渲染动画OpenGL ES内容的典型策略。

// Using a GLKit view and view controller to draw and animate OpenGL ES content

@implementation PlanetViewController // subclass of GLKViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // Create an OpenGL ES context and assign it to the view loaded from storyboard
    GLKView *view = (GLKView *)self.view;
    view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
 
    // Set animation frame rate
    self.preferredFramesPerSecond = 60;
 
    // Not shown: load shaders, textures and vertex arrays, set up projection matrix
    [self setupGL];
}
 
- (void)update
{
    _rotation += self.timeSinceLastUpdate * M_PI_2; // one quarter rotation per second
 
    // Set up transform matrices for the rotating planet
    GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(_rotation, 0.0f, 1.0f, 0.0f);
    _normalMatrix = GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL);
    _modelViewProjectionMatrix = GLKMatrix4Multiply(_projectionMatrix, modelViewMatrix);
}
 
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    // Clear the framebuffer
    glClearColor(0.0f, 0.0f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
    // Set shader uniforms to values calculated in -update
    glUseProgram(_diffuseShading);
    glUniformMatrix4fv(_uniformModelViewProjectionMatrix, 1, 0, _modelViewProjectionMatrix.m);
    glUniformMatrix3fv(_uniformNormalMatrix, 1, 0, _normalMatrix.m);
 
    // Draw using previously configured texture and vertex array
    glBindTexture(GL_TEXTURE_2D, _planetTexture);
    glBindVertexArrayOES(_planetMesh);
    glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT, 0);
}
 
@end

在此示例中,PlanetViewController类(自定义GLKViewController子类)的实例从故事板加载,以及标准GLKView实例及其可绘制属性。viewDidLoad方法创建一个OpenGL ES上下文并将其提供给视图,并且还设置动画循环的帧速率。

视图控制器自动为其视图的委托,因此它同时实现动画循环的更新和显示阶段。 在更新方法中,它计算显示旋转平面所需的变换矩阵。 在glkView:drawInRect:方法中,它将这些矩阵提供给着色器程序,并提交绘制命令以渲染平面几何。


Using GLKit to Develop Your Renderer - 使用GLKit开发您的渲染

除了查看和查看控制器基础外,GLKit框架还提供了几个其他功能来简化iOS上的OpenGL ES开发。

1. Handling Vector and Matrix Math - 处理向量和矩阵数学

OpenGL ES 2.0及更高版本不提供用于创建或指定变换矩阵的内置函数。 相反,可编程着色器提供顶点变换,并使用通用的均匀变量指定着色器输入。 GLKit框架包括矢量和矩阵类型和功能的综合库,针对iOS硬件上的高性能进行了优化。 (见GLKit Framework Reference。)

2. Migrating from the OpenGL ES 1.1 Fixed-Function Pipeline - 从OpenGL ES 1.1固定功能管道迁移

OpenGL ES 2.0及更高版本删除与OpenGL ES 1.1固定功能图形管道相关联的所有功能。GLKBaseEffect类为OpenGL ES 1.1管道的转换,照明和阴影阶段提供了Objective-C模拟,GLKSkyboxEffectGLKReflectionMapEffect类增加了对常见视觉效果的支持。 有关详细信息,请参阅这些类的参考文档。

3. Loading Texture Data - 加载纹理数据

GLKTextureLoader类提供了一种简单的方法,可将纹理数据从iOS支持的任何图像格式加载到OpenGL ES上下文中,同步或异步。 (请参阅Use the GLKit Framework to Load Texture Data。)

后记

未完,待续~~~

推荐阅读更多精彩内容