OpenGL ES 编程指南三:OpenGL ES和GLKit绘制

logo.png

Drawing with OpenGL ES and GLKit

The GLKit framework provides view and view controller classes that eliminate the setup and maintenance code that would otherwise be required for drawing and animating OpenGL ES content. The GLKView class manages OpenGL ES infrastructure to provide a place for your drawing code, and the GLKViewController class provides a rendering loop for smooth animation of OpenGL ES content in a GLKit view. These classes extend the standard UIKit design patterns for drawing view content and managing view presentation. As a result, you can focus your efforts primarily on your OpenGL ES rendering code and get your app up and running quickly. The GLKit framework also provides other features to ease OpenGL ES 2.0 and 3.0 development.

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内容)

The GLKView class provides an OpenGL ES–based equivalent of the standard UIView drawing cycle. A UIView instance automatically configures its graphics context so that your drawRect: implementation need only perform Quartz 2D drawing commands, and a GLKView instance automatically configures itself so that your drawing method need only perform OpenGL ES drawing commands. The GLKView class provides this functionality by maintaining a framebuffer object that holds the results of your OpenGL ES drawing commands, and then automatically presents them to Core Animation once your drawing method returns.

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

Like a standard UIKit view, a GLKit view renders its content on demand. When your view is first displayed, it calls your drawing method—Core Animation caches the rendered output and displays it whenever your view is shown. When you want to change the contents of your view, call its setNeedsDisplay method and the view again calls your drawing method, caches the resulting image, and presents it on screen. This approach is useful when the data used to render an image changes infrequently or only in response to user action. By rendering new view contents only when you need to, you conserve battery power on the device and leave more time for the device to perform other actions.

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

[图片上传失败...(image-517eab-1529501306089)]

Creating and Configuring a GLKit View(创建和配置GLView)

You can create and configure a GLKView object either programmatically or using Interface Builder. Before you can use it for drawing, you must associate it with an EAGLContext object (see Configuring OpenGL ES Contexts).

  • When creating a view programmatically, first create a context and then pass it to the view’s initWithFrame:context: method.
  • After loading a view from a storyboard, create a context and set it as the value of the view’s context property.

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

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

A GLKit view automatically creates and configures its own OpenGL ES framebuffer object and renderbuffers. You control the attributes of these objects using the view’s drawable properties, as illustrated in Listing 3-1. If you change the size, scale factor, or drawable properties of a GLKit view, it automatically deletes and re-creates the appropriate framebuffer objects and renderbuffers the next time its contents are drawn.

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

Listing 3-1 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;
}

You can enable multisampling for a GLKView instance using its drawableMultisample property. Multisampling is a form of antialiasingthat smooths jagged edges, improving image quality in most 3D apps at the cost of using more memory and fragment processing time—if you enable multisampling, always test your app’s performance to ensure that it remains acceptable.

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

Drawing With a GLKit View(在GLKit View上绘制)

Figure 3-1 outlines the three steps for drawing OpenGL ES content: preparing OpenGL ES infrastructure, issuing drawing commands, and presenting the rendered content to Core Animation for display. The GLKView class implements the first and third steps. For the second step, you implement a drawing method like the example in Listing 3-2.

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

Listing 3-2 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);
}

Note: The glClear function hints to OpenGL ES that any existing framebuffer contents can be discarded, avoiding costly memory operations to load the previous contents into memory. To ensure optimal performance, you should always call this function before drawing.

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

The GLKView class is able to provide a simple interface for OpenGL ES drawing because it manages the standard parts of the OpenGL ES rendering process:

  • Before invoking your drawing method, the view:
    • Makes its EAGLContext object the current context
    • Creates a framebuffer object and renderbuffers based on its current size, scale factor, and drawable properties (if needed)
    • Binds the framebuffer object as the current destination for drawing commands
    • Sets the OpenGL ES viewport to match the framebuffer size
  • After your drawing method returns, the view:
    Resolves multisampling buffers (if multisampling is enabled)
    • Discards renderbuffers whose contents are no longer needed
    • Presents renderbuffer contents to Core Animation for caching and display

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

  • 在调用绘图方法之前,该视图:
    • 使其EAGLContext对象成为当前上下文
    • 根据当前大小,比例和可绘制属性(如果需要)创建一个framebuffer对象和renderbuffers,
    • 将framebuffer对象绑定为绘制命令的当前目标
    • 设置OpenGL ES视口以匹配帧缓冲区大小
  • 在您的绘图方法返回后,视图:
    • 解决多采样缓冲区(如果启用多重采样)
    • 丢弃其内容不再需要的renderbuffers
      向Core Animation呈现renderbuffer内容进行缓存和显示

Rendering Using a Delegate Object(使用委托对象渲染)

Many OpenGL ES apps implement rendering code in a custom class. An advantage of this approach is that it allows you to easily support multiple rendering algorithms by defining a different renderer class for each. Rendering algorithms that share common functionality can inherit it from a superclass. For example, you might use different renderer classes to support both OpenGL ES 2.0 and 3.0 (see Configuring OpenGL ES Contexts). Or you might use them to customize rendering for better image quality on devices with more powerful hardware.

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

