集成并启动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
还提供了其它功能来简化OpenGL
2.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框架加载纹理数据)