首页 > 代码库 > Beginning OpenGL ES 2.0 with GLKit Part 1

Beginning OpenGL ES 2.0 with GLKit Part 1

Update 10/24/12: If you’d like a new version of this tutorial fully updated for iOS 6 and Xcode 4.5, check out iOS 5 by Tutorials Second Edition!

Note from Ray: This is the fourth iOS 5 tutorial in the iOS 5 Feast! This tutorial is a free preview chapter from our new book iOS 5 By Tutorials. Enjoy!

iOS 5 comes with a new set of APIs that makes developing with OpenGL much easier than it used to be.

The new set of APIs is collectively known as GLKit. It contains four main sections:

  • GLKView/GLKViewController. These classes abstract out much of the boilerplate code it used to take to set up a basic OpenGL ES project.
  • GLKEffects. These classes implement common shading behaviors used in OpenGL ES 1.0, to make transitioning to OpenGL ES 2.0 easier. They’re also a handy way to get some basic lighting and texturing working.
  • GLMath. Prior to iOS 5, pretty much every game needed their own math library with common vector and matrix manipulation routines. Now with GLMath, most of the common math routines are there for you!
  • GLKTextureLoader. This class makes it much easier to load images as textures to be used in OpenGL. Rather than having to write a complicated method dealing with tons of different image formats, loading a texture is now a single method call!

The goal of this tutorial is to get you quickly up-to-speed with the basics of using OpenGL with GLKit, from the ground up, assuming you have no experience whatsoever. We will build a simple app from scratch that draws a simple cube to the screen and makes it rotate around.

In the process, you’ll learn the basics of using each of these new APIs. It should be a good introduction to GLKit, whether you’ve already used OpenGL in the past, or if you’re a complete beginner!

Note that this tutorial slightly overlaps some of the other OpenGL ES 2.0 tutorials on this site. This tutorial does not assume you have read them first, but if you have you might want to skip over the sections you already know.

 

 

OpenGL ES 1.0 vs OpenGL ES 2.0

Open GL ES 2.0 - Y U Make Me Write Everything?

Before we get started, I wanted to mention that this tutorial will be focusing on OpenGL ES 2.0 (not OpenGL ES 1.0).

If you are new to OpenGL ES programming, here is the difference between OpenGL ES 1.0 and OpenGL ES 2.0:

  • OpenGL ES 1.0 uses a fixed pipeline, which is a fancy way of saying you use built-in functions to set lights, vertexes, colors, cameras, and more.
  • OpenGL ES 2.0 uses a programmable pipeline, which is a fancy way of saying all those built-in functions go away, and you have to write everything yourself.

“OMG!” you may think, “well why would I ever want to use OpenGL ES 2.0 then, if it’s just extra work?!” Although it does add some extra work, with OpenGL ES 2.0 you make some really cool effects that wouldn’t be possible in OpenGL ES 1.0, such as this toon shader (via Imagination Technologies):

Toon Shader with OpenGL ES 2.0

Or even these amazing lighting and shadow effects (via Fabien Sanglard):

Lighting and Shadow Shaders with OpenGL ES 2.0

Pretty cool eh?

OpenGL ES 2.0 is only available on the iPhone 3GS+, iPod Touch 3G+, and all iPads. But the percentage of people with these devices is in the majority now, so it’s well worth using!

OpenGL ES 2.0 does have a bit of a higher learning curve than OpenGL ES 1.0, but now with GLKit the learning curve is much easier, because the GLKEffects and GLKMath APIs lets you easily do a lot of the stuff that was built into OpenGL ES 1.0.

I’d say if you’re new to OpenGL programming, it’s probably best to jump straight into OpenGL ES 2.0 rather than trying to learn OpenGL ES 1.0 and then upgrading, especially now that GLKit is available. And this tutorial should help get you started with the basics! :]

Getting Started

OK, let’s get started!

Create a new project in Xcode and select the iOS\Application\Empty Application Template. We’re selecting the Empty Application template (not the OpenGL Game template!) so you can put everything together from scratch and get a better idea how everything fits together.

Set the Product Name to HelloGLKit, make sure the Device Family is set to iPhone, make sure “Use Automatic Reference Counting” is checked but the other options are not, and click Next. Choose a folder to save your project in, and click Create.

If you run your app, you should see a plain blank Window:

A blank window

The project contains almost no code at this point, but let’s take a quick look to see how it all fits together. If you went through the Storyboard tutorial earlier in this book, this should be a good review.

