CAEAGLLayer

当iOS要处理高性能图形绘制,必要时就是OpenGL。应该说它应该是最后的杀手锏,至少对于非游戏的应用来说是的。因为相比Core Animation和UIkit框架,它不可思议地复杂。

OpenGL提供了Core Animation的基础,它是底层的C接口,直接和iPhone,iPad的硬件通信,极少地抽象出来的方法。OpenGL没有对象或是图层的继承概念。它只是简单地处理三角形。OpenGL中所有东西都是3D空间中有颜色和纹理的三角形。用起来非常复杂和强大,但是用OpenGL绘制iOS用户界面就需要很多很多的工作了。

为了能够以高性能使用Core Animation,你需要判断你需要绘制哪种内容(矢量图形,例子,文本,等等),但后选择合适的图层去呈现这些内容,Core Animation中只有一些类型的内容是被高度优化的;所以如果你想绘制的东西并不能找到标准的图层类,想要得到高性能就比较费事情了。

因为OpenGL根本不会对你的内容进行假设,它能够绘制得相当快。利用OpenGL,你可以绘制任何你知道必要的集合信息和形状逻辑的内容。所以很多游戏都喜欢用OpenGL(这些情况下,Core Animation的限制就明显了:它优化过的内容类型并不一定能满足需求),但是这样依赖,方便的高度抽象接口就没了。

在iOS 5中,苹果引入了一个新的框架叫做GLKit,它去掉了一些设置OpenGL的复杂性,提供了一个叫做CLKViewUIView的子类,帮你处理大部分的设置和绘制工作。前提是各种各样的OpenGL绘图缓冲的底层可配置项仍然需要你用CAEAGLLayer完成,它是CALayer的一个子类,用来显示任意的OpenGL图形。

大部分情况下你都不需要手动设置CAEAGLLayer(假设用GLKView),过去的日子就不要再提了。特别的,我们将设置一个OpenGL ES 2.0的上下文,它是现代的iOS设备的标准做法。

尽管不需要GLKit也可以做到这一切,但是GLKit囊括了很多额外的工作,比如设置顶点和片段着色器,这些都以类C语言叫做GLSL自包含在程序中,同时在运行时载入到图形硬件中。编写GLSL代码和设置EAGLayer没有什么关系,所以我们将用GLKBaseEffect类将着色逻辑抽象出来。其他的事情,我们还是会有以往的方式。

在开始之前,你需要将GLKit和OpenGLES框架加入到你的项目中,然后就可以实现清单6.14中的代码,里面是设置一个GAEAGLLayer的最少工作,它使用了OpenGL ES 2.0 的绘图上下文,并渲染了一个有色三角(见图6.15).

清单6.14 用CAEAGLLayer绘制一个三角形

  1. #import "ViewController.h"
  2. #import <QuartzCore/QuartzCore.h>
  3. #import <GLKit/GLKit.h>
  4. @interface ViewController ()
  5. @property (nonatomic, weak) IBOutlet UIView *glView;
  6. @property (nonatomic, strong) EAGLContext *glContext;
  7. @property (nonatomic, strong) CAEAGLLayer *glLayer;
  8. @property (nonatomic, assign) GLuint framebuffer;
  9. @property (nonatomic, assign) GLuint colorRenderbuffer;
  10. @property (nonatomic, assign) GLint framebufferWidth;
  11. @property (nonatomic, assign) GLint framebufferHeight;
  12. @property (nonatomic, strong) GLKBaseEffect *effect;
  13. @end
  14. @implementation ViewController
  15. - (void)setUpBuffers
  16. {
  17. //set up frame buffer
  18. glGenFramebuffers(1, &_framebuffer);
  19. glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
  20. //set up color render buffer
  21. glGenRenderbuffers(1, &_colorRenderbuffer);
  22. glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
  23. glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer);
  24. [self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
  25. glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_framebufferWidth);
  26. glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_framebufferHeight);
  27. //check success
  28. if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
  29. NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER));
  30. }
  31. }
  32. - (void)tearDownBuffers
  33. {
  34. if (_framebuffer) {
  35. //delete framebuffer
  36. glDeleteFramebuffers(1, &_framebuffer);
  37. _framebuffer = 0;
  38. }
  39. if (_colorRenderbuffer) {
  40. //delete color render buffer
  41. glDeleteRenderbuffers(1, &_colorRenderbuffer);
  42. _colorRenderbuffer = 0;
  43. }
  44. }
  45. - (void)drawFrame {
  46. //bind framebuffer & set viewport
  47. glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
  48. glViewport(0, 0, _framebufferWidth, _framebufferHeight);
  49. //bind shader program
  50. [self.effect prepareToDraw];
  51. //clear the screen
  52. glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0);
  53. //set up vertices
  54. GLfloat vertices[] = {
  55. -0.5f, -0.5f, -1.0f, 0.0f, 0.5f, -1.0f, 0.5f, -0.5f, -1.0f,
  56. };
  57. //set up colors
  58. GLfloat colors[] = {
  59. 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
  60. };
  61. //draw triangle
  62. glEnableVertexAttribArray(GLKVertexAttribPosition);
  63. glEnableVertexAttribArray(GLKVertexAttribColor);
  64. glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, vertices);
  65. glVertexAttribPointer(GLKVertexAttribColor,4, GL_FLOAT, GL_FALSE, 0, colors);
  66. glDrawArrays(GL_TRIANGLES, 0, 3);
  67. //present render buffer
  68. glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
  69. [self.glContext presentRenderbuffer:GL_RENDERBUFFER];
  70. }
  71. - (void)viewDidLoad
  72. {
  73. [super viewDidLoad];
  74. //set up context
  75. self.glContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
  76. [EAGLContext setCurrentContext:self.glContext];
  77. //set up layer
  78. self.glLayer = [CAEAGLLayer layer];
  79. self.glLayer.frame = self.glView.bounds;
  80. [self.glView.layer addSublayer:self.glLayer];
  81. self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8};
  82. //set up base effect
  83. self.effect = [[GLKBaseEffect alloc] init];
  84. //set up buffers
  85. [self setUpBuffers];
  86. //draw frame
  87. [self drawFrame];
  88. }
  89. - (void)viewDidUnload
  90. {
  91. [self tearDownBuffers];
  92. [super viewDidUnload];
  93. }
  94. - (void)dealloc
  95. {
  96. [self tearDownBuffers];
  97. [EAGLContext setCurrentContext:nil];
  98. }
  99. @end

图6.15

图6.15 用OpenGL渲染的CAEAGLLayer图层

在一个真正的OpenGL应用中,我们可能会用NSTimerCADisplayLink周期性地每秒钟调用-drawRrame方法60次,同时会将几何图形生成和绘制分开以便不会每次都重新生成三角形的顶点(这样也可以让我们绘制其他的一些东西而不是一个三角形而已),不过上面这个例子已经足够演示了绘图原则了。