GLKit is well suited to this approach—you can make your renderer object the delegate of a standard GLKView instance. Instead of subclassing GLKView and implementing the drawRect: method, your renderer class adopts the GLKViewDelegate protocol and implements the glkView:drawInRect: method. Listing 3-3 demonstrates choosing a renderer class based on hardware features at app launch time.

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

Listing 3-3 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内容)

By default, a GLKView object renders its contents on demand. That said, a key advantage to drawing with OpenGL ES is its ability to use graphics processing hardware for continuous animation of complex scenes—apps such as games and simulations rarely present static images. For these cases, the GLKit framework provides a view controller class that maintains an animation loop for the GLKView object it manages. This loop follows a design pattern common in games and simulations, with two phases: update and display. Figure 3-2 shows a simplified example of an animation loop.

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

[图片上传失败...(image-3b64a3-1529501306090)]

Understanding the Animation Loop

For the update phase, the view controller calls its own update method (or its delegate’s glkViewControllerUpdate:
method). In this method, you should prepare for drawing the next frame. For example, a game might use this method to determine the positions of player and enemy characters based on input events received since the last frame, and a scientific visualization might use this method to run a step of its simulation. If you need timing information to determine your app’s state for the next frame, use one of the view controller’s timing properties such as the timeSinceLastUpdate property. In Figure 3-2, the update phase increments an angle variable and uses it to calculate a transformation matrix.

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

For the display phase, the view controller calls its view’s display method, which in turn calls your drawing method. In your drawing method, you submit OpenGL ES drawing commands to the GPU to render your content. For optimal performance, your app should modify OpenGL ES objects at the start of rendering a new frame, and submit drawing commands afterward. In Figure 3-2, the display phase sets a uniform variable in a shader program to the matrix calculated in the update phase, and then submits a drawing command to render new content.

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

The animation loop alternates between these two phases at the rate indicated by the view controller’s framesPerSecond property. You can use the preferredFramesPerSecond property to set a desired frame rate—to optimize performance for the current display hardware, the view controller automatically chooses an optimal frame rate close to your preferred value.

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

Important: For best results, choose a frame rate your app can consistently achieve. A smooth, consistent frame rate produces a more pleasant user experience than a frame rate that varies erratically.

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

Using a GLKit View Controller

Listing 3-4 demonstrates a typical strategy for rendering animated OpenGL ES content using a GLKViewController subclass and GLKView instance.

清单3-4演示了使用GLKViewController子类和GLKView实例渲染动画OpenGL ES内容的典型策略。

Listing 3-4 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

In this example, an instance of the PlanetViewController class (a custom GLKViewController subclass) is loaded from a storyboard, along with a standard GLKView instance and its drawable properties. The viewDidLoad method creates an OpenGL ES context and provides it to the view, and also sets the frame rate for the animation loop.

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

The view controller is automatically the delegate of its view, so it implements both the update and display phases of the animation loop. In the update method, it calculates the transformation matrices needed to display a rotating planet. In the glkView: drawInRect: method, it provides those matrices to a shader program and submits drawing commands to render the planet geometry.

视图控制器毫无疑问是视图的委托,因此它实现了动画循环的更新和显示阶段。 在更新方法中,它计算显示旋转行星所需的变换矩阵。 在glkView: drawInRect: 方法中,它将这些矩阵提供给着色器程序,并提交绘图命令来渲染行星几何。

Using GLKit to Develop Your Renderer(使用GLKit开发渲染器)

In addition to view and view controller infrastructure, the GLKit framework provides several other features to ease OpenGL ES development on iOS.

除了View和View Controller之外,GLKit框架还提供了几个其他功能来简化iOS上的OpenGL ES开发。

Handling Vector and Matrix Math(处理向量和矩阵数学)

OpenGL ES 2.0 and later doesn’t provide built-in functions for creating or specifying transformation matrices. Instead, programmable shaders provide vertex transformation, and you specify shader inputs using generic uniform variables. The GLKit framework includes a comprehensive library of vector and matrix types and functions, optimized for high performance on iOS hardware. (See GLKit Framework Reference.)

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

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

OpenGL ES 2.0 and later removes all functionality associated with the OpenGL ES 1.1 fixed-function graphics pipeline. The GLKBaseEffect class provides an Objective-C analog to the transformation, lighting and shading stages of the OpenGL ES 1.1 pipeline, and the GLKSkyboxEffect and GLKReflectionMapEffect classes add support for common visual effects. See the reference documentation for these classes for details.

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

Loading Texture Data(加载文理数据)

The GLKTextureLoader class provides a simple way to load texture data from any image format supported by iOS into an OpenGL ES context, synchronously or asynchronously. (See Use the GLKit Framework to Load Texture Data.)

GLKTextureLoader类提供了一种简单的方式来将纹理数据从iOS支持的任何图像格式加载到OpenGL ES上下文中,同步或异步。 (请参阅使用GLKit框架加载纹理数据。)


logo.png

推荐阅读更多精彩内容