Open main.m, and you’ll see the first function that is called when the app starts up, unsurprisingly called main:

 

int main(int argc, char *argv[]){    @autoreleasepool {        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));    }}

The last parameter to this method tells UIApplication the class to crate an instance of and use as it’s delegate – in this case, a class called AppDelegate.

So switch over to the only class in the template, AppDelegate.m, and take a look at the method that gets called when the app starts up:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];    // Override point for customization after application launch.    self.window.backgroundColor = [UIColor whiteColor];    [self.window makeKeyAndVisible];    return YES;}

This programatically creates the main window for the app and makes it visible. And that’s it! About as “from scratch” as you can get.

Introducing GLKView

To get started with OpenGL ES 2.0, the first thing we need to do is add a subview to the window that does its drawing with OpenGL. If you’ve done OpenGL ES 2.0 programming before, you know that there was a ton of boilerplate code to get this working – things like creating a render buffer and frame buffer, etc.

But now it’s nice and easy with a new GLKit class called GLKView! Whenever you want to use OpenGL rendering inside a view, you simply add a GLKView (which is a normal subclass of UIView) and configure a few properties on it.

You can then set a class as the GLKView’s delegate, and it will call a method on that class when it needs to be drawn. In this method you can put in your OpenGL commands!

Let’s see how this works. First things first – you need to add a few frameworks to your project to use GLKit. Select your HelloGLKit project in the Project Navigator, select the HelloGLKit target, select Build Phases, Expand the Link Binary With Libraries section, and click the Plus button. From the drop-down list, select the following frameworks and click Add:

  • QuartzCore.framework
  • OpenGLES.framework
  • GLKit.framework

Adding frameworks for GLKit to your Xcode project

Switch to AppDelegate.h, and at the top of the file import the header file for GLKit as follows:

#import <GLKit/GLKit.h>

Next switch to AppDelegate.m, and modify the application:didFinishLaunchingWithOptions to add a GLKView as a subview of the main window:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];     EAGLContext * context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; // 1    GLKView *view = [[GLKView alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // 2    view.context = context; // 3    view.delegate = self; // 4    [self.window addSubview:view]; // 5     self.window.backgroundColor = [UIColor whiteColor];    [self.window makeKeyAndVisible];    return YES;}

The lines marked with comments are the new lines – so let’s go over them one by one.

1. Create a OpenGL context. To do anything with OpenGL, you need to create a EAGLContext.

An EAGLContext manages all of the information iOS needs to draw with OpenGL. It’s similar to how you need a Core Graphics context to do anything with Core Graphics.

When you create a context, you specify what version of the API you want to use. Here, you specify that you want to use OpenGL ES 2.0. If it is not available (such as if the program were run on an iPhone 3G), the app would terminate.

2. Create a GLKView. This creates a new instance of a GLKView, and makes it as large as the entire window.

3. Set the GLKView’s context. When you create a GLKView, you need to tell it the OpenGL context to use, so we specify the one we already created.

4. Set the GLKView’s delegate. This sets the current class (AppDelegate) as the GLKView’s delegate. This means whenever the view needs to be redrawn, it will call a method named glkView:drawInRect on whatever class you specify here. We will implement this inside the App Delegate shortly to contain some basic OpenGL commands to paint the screen red.

5. Add the GLKView as a subview. This line adds the GLKView as a subview of the main window.

Since we marked the App Delegate as GLKView’d delegate, we need to mark it as implementing the GLKViewDelegate protocol. So switch to AppDelegate.h and modify the @interface line as follows:

@interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate>

One step left! Switch back to AppDelegate.m, add the following code right before the @end:

#pragma mark - GLKViewDelegate - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {     glClearColor(1.0, 0.0, 0.0, 1.0);    glClear(GL_COLOR_BUFFER_BIT); }

The first line calls glClearColor to specify the RGB and alpha (transparency) values to use when clearing the screen. We set it to red here.

The second line calls glClear to actually perform the clearing. Remember that there can be different types of buffers, such as the render/color buffer we’re displaying, and others we’re not using yet such as depth or stencil buffers. Here we use the GL_COLOR_BUFFER_BIT to specify what exactly to clear – in this case, the current render/color buffer.

That’s it! Compile and run, and with just 7 lines of code we have OpenGL rendering to the screen!

Hello, GLKit!

Those of you who are completely new to OpenGL might not be very impressed, but those of you who have done this before will be very happy with the convenience here :]

GLKView Properties and Methods

We only set a few properties of GLKView here (context and delegate), but I wanted to mention the other properties and methods on GLKView that might be useful to you in the future.

