首先我们去创建这个一个AGLKView去继承自UIView类,然后我们进行自定义操作,主要就是去模仿GLKView中的定义
#import
#import
#import
@class EAGLContext;
//协议和GLKViewDelegate类似
@protocol AGLKViewDelegate;
// Type for depth buffer formats.
//深度缓冲区的格式
typedef enum
{
AGLKViewDrawableDepthFormatNone = 0,
AGLKViewDrawableDepthFormat16,
} AGLKViewDrawableDepthFormat;
/////////////////////////////////////////////////////////////////
// This subclass of the Cocoa Touch UIView class uses OpenGL ES
// to render pixel data into a Frame Buffer that shares pixel
// color storage with a Core Animation Layer.
@interface AGLKView : UIView
{
//默认的帧缓冲区
GLuint defaultFrameBuffer;
//颜色缓冲区
GLuint colorRenderBuffer;
//深度缓冲区
GLuint depthRenderBuffer;
}
@property (nonatomic, weak) IBOutlet id
delegate;
//上下文
@property (nonatomic, strong) EAGLContext *context;
@property (nonatomic, readonly) NSInteger drawableWidth;
@property (nonatomic, readonly) NSInteger drawableHeight;
@property (nonatomic) AGLKViewDrawableDepthFormat
drawableDepthFormat;
- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context;
- (void)display;
@end
#pragma mark - AGLKViewDelegate
//让AGLKView的代理对象实现渲染操作的方法
@protocol AGLKViewDelegate <NSObject>
@required
- (void)glkView:(AGLKView *)view drawInRect:(CGRect)rect;
AGLKView的实现方法
#import "AGLKView.h"
#import
@implementation AGLKView
@synthesize delegate;
@synthesize context;
@synthesize drawableDepthFormat;
/////////////////////////////////////////////////////////////////
// This method returns the CALayer subclass to be used by
// CoreAnimation with this view
//设置AGLKView的layer层,由于GLKView就是属于这一类型的CAEAGLLayer,所以我们也要进行设置
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
/////////////////////////////////////////////////////////////////
// This method is designated initializer for the class
//该方法是该类的指定初始化器方法
- (id)initWithFrame:(CGRect)frame context:(EAGLContext *)aContext;
{
if ((self = [super initWithFrame:frame]))
{
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
//设置CAEAGLLayer实例中要用到的OpenGL ES的帧缓存类型的信息
/*
kEAGLDrawablePropertyRetainedBacking 的值为NO,不使用保留背景其实就是告诉Core Animation 不要试图保留任何以前绘制的图像留作以后重用
kEAGLColorFormatRGBA8 告诉Core Animation 用8位来保存层内每个像素的每个颜色元素的值
*/
eaglLayer.drawableProperties =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO],
kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8,
kEAGLDrawablePropertyColorFormat,
nil];
self.context = aContext;
}
return self;
}
/////////////////////////////////////////////////////////////////
// This method is called automatically to initialize each Cocoa
// Touch object as the object is unarchived from an
// Interface Builder .xib or .storyboard file.
- (id)initWithCoder:(NSCoder*)coder
{
if ((self = [super initWithCoder:coder]))
{
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
//设置CAEAGLLayer实例中要用到的OpenGL ES的帧缓存类型的信息
/*
kEAGLDrawablePropertyRetainedBacking 的值为NO,不使用保留背景其实就是告诉Core Animation 不要试图保留任何以前绘制的图像留作以后重用
kEAGLColorFormatRGBA8 告诉Core Animation 用8位来保存层内每个像素的每个颜色元素的值
*/
eaglLayer.drawableProperties =
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO],
kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8,
kEAGLDrawablePropertyColorFormat,
nil];
}
return self;
}
/////////////////////////////////////////////////////////////////
// This method sets the receiver's OpenGL ES Context. If the
// receiver already has a different Context, this method deletes
// OpenGL ES Frame Buffer resources in the old Context and the
// recreates them in the new Context.
- (void)setContext:(EAGLContext *)aContext
{
//由于上下文保存了缓存,所以修改视图的上下文会导致先前创建的所有缓存失效,并需要创建和配置新的缓存,会受缓存操作影响的上下文是在调用openGL ES 函数之前设定为当前上下文的
if(context != aContext)
{ // Delete any buffers previously created in old Context
[EAGLContext setCurrentContext:context];
if (0 != defaultFrameBuffer)
{
glDeleteFramebuffers(1, &defaultFrameBuffer); // Step 7
defaultFrameBuffer = 0;
}
if (0 != colorRenderBuffer)
{
glDeleteRenderbuffers(1, &colorRenderBuffer); // Step 7
colorRenderBuffer = 0;
}
if (0 != depthRenderBuffer)
{
glDeleteRenderbuffers(1, &depthRenderBuffer); // Step 7
depthRenderBuffer = 0;
}
context = aContext;
if(nil != context)
{ // Configure the new Context with required buffers
context = aContext;
[EAGLContext setCurrentContext:context];
glGenFramebuffers(1, &defaultFrameBuffer); // Step 1
glBindFramebuffer( // Step 2
GL_FRAMEBUFFER,
defaultFrameBuffer);
glGenRenderbuffers(1, &colorRenderBuffer); // Step 1
glBindRenderbuffer( // Step 2
GL_RENDERBUFFER,
colorRenderBuffer);
// Attach color render buffer to bound Frame Buffer
//配置当前的帧缓存,去连接colorRenderBuffer,去保存渲染的像素颜色
glFramebufferRenderbuffer(
GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER,
colorRenderBuffer);
// Create any additional render buffers based on the
// drawable size of defaultFrameBuffer
//根据defaultFrameBuffer的drawable size创建任何额外的render缓冲区。
[self layoutSubviews];
}
}
}
/////////////////////////////////////////////////////////////////
// This method returns the receiver's OpenGL ES Context
- (EAGLContext *)context
{
return context;
}
/////////////////////////////////////////////////////////////////
// Calling this method tells the receiver to redraw the contents
// of its associated OpenGL ES Frame Buffer. This method
// configures OpenGL ES and then calls -drawRect:
- (void)display;
{
//设置视图的上下文为当前的上下文
[EAGLContext setCurrentContext:self.context];
//告诉OpenGL ES 让渲染填满整个帧缓存
glViewport(0, 0, self.drawableWidth, self.drawableHeight);
//调用视图的drawRect方法就是用来实现用OpenGL ES的真正的绘图
[self drawRect:[self bounds]];
//让上下文调整外观并且去使用Core Animation合成器把帧缓存的像素颜色渲染缓存和其他相关层混合起来,然后在屏幕上显示一个renderbuffer的内容
[self.context presentRenderbuffer:GL_RENDERBUFFER];
}
/////////////////////////////////////////////////////////////////
// This method is called automatically whenever the receiver
// needs to redraw the contents of its associated OpenGL ES
// Frame Buffer. This method should not be called directly. Call
// -display instead which configures OpenGL ES before calling
// -drawRect:
//在里面调用的是- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;方法
- (void)drawRect:(CGRect)rect
{
if(delegate)
{
//让代理去调用glkView:drawInRect方法
[self.delegate glkView:self drawInRect:[self bounds]];
}
}
/////////////////////////////////////////////////////////////////
// This method is called automatically whenever a UIView is
// resized including just after the view is added to a UIWindow.
//当一个UIView被重新调整,包括视图被添加到一个UIWindow时,这个方法就会被自动调用。
//任何接收到视图重新调整大小的消息,都会去调用layoutSubviews方法
- (void)layoutSubviews
{
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
// Make sure our context is current
//设置视图的上下文为当前的上下文
[EAGLContext setCurrentContext:self.context];
// Initialize the current Frame Buffer’s pixel color buffer 初始化当前帧缓冲区的像素颜色缓存,它会去和Core Animation Layer去共享像素颜色仓库
// so that it shares the corresponding Core Animation Layer’s
// pixel color storage.
//下面这个方法会调整视图的缓存的尺寸以匹配层的尺寸,所以会在setContext设置完上下文之后进行调用
[self.context renderbufferStorage:GL_RENDERBUFFER
fromDrawable:eaglLayer];
//如果深度缓冲区存在,就去删除
if (0 != depthRenderBuffer)
{
glDeleteRenderbuffers(1, &depthRenderBuffer); // Step 7
depthRenderBuffer = 0;
}
GLint currentDrawableWidth = self.drawableWidth;
GLint currentDrawableHeight = self.drawableHeight;
if(self.drawableDepthFormat !=
AGLKViewDrawableDepthFormatNone &&
0 < currentDrawableWidth &&
0 < currentDrawableHeight)
{
//生成深度缓冲区
glGenRenderbuffers(1, &depthRenderBuffer); // Step 1
//绑定深度缓冲区
glBindRenderbuffer(GL_RENDERBUFFER, // Step 2
depthRenderBuffer);
//初始化深度缓冲区
glRenderbufferStorage(GL_RENDERBUFFER, // Step 3
GL_DEPTH_COMPONENT16,
currentDrawableWidth,
currentDrawableHeight);
//连接深度缓冲区
glFramebufferRenderbuffer(GL_FRAMEBUFFER, // Step 4
GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER,
depthRenderBuffer);
}
// Check for any errors configuring the render buffer
//检查配置渲染缓冲区的错误
GLenum status = glCheckFramebufferStatus(
GL_FRAMEBUFFER) ;
if(status != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"failed to make complete frame buffer object %x", status);
}
// Make the Color Render Buffer the current buffer for display
//使颜色渲染缓冲区在当前缓冲区显示
glBindRenderbuffer(GL_RENDERBUFFER, colorRenderBuffer);
}
/////////////////////////////////////////////////////////////////
// This method returns the width in pixels of current context's
// Pixel Color Render Buffer
- (NSInteger)drawableWidth;
{
GLint backingWidth;
//根据OpenGL ES的glGetRenderbufferParameteriv来查询当前上下文的帧缓存的像素颜色的渲染缓存尺寸
glGetRenderbufferParameteriv(
GL_RENDERBUFFER,
GL_RENDERBUFFER_WIDTH,
&backingWidth);
return (NSInteger)backingWidth;
}
/////////////////////////////////////////////////////////////////
// This method returns the height in pixels of current context's
// Pixel Color Render Buffer
//返回高度
- (NSInteger)drawableHeight;
{
GLint backingHeight;
glGetRenderbufferParameteriv(
GL_RENDERBUFFER,
GL_RENDERBUFFER_HEIGHT,
&backingHeight);
return (NSInteger)backingHeight;
}
/////////////////////////////////////////////////////////////////
// This method is called automatically when the reference count
// for a Cocoa Touch object reaches zero.
- (void)dealloc
{
// Make sure the receiver's OpenGL ES Context is not current
if ([EAGLContext currentContext] == context)
{
[EAGLContext setCurrentContext:nil];
}
// Deletes the receiver's OpenGL ES Context
//设置context=nil,就会去删除缓存区对象会去调用setContext方法
context = nil;
}
之后我们再去自定义一个AGLKViewController类,这个类就是拿来模仿GLKViewController类的,很简单里面我们只需要去添加一个CADisplayLink定时器类和可以调节刷新的帧率就可以了。类定义如下所示
#import
#import "AGLKView.h"
@class CADisplayLink;
@interface AGLKViewController : UIViewController
{
CADisplayLink *displayLink;
NSInteger preferredFramesPerSecond;
}
/////////////////////////////////////////////////////////////////
// This property contains the desired frames per second rate for
// drawing. The default is 30.
@property (nonatomic) NSInteger preferredFramesPerSecond;
/////////////////////////////////////////////////////////////////
// This property contains the actual frames per second based
// upon the value for preferredFramesPerSecond property
// and the screen on which the GLKView resides.
// The value is as close to preferredFramesPerSecond as
// possible, without exceeding the screen's refresh rate. This
// value does not account for dropped frames, so it is not a
// measurement of your statistical frames per second. It is the
// static value for which updates will take place.
@property (nonatomic, readonly) NSInteger framesPerSecond;
/////////////////////////////////////////////////////////////////
// Thi property determines whether to pause or resume drawing
// at the rate defined by the framesPerSecond property.
// Initial value is NO.
@property (nonatomic, getter=isPaused) BOOL paused;
@end
类的实现
#import "AGLKViewController.h"
#import
@implementation AGLKViewController
/////////////////////////////////////////////////////////////////
// This constant defines the default number of frame per second
// rate to redraw the receiver's view when the receiver is not
// paused.
//定义了每秒帧数的默认数量
static const NSInteger kAGLKDefaultFramesPerSecond = 30;
/////////////////////////////////////////////////////////////////
// This method is the designated initializer.
// The receiver's Core Animation displayLink instance is created
// and configured to prompt redraw of the receiver's view
// at the default number of frames per second rate.
- (id)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil;
{
if(nil != (self = [super initWithNibName:nibNameOrNil
bundle:nibBundleOrNil]))
{
//创建displayLink,循环调用drawView方法,这个方法是去调用的AGLKView的
//display方法,display方法调用AGLKView的drawRect方法,draw内部则让代理对象
//去调用- (void)glkView:(AGLKView *)view drawInRect:(CGRect)rect方法
displayLink =
[CADisplayLink displayLinkWithTarget:self
selector:@selector(drawView:)];
//设置首选帧率为kAGLKDefaultFramesPerSecond也就是30
self.preferredFramesPerSecond =
kAGLKDefaultFramesPerSecond;
//添加进运行循环
[displayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
self.paused = NO;
}
return self;
}
/////////////////////////////////////////////////////////////////
// This method is called automatically to initialize each Cocoa
// Touch object as the object is unarchived from an
// Interface Builder .xib or .storyboard file.
// The receiver's Core Animation displayLink instance is created
// and configured to prompt redraw of the receiver's view
// at the default number of frames per second rate.
- (id)initWithCoder:(NSCoder*)coder
{
if (nil != (self = [super initWithCoder:coder]))
{
displayLink =
[CADisplayLink displayLinkWithTarget:self
selector:@selector(drawView:)];
self.preferredFramesPerSecond =
kAGLKDefaultFramesPerSecond;
[displayLink addToRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
self.paused = NO;
}
return self;
}
/////////////////////////////////////////////////////////////////
// This method is called when the view controller's view is
// loaded and performs initialization before the view is asked
// to draw.
- (void)viewDidLoad
{
[super viewDidLoad];
// Verify the type of view created automatically by the
// Interface Builder storyboard
AGLKView *view = (AGLKView *)self.view;
//判断view是不是AGLKView类的
NSAssert([view isKindOfClass:[AGLKView class]],
@"View controller's view is not a AGLKView");
view.opaque = YES;
view.delegate = self;
}
/////////////////////////////////////////////////////////////////
// This method is called when the receiver's view appears and
// unpauses the receiver.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.paused = NO;
}
/////////////////////////////////////////////////////////////////
// This method is called when the receiver's view disappears and
// pauses the receiver.
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.paused = YES;
}
/////////////////////////////////////////////////////////////////
// This method is called automatically and allows all standard
// device orientations.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
if ([[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPhone)
{
return (interfaceOrientation !=
UIInterfaceOrientationPortraitUpsideDown);
}
else
{
return YES;
}
}
/////////////////////////////////////////////////////////////////
// This method is called at the receiver's framesPerSecond rate
// when the receiver is not paused and instructs the receiver's
// view to redraw.
- (void)drawView:(id)sender
{
[(AGLKView *)self.view display];
}
/////////////////////////////////////////////////////////////////
// Returns the receiver's framesPerSecond property value.
- (NSInteger)framesPerSecond;
{
return 60 / displayLink.frameInterval;
}
/////////////////////////////////////////////////////////////////
// This method returns the desired frames per second rate at for
// redrawing the receiver's view.
- (NSInteger)preferredFramesPerSecond;
{
return preferredFramesPerSecond;
}
/////////////////////////////////////////////////////////////////
// This method sets the desired frames per second rate at for
// redrawing the receiver's view.
- (void)setPreferredFramesPerSecond:(NSInteger)aValue
{
preferredFramesPerSecond = aValue;
//设置小于1会出错,如果设置为2 就表示为帧率的一半
displayLink.frameInterval = MAX(1, (60 / aValue));
}
/////////////////////////////////////////////////////////////////
// This method returns YES if the receiver is paused and NO
// otherwise. The receiver does not automatically prompt redraw
// of the receiver's view when paused.
- (BOOL)isPaused
{
return displayLink.paused;
}
/////////////////////////////////////////////////////////////////
// This method sets whether the receiver is paused. The receiver
// automatically prompts redraw of the receiver's view
// unless paused.
- (void)setPaused:(BOOL)aValue
{
displayLink.paused = aValue;
}
/////////////////////////////////////////////////////////////////
// This required AGLKViewDelegate method does nothing. Subclasses
// of this class may override this method to draw on behalf of
// the receiver's view.
- (void)glkView:(AGLKView *)view drawInRect:(CGRect)rect;
{
}
@end
接下来就用这两个类来实现画一个三角形,创建一个继承于AGLKViewController的类
类定义
#import "AGLKViewController.h"
#import
@interface OpenGLES_ViewController : AGLKViewController
{
GLuint vertexBufferID;
}
//苹果帮我们封装好的可以相当于一个着色器简化类
@property (strong, nonatomic) GLKBaseEffect *baseEffect;
@end
类的实现,其实主要就是设置顶点数据,传入顶点数据给顶点缓冲区,vertexBufferID这个是顶点缓冲区的标识符,然后再去实现- (void)glkView:(AGLKView *)view drawInRect:(CGRect)rect这个代理方法,然后进行绘制前的配置操作,比如说调用glClear清屏,以及设置允许使用绑定顶点缓存的位置,和设置数据格式调用glVertexAttribPointer方法,然后再调用glDrawArrays进行绘制
#import "OpenGLES_ViewController.h"
@implementation OpenGLES_Ch2_2ViewController
@synthesize baseEffect;
/////////////////////////////////////////////////////////////////
// This data type is used to store information for each vertex
typedef struct {
GLKVector3 positionCoords;
}
SceneVertex;
/////////////////////////////////////////////////////////////////
// Define vertex data for a triangle to use in example
static const SceneVertex vertices[] =
{
{{-0.5f, -0.5f, 0.0}}, // lower left corner
{{ 0.5f, -0.5f, 0.0}}, // lower right corner
{{-0.5f, 0.5f, 0.0}} // upper left corner
};
/////////////////////////////////////////////////////////////////
// Called when the view controller's view is loaded
// Perform initialization before the view is asked to draw
- (void)viewDidLoad
{
[super viewDidLoad];
// Verify the type of view created automatically by the
// Interface Builder storyboard
AGLKView *view = (AGLKView *)self.view;
NSAssert([view isKindOfClass:[AGLKView class]],
@"View controller's view is not a AGLKView");
// Create an OpenGL ES 2.0 context and provide it to the
// view
//创建view的上下文
view.context = [[EAGLContext alloc]
initWithAPI:kEAGLRenderingAPIOpenGLES2];
// Make the new context current
//设置当前的上下文
[EAGLContext setCurrentContext:view.context];
// Create a base effect that provides standard OpenGL ES 2.0
// shading language programs and set constants to be used for
// all subsequent rendering
//创建basseffect对象
self.baseEffect = [[GLKBaseEffect alloc] init];
self.baseEffect.useConstantColor = GL_TRUE;
self.baseEffect.constantColor = GLKVector4Make(
1.0f, // Red
1.0f, // Green
1.0f, // Blue
1.0f);// Alpha
// Set the background color stored in the current context
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // background color
// Generate, bind, and initialize contents of a buffer to be
// stored in GPU memory
glGenBuffers(1, // STEP 1
&vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, // STEP 2
vertexBufferID);
glBufferData( // STEP 3
GL_ARRAY_BUFFER, // Initialize buffer contents
sizeof(vertices), // Number of bytes to copy
vertices, // Address of bytes to copy
GL_STATIC_DRAW); // Hint: cache in GPU memory
}
/////////////////////////////////////////////////////////////////
// GLKView delegate method: Called by the view controller's view
// whenever Cocoa Touch asks the view controller's view to
// draw itself. (In this case, render into a frame buffer that
// shares memory with a Core Animation Layer)
- (void)glkView:(AGLKView *)view drawInRect:(CGRect)rect
{
//准备绘制
[self.baseEffect prepareToDraw];
// Clear back frame buffer (erase previous drawing)
glClear(GL_COLOR_BUFFER_BIT);
// Enable use of positions from bound vertex buffer
glEnableVertexAttribArray( // STEP 4
GLKVertexAttribPosition);
glVertexAttribPointer( // STEP 5
GLKVertexAttribPosition,
3, // three components per vertex
GL_FLOAT, // data is floating point
GL_FALSE, // no fixed point scaling
sizeof(SceneVertex), // no gaps in data
NULL); // NULL tells GPU to start at
// beginning of bound buffer
// Draw triangles using the first three vertices in the
// currently bound vertex buffer
glDrawArrays(GL_TRIANGLES, // STEP 6
0, // Start with first vertex in currently bound buffer
3); // Use three vertices from currently bound buffer
}
/////////////////////////////////////////////////////////////////
// Called when the view controller's view has been unloaded
// Perform clean-up that is possible when you know the view
// controller's view won't be asked to draw again soon.
- (void)viewDidUnload
{
[super viewDidUnload];
// Delete buffers that aren't needed when view is unloaded
if (0 != vertexBufferID)
{
glDeleteBuffers (1, // STEP 7
&vertexBufferID);
vertexBufferID = 0;
}
// Stop using the context created in -viewDidLoad
((AGLKView *)self.view).context = nil;
[EAGLContext setCurrentContext:nil];
}
@end
效果图,画出一个三角形
总结