GLKit Tutorial for iOS: Getting started with OpenGL ES
Learn how to use OpenGL ES in iOS in this GLKit tutorial. You’ll go from fresh project to spinning cube rendered using OpenGL and learn all the theory along the way! By Felipe Laso-Marsetti.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
GLKit Tutorial for iOS: Getting started with OpenGL ES
35 mins
Tidying Up
You’ve created several buffers that need to be cleaned up. Add the following method in ViewController
to do so:
private func tearDownGL() {
EAGLContext.setCurrent(context)
glDeleteBuffers(1, &vao)
glDeleteBuffers(1, &vbo)
glDeleteBuffers(1, &ebo)
EAGLContext.setCurrent(nil)
context = nil
}
With the code above, you:
- Set the
EAGLContext
to your context — the one you’ve been working with this whole time. - Delete the VBO, EBO and VAO.
- Nil out the context and also set the
context
variable tonil
to prevent anything else from being done with it.
Now, add the following method:
deinit {
tearDownGL()
}
This is the deinitializer, which simply calls the teardown method.
Build and run the project — notice the same gray screen? The thing about graphics programming and working with OpenGL is that it often requires a lot of initial setup code before you can see things.
Introducing GLKBaseEffect
Now it’s time for the next topic: Shaders.
Modern OpenGL uses what’s known as a programmable pipeline that gives developers full control of how each pixel is rendered. This gives you amazing flexibility and allows for some gorgeous scenes and effects to be rendered. The tradeoff, however, is that there’s more work for the programmer than in the past. Shaders are written in GLSL (OpenGL Shading Language) and need to be compiled before they can be used.
Here’s where GLKit comes to the rescue! With GLKBaseEffect
, you don’t have to worry about writing shaders. It helps you achieve basic lighting and shading effects with little code.
To create an effect, add this variable to ViewController
:
private var effect = GLKBaseEffect()
Then, add this line at the bottom of glkView(_ view:, drawIn rect:)
:
effect.prepareToDraw()
That single line of code binds and compiles shaders for you, and it does it all behind the scenes without writing any GLSL or OpenGL code. Pretty cool, huh? Build your project to ensure it compiles.
With your buffers and effects ready, you now need three more lines of code to tell OpenGL what to draw and how to draw it. Add the following lines right below the line you just added:
glBindVertexArrayOES(vao);
glDrawElements(GLenum(GL_TRIANGLES), // 1
GLsizei(Indices.count), // 2
GLenum(GL_UNSIGNED_BYTE), // 3
nil) // 4
glBindVertexArrayOES(0)
Here’s what each method call does. The call to glBindVertexArrayOES
binds (attaches) your VAO so that OpenGL uses it — and all its the setup and configuration — for the upcoming draw calls.
glDrawElements()
is the call to perform drawing and it takes four parameters. Here’s what each of them does:
- This tells OpenGL what you want to draw. Here, you specify triangles by using the
GL_TRIANGLES
parameter cast as aGLenum
. - Tells OpenGL how many vertices you want to draw. It’s cast to
GLsizei
since this is what the method expects. - Specifies the type of values contained in each index. You use
GL_UNSIGNED_BYTE
becauseIndices
is an array ofGLubyte
elements. - Specifies an offset within a buffer. Since you’re using an EBO, this value is
nil
.
The moment of truth has arrived! Time to build and run your project.
You’ll see something like this:
Not bad, but also not what you were expecting. This isn’t a square being drawn and it’s not rotating. What’s going on? Well, no properties were set on the GLKBaseEffect
, specifically, the transform properties for the projection and model view matrices.
Setting Up the Geometry
Time for some theory…
A projection matrix is how you tell the GPU how to render 3D geometry on a 2D plane. Think of it as drawing a bunch of lines out from your eye through each pixel in your screen. The pixel that is drawn to the screen is determined by whatever the frontmost 3D object each line hits.
GLKit has some handy functions to set up a projection matrix. The one you’re going to use allows you to specify the field of view along the y-axis, the aspect ratio and the near and far planes.
The field of view is like a camera lens. A small field of view (e.g., 10) is like a telephoto lens — it magnifies images by “pulling” them closer to you. A large field of view (e.g., 100) is like a wide-angle lens — it makes everything seem farther away. A typical value to use for this is around 65-75.
The aspect ratio is the aspect ratio that you want to render to (e.g., the aspect ratio of the view). It uses this in combination with the field of view, which is for the y-axis, to determine the field of view along the x-axis.
The near and far planes are the bounding boxes for the “viewable” volume in the scene. If something is closer to the eye than the near plane, or further away than the far plane, it won’t be rendered.
Add the following code to the bottom of glkViewControllerUpdate(_:)
:
// 1
let aspect = fabsf(Float(view.bounds.size.width) / Float(view.bounds.size.height))
// 2
let projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0), aspect, 4.0, 10.0)
// 3
effect.transform.projectionMatrix = projectionMatrix
Here’s what that does:
- Calculates the aspect ratio of the GLKView.
- Uses a built-in helper function in the GLKit math library to create a perspective matrix; all you have to do is pass in the parameters discussed above. You set the near plane to four units away from the eye, and the far plane to 10 units away.
- Sets the projection matrix on the effect’s transform property.
You need to set one more property on the effect — the modelViewMatrix
. This is the transform that is applied to any geometry that the effect renders.
The GLKit math library, once again, comes to the rescue with some handy functions that make performing translations, rotations and scales easy, even if you don’t know much about matrix math. Add the following lines to the bottom of glkViewControllerUpdate(_ controller:)
:
// 1
var modelViewMatrix = GLKMatrix4MakeTranslation(0.0, 0.0, -6.0)
// 2
rotation += 90 * Float(timeSinceLastUpdate)
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(rotation), 0, 0, 1)
// 3
effect.transform.modelviewMatrix = modelViewMatrix
If you look back to where you set up the vertices for the square, remember that the z-coordinate for each vertex was 0. If you tried to render it with this perspective matrix, it wouldn’t show up because it’s closer to the eye than the near plane.
Here’s how you fix that with the code above:
- The first thing you need to do is move the objects backwards. In the first line, you use the
GLKMatrix4MakeTranslation
function to create a matrix that translates six units backwards. - Next, you want to make the cube rotate. You increment an instance variable, which you’ll add in a second, that keeps track of the current rotation and use the
GLKMatrix4Rotate
method to change the current transformation by rotating it as well. It takes radians, so you use theGLKMathDegreesToRadians
method for the conversion. - Finally, you set the model view matrix on the effect’s transform property.
Finally, add the following property to the top of the class:
private var rotation: Float = 0.0
Build and run the app one last time and check out the results:
A rotating square! Perfect!