This is an optional reference section and is for informational purposes only. If you want to keep coding away, feel free to skip to the next section! :]

context and delegate

We already covered these in the previous section, so I won’t repeat here.

drawableColorFormat

Your OpenGL context has a buffer it uses to store the colors that will be displayed to the screen. You can use this property to set the color format for each pixel in the buffer.

The default value is GLKViewDrawableColorFormatRGBA8888, which means 8 bits are used for each color component in the buffer (so 4 bytes per pixel). This is nice because it gives you the widest possible range of colors to work with, which often makes your app look nicer.

But if your app can get away with a lower range of colors, you might want to switch this to GLKViewDrawableColorFormatRGB565, which makes your app consume less resources (memory and processing tieme).

drawableDepthFormat

Your OpenGL context can also optionally have another buffer associated with it called the depth buffer. This helps make sure that objects closer to the viewer show up in front of objects farther away.

The way it works by default is OpenGL stores the closest object to the viewer at each pixel in a buffer. When it goes to draw a pixel, it checks the depth buffer to see if it’s already drawn something closer to the viewer, and if so discards it. Otherwise, it adds it to the depth buffer and the color buffer.

You can set this property to choose the format of the depth buffer. The default value is GLKViewDrawableDepthFormatNone, which means that no depth buffer is enabled at all.

But if you want this feature (which you usually do for 3D games), you should choose GLKViewDrawableDepthFormat16 or GLKViewDrawableDepthFormat24. The tradeoff here is with GLKViewDrawableDepthFormat16 your app will use less resources, but you might have rendering issues when objects are very close to each other.

drawableStencilFormat

Another optional buffer your OpenGL context can have is the stencil buffer. This helps you restrict drawing to a particular portion of the screen. It’s often useful for things like shadows – for example you might use the stencil buffer to make sure the shadows to be cast on the floor.

The default value for this property is GLKViewDrawableStencilFormatNone, which means there is no stencil buffer, but you can enable it by setting it to the only alternative – GLKViewDrawableStencilFormat8.

drawableMultisample

The last optional buffer you can set up through a GLKView property is the multisampling buffer. If you ever try drawing lines with OpenGL and notice “jagged lines”, multisampling can help with this issue.

Basically what it does is instead of calling the fragment shader one time per pixel, it divides up the pixel into smaller units and calls the fragment shader multiple times at smaller levels of detail. It then merges the colors returned, which often results in a much smoother look around edges of geometry.

Be careful about setting this because it requires more processing time and meomry for your app. The default value is GLKViewDrawableMultisampleNone, but you can enable it by setting it to the only alternative – GLKViewDrawableMultisample4X.

drawableHeight/drawableWidth

These are read-only properties that indicate the integer height and width of your various buffers. These are based on the bounds and contentSize of the view – the buffers are automatically resized when these change.

snapshot

This is a handy way to get a UIImage of the view’s current context.

bindDrawable

OpenGL has yet another buffer called a frame buffer, which is basically a collection of all the other buffers we talked about (color buffer, depth buffer, stencil buffer etc).

Before your glkView:drawInRect is called, GLKit will bind to the frame buffer it set up for you behind the scenes. But if your game needs to change to a different frame buffer to perform some other kind of rendering (for example, if you’re rendering to another texture), you can use the bindDrawable method to tell GLKit to re-bind back to the frame buffer it set up for you.

deleteDrawable

GLKView and OpenGL take a substantial amount of memory for all of these buffers. If your GLKView isn’t visible, you might find it useful to deallocate this memory temporarily until it becomes visible again. If you want to do this, just use this method!

Next time the view is drawn, GLKView will automatically re-allocate the memory behind the scenes. Quite handy, eh?

enableSetNeedsDisplay and display

I don’t want to spoil the surprise – we’ll explain these in the next section! :]

Updating the GLKView

Let’s try to update our GLKView periodically, like we would in a game. How about we make the screen pulse from red to black, kind of like a “Red Alert” effect!

Go to the top of AppDelegate.m and modify the @implementation line to add two private variables as follows:

@implementation AppDelegate {    float _curRed;    BOOL _increasing;}

And initialize these in application:didFinishLaunchingWithOptions:

_increasing = YES;_curRed = 0.0;

