绘制OpenGL ES和GLKit
GLKit框架提供视图和视图控制器类,可以消除为绘制和动画化OpenGL ES内容而需要的设置和维护代码。 GLKView类管理OpenGL ES基础架构,为您的绘图代码提供一个地方,GLKViewController类为GLKit视图中OpenGL ES内容的平滑动画提供了一个渲染循环。这些类扩展了用于绘制视图内容和管理视图呈现的标准UIKit设计模式。因此,您可以将重点放在OpenGL ES渲染代码上,并使您的应用程序快速启动并运行。 GLKit框架还提供了其他功能来简化OpenGL ES 2.0和3.0开发。
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方法,再次调用绘图方法,缓存生成的图像,并将其显示在屏幕上。当用于渲染图像的数据不经常更改或仅响应于用户操作时,此方法非常有用。通过仅在需要时才提供新的视图内容,您可以节省设备上的电池电量,并为设备执行其他操作留出更多时间
创建和配置GLKit视图
您可以以编程方式或使用Interface Builder创建和配置GLKView对象。在使用它绘制之前,必须将其与EAGLContext对象相关联(请参阅配置OpenGL ES上下文)。
- 以编程方式创建视图时,首先创建上下文,然后将其传递给视图的initWithFrame:context:方法。
- 从故事板加载视图后,创建上下文并将其设置为视图的上下文属性的值。
GLKit视图会自动创建和配置自己的OpenGL ES framebuffer对象和renderbuffers。您可以使用视图的可绘制属性来控制这些对象的属性,如清单3-1所示。如果更改GLKit视图的大小,比例因子或可绘制属性,则会在下次绘制内容时自动删除并重新创建相应的framebuffer对象和renderbuffers。
Listing3-1
- (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应用程序的图像质量提升,如果启用多采样,则始终测试应用程序的性能,以确保其仍然可以接受。
绘制GLKit视图
图3-1概述了绘制OpenGL ES内容的三个步骤:准备OpenGL ES基础设施,发布绘图命令,并将呈现的内容呈现给Core Animation进行显示。 GLKView类实现了第一和第三步。对于第二步,您将实现一个绘图方法,如清单3-2中的示例所示。
Listing3-2
- (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内容以进行缓存和显示
使用委托对象呈现
许多OpenGL ES应用程序在自定义类中实现渲染代码。这种方法的优点在于它允许您通过为每个渲染算法定义不同的渲染器类来轻松支持多种渲染算法。共享公共功能的渲染算法可以从超类继承。例如,您可以使用不同的渲染器类来支持OpenGL ES 2.0和3.0(请参阅配置OpenGL ES上下文)。或者您可以使用它们来定制渲染,从而在具有更强大硬件的设备上获得更好的图像质量
GLKit非常适合这种方法 - 您可以使您的渲染器对象成为标准GLKView实例的委托。您的渲染器类不是将GLKView子类化并实现drawRect:方法,而是使用GLKViewDelegate协议并实现glkView:drawInRect:方法。程序清单3-3演示了在应用程序启动时基于硬件功能选择渲染器类
Listing3-3
- (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;
}
GLKit视图控制器动画化OpenGL ES内容
默认情况下,GLKView对象根据需要呈现其内容。也就是说,使用OpenGL ES绘制的一个关键优点是它能够使用图形处理硬件来连续制作复杂场景 - 诸如游戏和模拟等应用程序很少呈现静态图像。对于这些情况,GLKit框架提供了一个视图控制器类,它为其管理的GLKView对象维护一个动画循环。该循环遵循游戏和模拟中常见的设计模式,分为两个阶段:更新和显示。图3-2显示了动画循环的简化示例。
了解动画循环
对于更新阶段,视图控制器调用其自己的更新方法(或其委托的glkViewControllerUpdate:方法)。在这种方法中,你应该准备绘制下一帧。例如,游戏可能会使用这种方法根据自最后一帧以来接收到的输入事件来确定玩家和敌人角色的位置,科学可视化可能会使用此方法来运行其模拟步骤。如果您需要时间信息来确定应用的下一帧的状态,请使用其中一个视图控制器的时间属性,如timeSinceLastUpdate属性。在图3-2中,更新阶段增加一个角度变量,并使用它来计算变换矩阵。
对于显示阶段,视图控制器调用其视图的显示方法,该方法又调用您的绘图方法。在绘图方法中,您可以向GPU提交OpenGL ES绘图命令以呈现内容。为了获得最佳性能,您的应用程序应在渲染新帧时开始修改OpenGL ES对象,之后提交绘图命令。在图3-2中,显示阶段将着色器程序中的均匀变量设置为在更新阶段计算的矩阵,然后提交绘图命令以呈现新内容。
动画循环按照视图控制器的framePerSecond属性指示的速率在这两个阶段之间进行交替。您可以使用preferredFramesPerSecond属性设置所需的帧速率,以优化当前显示硬件的性能,视图控制器会自动选择接近您的首选值的最佳帧速率。
重要提示:为获得最佳效果,请选择应用程序可以始终如一地实现的帧率平滑,一致的帧速率产生比不规则变化的帧速率更愉快的用户体验。
使用GLKit视图控制器
清单3-4演示了使用GLKViewController子类和GLKView实例渲染动画OpenGL ES内容的典型策略。
@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:方法中,它将这些矩阵提供给着色器程序,并提交绘图命令来渲染行星几何。
使用GLKit开发您的渲染器
除了查看和查看控制器基础设施外,GLKit框架还提供了几个其他功能来简化iOS上的OpenGL ES开发。
处理矢量和矩阵数学
OpenGL ES 2.0及更高版本不提供用于创建或指定变换矩阵的内置函数。相反,可编程着色器提供顶点变换,并使用通用的均匀变量指定着色器输入。 GLKit框架包括矢量和矩阵类型和功能的综合库,针对iOS硬件上的高性能进行了优化。 (见GLKit框架参考。)
从OpenGL ES 1.1固定功能管道迁移
OpenGL ES 2.0及更高版本删除与OpenGL ES 1.1固定功能图形管道相关联的所有功能。 GLKBaseEffect类为OpenGL ES 1.1流水线的转换,照明和阴影阶段提供了Objective-C模拟,GLKSkyboxEffect和GLKReflectionMapEffect类增加了对常见视觉效果的支持。有关详细信息,请参阅这些类的参考文档。
加载纹理数据
GLKTextureLoader类提供了一种简单的方法来将纹理数据从iOS支持的任何图像格式加载到OpenGL ES上下文中,同步或异步。 (请参阅使用GLKit框架加载纹理数据。)