集成并启动OpenGL ES应用程序
这些章节概述了OpenGL ES如何集成到iOS中,以及在iOS设备上启动并运行第一个OpenGL ES应用所需的所有详细信息。
配置OpenGL ES上下文
每个OpenGL ES实现都提供了一种创建渲染上下文的方法,来管理OpenGL ES规范要求的状态。通过将这个状态置于上下文中,多个app可以简单的共享图形硬件,而不会干扰其他状态
EAGL是OpenGL ES渲染上下文的iOS实现
在app调用任何OpenGL ES函数之前,必须初始化EAGLContext对象。其还提供了用于将OpenGL ES和Core Animation集成的方法
OpenGL ES函数调用的目标即为 Current Context
iOS中的每个线程都有其上下文 当调用OpenGL ES函数时,这是其状态被调用更改的上下文
通过EAGLContext 的 setCurrentContext方法设置当前上下文[EAGLContext setCurrentContext: myContext];
而通过调用EAGLContext的currentContext来检索线程的当前上下文
当应用程序在同一线程的两个或者多个上下文间主动切换时,请在将新的上下文设置为当前上下文前,调用
glFlush函数,可以确保将先前提交的命名即使交付给图形硬件
OpenGL ES对当前上下文的EAGLContext对象具有强引用。当设置setCurrentContext时,就不在引用先前的context。因此,如果想context在不是当前上下文时不被dealloc,app就要保持对这些对象的强引用
针对特定版本的OpenGL ES有特定的上下文
一个EAGLContext仅支持OpenGL ES的一个版本。
应用会在创建和初始化EAGLContext时决定要支持的OpenGL ES版本。如果设备不支持该版本,则initWithAPI:方法会返回nil,因此应用必须进行测试来确保上下文在使用前已经初始化成功。因此,可以再app中支持多版本的OpenGL ES,先尝试最新版本的渲染上下文,如果返回为nil,则尝试初始化旧版本的上下文
EAGLContext* CreateBestEAGLContext()
{
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (context == nil) {
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
return context;
}
context的API属性@property(readonly) EAGLRenderingAPI API;说明了上下文支持的OpenGL ES版本。app应该测试context的api属性,来选择正确的渲染路径,我们通常为每个渲染路径创建一个类,然后app会在初始化时测试context并创建一次渲染器。
EAGL Sharegroup 为上下文管理OpenGL ES对象
尽管Context保持了OpenGL ES状态,但是并不直接管理OpenGL ES对象。OpenGL ES对象是由EAGLSharegroup对象进行创建和维护。每个context都包含一个EAGLSharegroup对象,将对象的创建委托给该对象
当多个context连接到同一Sharegroup时(可见sharegroup的优势),任一context创建的OpenGL ES对象,在所有context中均是可用的。如果在创建的context与其它context绑定了相同的对象标识符,则就引用相同的OpenGL ES对象。
移动设备资源短缺,多个context创建相同内容的多个副本是浪费的,共享资源可以更好地利用设备上的可用图形资源
sharegroup是不透明对象,并没有app可以调用的属性和方法,使用sharegroup的context对其保持强引用

Sharegroups在两种特定情况下很有用
- 当context之间共享的大多数资源都不变时
- 当想要app能在渲染主线程外的其它线程创建新的OpenGL ES对象时。此时,第二个context在单独线程运行,并且用于获取数据和创建加载资源。加载资源后,第一个线程可以绑定到该对象并立即使用它。
GLKTextureLoader就是使用这种方式实现异步纹理加载
如果要创建引用同一sharegroup的多个context,可以通过initWithAPI:来初始化第一个context,此时将会自动创建sharegroup。然后通过调用initWithAPI:sharegroup:将其他context初始化为第一个context的共享组。
同一sharegroup的所有context必须使用相同的OpenGL ES版本
EAGLContext* firstContext = CreateBestEAGLContext();
EAGLContext* secondContext = [[EAGLContext alloc] initWithAPI:[firstContext API] sharegroup: [firstContext sharegroup]];
当多个context共享sharegroup时,应用有责任管理OpenGL ES的状态更改,
- 未修改对象时,app可同时在多个上下文中访问该对象
- 通过发送到context的命令修改对象时,不能在其他context中读取或者修改对象
- 当修改对象后,所有context必须重新绑定对象来查看其更改。在context对其绑定之前 引用对象,该对象内容是undefined的
应用更新OpenGL ES对象应遵循的步骤:
- 在可能正在使用该对象的每个context中调用
glFlush - 在想要修改对象的context中,调用一个或者多个OpenGL ES函数来更改对象
- 在收到状态修改命令的上下中调用
glFlush - 在所有的其它上下文中,重新绑定该对象标识符
共享对象的另一种方法是使用单个渲染上下文,但使用多个目标帧缓冲区。在渲染时,您的应用绑定了适当的帧缓冲区并根据需要渲染其帧。由于所有OpenGL ES对象都是从单个上下文中引用的,因此它们将看到相同的OpenGL ES数据。此模式使用的资源较少,但是仅对单线程应用有用,在该应用中您可以仔细控制上下文的状态
使用GLKit绘图
GLKit framework提供view和viewController,消除了对OpenGL内容绘制和动画必须设置和维护的代码。
GLKView类管理OpenGL ES基础结构为绘图代码提供位置
GLKViewController类提供了渲染循环 来使GLKit中的OpenGL内容可以平滑动画。
GLKit帮助我们主要将精力集中在OpenGL ES渲染代码上,并使app可以快读启动并运行。
GLKit还提供了其它功能来简化OpenGL2.0和3.0的开发
GLKit View按需绘制OpenGL内容
GLKView提供了基于OpenGL的等效于标准的UIView的绘图周期。类似UIView自动配置其图形上下文,方便我们实现drawRect:方法只需要执行Quartz 2D命令,而GLKView实例自动进行自身配置,以便于我们绘制方法仅需要执行OpenGL绘制命令。GLKView类通过维护了保存OpenGLES绘制命令结果的帧缓冲区对象,然后在绘制方法return后自动将其呈现给Core Animation
像标准的UIKit UIView一样,GLKit View按需呈现其内容。首次显示View时,它会调用您的绘制方法--Core Animation缓存渲染的输出,并在无论何时显示View时将其展示。当您想要更改View的内容时,请调用其setNeedsDisplay方法,然后该视图再次调用您的绘图方法,缓存生成的图像,并将其显示在屏幕上。当用于渲染图像的数据不经常更改或仅响应用户操作而更改时,此方法很有用。通过仅在需要时渲染新的视图内容,可以节省设备的电池电量,并为设备留出更多时间执行其他操作。

创建和配置GLKit View
可以通过代码或者XIB创建GLKView对象,但是在试用其之前必须将其与EAGLContext对象相关联
- 当以代码方式创建时,请先创建一个context然后将其传递给view的
initWithFrame:context:方法 - 如果容storyboard中加载视图后,创建一个context并将其设置为视图的context属性值
GLKView会自动创建和配置它自己的OpenGL的framebuffer对象和renderbuffer。可以使用drawable属性控制这些对象的属性,如果更改GLKit View的size、scale factor或者drawable属性,则会在下次绘制其内容时自动删除并重新创建适当的framebuffer和renderbuffer
- (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启用多重采样。多重采样是一种抗锯齿形式,可以平滑锯齿边缘,以占用更多内存和和片段处理为代价,提高大多数3Dapp的图像质量,因此,启用多重采样,需要始终测试应用性能是可接受的
使用GLKit绘制
绘制OpenGL ES内容的3个步骤:
- 准备OpenGL ES基础结构
- 发出绘制命令
- 将渲染的内容呈现给CoreAnimation进行显示
而GLKView实现了第一和第三步
//第二步 绘制示例
- (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提示可以丢弃任何现有帧缓冲区内容,避免将先前内容加载到内存中的昂贵内存操作。因此,为了确保最佳性能,应始终在绘制之前调用此函数。
GLKitView类能为OpenGL ES绘图提供一个简单的接口,因为其管理渲染进程的标准部分:
- 在调用绘图方法之前:
- 将其
EAGLContext设置为当前上下文 - 根据当前size、scale factor和drawable属性创建
framebuffer对象和renderbuffer - 将
framebuffer对象绑定为当前绘图命令的目标 - 设置OpenGL ES的Viewport来匹配
framebuffer size
- 将其
- 在绘制方法返回后:
- 解析多重采样缓冲区(如果启用了多重采样)
- 丢弃不再需要内容的renderbuffers
- 将renderbuffer内容呈现给
Core Animation进行缓存和显示
使用Delegate对象渲染
许多OpenGL ES app在自定义类中实现渲染代码。这种方法的优点是,通过为每个渲染算法定义一个不同的渲染器类,可以轻松支持多种渲染算法。共享通用功能的渲染算法可以从super class继承它。例如,您可能使用不同的渲染器类来支持OpenGL ES 2.0和3.0。或者,您可以使用它们来自定义渲染,以在具有更强大硬件的设备上获得更好的图像质量。
GLKit非常适合上面所说的方法,可以使你的渲染对象称为GLKView实例的代理。render class继承GLKViewDelegate协议并且实现glkView:drawRect:方法,而不是子类化GLKView实现drawRect:方法。
//在app启动时,基于硬件功能选择渲染器类:
- (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 View Controller使OpenGL ES动画内容
GLKit提供了一个视图控制器,该类维护其管理的GLKView对象的一个动画循环。这个循环遵循在游戏和模拟中常见的设计模式,分为两个阶段:更新和展示

动画循环的理解
在更新阶段,视图控制器调用自己的update方法(或者其delegate的glkViewControllerUpdate:方法)。在这个方法中,应该准备绘画下一帧。也可以使用ViewController的timing属性例如timeSinceLastUpdate来决定下一帧的状态。
在显示阶段,viewController调用view的display方法,其又会调用你的绘制方法。在绘制方法中,将OpenGL ES命令提交给GPU来渲染内容。为了获得最佳性能,应用应该在渲染新帧开始时修改OpenGL ES对象,然后提交绘图命令。
animation loop在ViewController的framePerSecond属性指示的速率在两个阶段之间交替。可以使用preferredFramesPerSecond属性设置所需的帧速率,为了优化当前显示的硬件性能,控制器会自动选择接近首选值的最佳帧速率
使用GLKit View Controller
使用GLKViewController和GLKView实例渲染动态OpenGL ES内容
@implementation PlanetViewController // subclass of GLKViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 加载PlantViewController类 以及标准的GLKView实例 及其可绘制属性
GLKView *view = (GLKView *)self.view;
//创建和设置context
view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
// 设置动画循环的帧率
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
// 计算旋转的行星所需要变换矩阵
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeRotation(_rotation, 0.0f, 1.0f, 0.0f);
_normalMatrix = GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL);
_modelViewProjectionMatrix = GLKMatrix4Multiply(_projectionMatrix, modelViewMatrix);
}
//视图控制器 自动称为其View的delegate 实现动画循环的显示阶段
//将计算出来的矩阵提供给着色器程序 并提交绘制
- (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
使用GLKit开发渲染器
除了view和viewController结构,GLKit 框架还提供了一些其他特性来简化OpenGL ES的开发
处理向量和数学矩阵
OpenGL ES 2.0及更高版本不提供用于创建或指定转换矩阵的内置函数。而是,可编程着色器提供顶点转换,您可以使用通用的uniform variables指定着色器输入。 GLKit框架包括一个全面的矢量,矩阵类型和函数库,针对iOS硬件上的高性能进行了优化。参考GLKit Framework Reference.
从OpenGL ES 1.1固定功能管道迁移
加载纹理数据
GLKTextureLoader类提供了一种简单的方法,可以将纹理数据从iOS支持的任何图像格式同步或异步加载到OpenGL ES上下文中。 (请参阅使用GLKit框架加载纹理数据)