Then go to the glkView:drawInRect method and update it to the following:

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {     if (_increasing) {        _curRed += 0.01;    } else {        _curRed -= 0.01;    }    if (_curRed >= 1.0) {        _curRed = 1.0;        _increasing = NO;    }    if (_curRed <= 0.0) {        _curRed = 0.0;        _increasing = YES;    }     glClearColor(_curRed, 0.0, 0.0, 1.0);    glClear(GL_COLOR_BUFFER_BIT); }

Every time drawInRect is called, it updates the _curRed value a little bit based on whether it’s increasing or decreasing. Note that this code isn’t perfect, because it doesn’t take into effect how long it takes between calls to drawInRect. This means that the animation might be faster or slower based on how quickly drawInRect is called. We’ll discuss a way to fix this later in the tutorial.

Compile and run and… wait a minute, nothing’s happening!

By default, the GLKView only updates itself on an as-needed basis – i.e. when views are first shown, the size changes, or the like. However for game programming, you often need to redraw every frame!

We can disable this default behavior of GLKView by setting enableSetNeedsDisplay to false. Then, we can control when the redrawing occurs by calling the display method on GLKView whenever we want to update the screen.

Ideally we would like to synchronize the time we render with OpenGL to the rate at which the screen refreshes.

Luckily, Apple provides an easy way for us to do this with CADisplayLink! It’s really easy to use so let’s just dive in. First add this import to the top of AppDelegate.m:

#import <QuartzCore/QuartzCore.h>

Then add these lines to application:didFinishLaunchingWithOptions:

view.enableSetNeedsDisplay = NO;CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

Then add a new render function as follows:

- (void)render:(CADisplayLink*)displayLink {    GLKView * view = [self.window.subviews objectAtIndex:0];    [view display];}

Compile and run, and you should now see a cool pulsating “red alert” effect!

Introducing GLKViewController

You know that code we just wrote in that last section? Well you can just forget about it, because there’s a much easier way to do so by using GLKViewController :]

The reason I showed you how to do it with plain GLKView first was so you understand the point behind using GLKViewController – it saves you from writing that code, plus adds some extra neat features that you would have had to code yourself.

So try out GLKViewController. Modify your application:didFinishLaunchingWithOptions to look like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];     EAGLContext * context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];    GLKView *view = [[GLKView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];    view.context = context;    view.delegate = self;    //[self.window addSubview:view];     _increasing = YES;    _curRed = 0.0;     //view.enableSetNeedsDisplay = NO;    //CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];    //[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];      GLKViewController * viewController = [[GLKViewController alloc] initWithNibName:nil bundle:nil]; // 1    viewController.view = view; // 2    viewController.delegate = self; // 3    viewController.preferredFramesPerSecond = 60; // 4    self.window.rootViewController = viewController; // 5     self.window.backgroundColor = [UIColor whiteColor];    [self.window makeKeyAndVisible];    return YES;}

Feel free to delete the commented lines – I just commented them out so it is easy to see what’s no longer needed. There are also four new lines (marked with comments):

1. Create a GLKViewController. This creates a new instance of a GLKViewController programatically. In this case, it has no XIB associated.

2. Set the GLKViewController’s view. The root view of a GLKViewController should be a GLKView, so we set it to the one we already created.

3. Set the GLKViewController’s delegate. We set the current class (AppDelegate) as the delegate of the GLKViewController. This means that the GLKViewController will notify us each frame so we can run game logic, or when the game pauses (a nice built-in feature of GLKViewController we’ll demonstrate later).

4. Set the preferred FPS. The GLKViewController will call your draw method a certain number of times per second. This number gives a hint to the GLKViewController how often you’d like to be called. Of course, if your game takes a long time to render frames, the actual number may be lower than this.

The default value is 30 FPS. Apple’s guidelines are to set this to whatever your app can reliably support to the frame rate is consistent and doesn’t seem to stutter. This app is very simple so can easily run at 60 FPS, so we set it to that.

Also as an FYI, if you want to see the actual number of times the OS will attempt to call your update/draw methods, check the read-only framesPerSecond property.

5. Set the rootViewController. We want this view controller to be the first thing that shows up, so we add it as the rootViewController of the window. Note that we no longer need to add the view as a subview of the window manually, because it’s the root view of the GLKViewController.

Notice that we no longer need the code to run the render loop and tell the GLView to refresh each frame – GLKViewController does that for us in the background! So go ahead and comment out the render method as well.

Also remember that we set the GLKViewController’s delegate to the current class (AppDelegate), so let’s mark it as implementing GLKViewControllerDelegate. Switch to AppDelegate.h and replace the @implementation with the following line:

 

Beginning OpenGL ES 2.0 with GLKit Part 1