Kodeco Forums

OpenGL Tutorial for iOS: OpenGL ES 2.0

In this OpenGL tutorial you'll get hands-on experience with OpenGL ES 2.0 and will create a simple "Hello, World" app that displays some simple geometry.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/3063-opengl-tutorial-for-ios-opengl-es-2-0

A Swift implementation of the code can be found here:

https://github.com/drouck/HelloOpenGL_Swift

Cool, thanks @drouck! :]

I tried the same code with Xcode 7. It builds successfully but crashes during execution. I always get the following error: ā€˜Application windows are expected to have a root view controller at the end of application launch’

Problem solved! I added the following line of code inside the function "- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

NSArray *windows = [[UIApplication sharedApplication] windows];
for(UIWindow window in windows) {
NSLog(@ā€œwindow: %@ā€,window.description);
if(window.rootViewController == nil){
UIViewController
vc = [[UIViewController alloc]initWithNibName:nil bundle:nil];
window.rootViewController = vc;
}
}

Start up Xcode, and go to File\New\New Project. Select iOS\Application\Window-based Application, and click Next.

There is no longer such a template in Xcode. What should you use instead? Also, the tutorial says nothing about what language you should specify under the ā€œLanguageā€ dropdown during the new project wizard.
Also there is no info on how to compile things.

Tutorial looks promising, however I’m sure that people just drop out because they cannot progress further. I don’t have the time or energy to learn XCODE (since I’m not even using it for learning shaders - my shaders will be previewed in another application).

There are tons of tutorials about OpenGL ES 2.0, but this one is the best. Thank you Ray, I use what I learned from your tutorial and made a RPG game render example, if someone who also learned OpenGL ES 2.0 from this article and want to make a game based on this tutorial but have no idea how to start, maybe can have a look at my example: https://github.com/huxingyi/libphone/tree/master/samples/rpggame#overview

Great tutorial Ray!
Is there an updated version for Xcode 7+? I’m not able render into the window. I just get a white screen.

Thanks Ray!

I just completed the tutorial with Xcode 7. There were a few gotchas, so I hope this saves someone the trouble next time.

  1. There is no Window-based application template. Use the single view template.
  2. Delete the Main.storyboard file.
  3. Clear the reference on the project’s target’s Main Interface field.
  4. Delete ViewController.h and ViewController.m.
  5. Replace your app delegate’s application:didFinishLaunchingWithOptions: method with the code at the bottom of this post.
  6. Use Ray’s zipped Cocos3D file, but you’re going to have to fix a few things.
  7. Add #import <UIKit/UIColor.h> at the top of CC3Foundation.h.
  8. Change the calls to abs in CC3Kazmath.c to fabs.
  9. Add -fno-objc-arc to CC3Kazmath.c, CC3Foundation.m, CC3GLMatrix.m, CC3Math.m in the project’s target’s Build Phases Compile Sources section.

Here’s the code for AppDelegate.m.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    UIViewController *vc = [[UIViewController alloc] init];
    [vc setView:[[OpenGLView alloc] initWithFrame:[_window bounds]]];
    
    [_window setRootViewController:vc];
    [_window makeKeyAndVisible];
    return YES;
}

Great tutorial! It all worked great for me (with some of the mods mentioned in the prior comments), right up until adding the depth buffer, at which point I get a white screen on execution. Commenting out the call to [self setupDepthBuffer] from initWithFrame restores the functionality (a rotating cube that you can ā€œsee inside ofā€).

xcode 8.3.2
iOS 10.3.1 (real device)

Thanks for sharing this excellent tutorial. Do you have any examples on how to dynamically render text? E.g. ā€œHello Worldā€

Thanks Ray! I’ve followed the tutorial using Swift but stuck with the Creating Vertex Buffer Objects and Updated Render Code, here is my OpenGLView.swift code:

//
// OpenGLView.swift
// LearnOpenGLES
//
// Created by tomisacat on 08/06/2017.
// Copyright Ā© 2017 tomisacat. All rights reserved.
//

import Foundation
import OpenGLES
import GLKit
import QuartzCore

struct Vertex {
var Position:(Float, Float, Float)
var Color: (Float, Float, Float, Float)
}

var vertices = [
Vertex(Position: (1, -1, 0), Color: (1, 0, 0, 1)),
Vertex(Position: (1, 1, 0) , Color: (0, 1, 0, 1)),
Vertex(Position: (-1, 1, 0) , Color: (0, 0, 1, 1)),
Vertex(Position: (-1, -1, 0), Color: (0, 0, 0, 1))
]

var indices: [GLubyte] = [
0, 1, 2,
2, 3, 0
]

class OpenGLView: UIView {
fileprivate var eaglLayer: CAEAGLLayer!
fileprivate var context: EAGLContext!
fileprivate var colorRenderBuffer: GLuint = 0
fileprivate var positionSlot: GLuint = 0
fileprivate var colorSlot: GLuint = 0

override init(frame: CGRect) {
    super.init(frame: frame)
    
    callSetups()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    
    callSetups()
}

override class var layerClass: AnyClass {
    return CAEAGLLayer.self
}

// MARK: private setup

private func compileShaders() {
    let vertexShader: GLuint = compile(shader: "SimpleVertex", withType: GLenum(GL_VERTEX_SHADER))
    let fragmentShader: GLuint = compile(shader: "SimpleFragment", withType: GLenum(GL_FRAGMENT_SHADER))
    
    let programHandle: GLuint = glCreateProgram()
    glAttachShader(programHandle, vertexShader)
    glAttachShader(programHandle, fragmentShader)
    glLinkProgram(programHandle)
    
    var linkSuccess: GLint = GL_FALSE
    glGetProgramiv(programHandle, GLenum(GL_LINK_STATUS), &linkSuccess)
    if linkSuccess == GL_FALSE {
        let messages: UnsafeMutablePointer<GLchar> = UnsafeMutablePointer.allocate(capacity: 256 * MemoryLayout<GLchar>.size)
        var length: GLsizei = 0
        glGetProgramInfoLog(programHandle, GLsizei(MemoryLayout.size(ofValue: messages)), &length, messages)
        if let log = NSString(utf8String: messages) {
            print(log)
        }
    }
    
    glUseProgram(programHandle)
    
    positionSlot = GLuint(glGetAttribLocation(programHandle, "Position"))
    colorSlot = GLuint(glGetAttribLocation(programHandle, "SourceColor"))
    glEnableVertexAttribArray(positionSlot)
    glEnableVertexAttribArray(colorSlot)
}

private func compile(shader: String, withType type: GLenum) -> GLuint {
    guard let shaderPath = Bundle.main.path(forResource: shader, ofType: "glsl") else {
        return 0
    }
    
    do {
        let shaderString = try String(contentsOfFile: shaderPath)
        
        let shaderHandle = glCreateShader(type)
        var shaderStringUTF8 = UnsafePointer(shaderString.cString(using: .utf8))
        var shaderStringLength = GLint(shaderString.characters.count)
        
        glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength)
        glCompileShader(shaderHandle)
        
        var compileSuccess: GLint = GL_FALSE
        glGetShaderiv(shaderHandle, GLenum(GL_COMPILE_STATUS), &compileSuccess)
        if compileSuccess == GL_FALSE {
            let messages: UnsafeMutablePointer<GLchar> = UnsafeMutablePointer.allocate(capacity: 256 * MemoryLayout<GLchar>.size)
            var length: GLsizei = 0
            glGetShaderInfoLog(shaderHandle, GLsizei(MemoryLayout.size(ofValue: messages)), &length, messages)
            if let log = NSString(utf8String: messages) {
                print(log)
            }
            
            return 0
        }
        
        return shaderHandle
    } catch {
        return 0
    }
}

//

private func setupVBOs() {
    var vertexBuffer: GLuint = 0
    glGenBuffers(1, &vertexBuffer)
    glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)
    glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout.size(ofValue: vertices), vertices, GLenum(GL_STATIC_DRAW))
    
    var indexBuffer: GLuint = 0
    glGenBuffers(1, &indexBuffer)
    glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), indexBuffer)
    glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER), MemoryLayout.size(ofValue: indices), indices, GLenum(GL_STATIC_DRAW))
}

private func render() {
    glClearColor(0, 104/255, 55/255, 1)
    glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
    
    glViewport(0, 0, GLsizei(frame.size.width), GLsizei(frame.size.height))
    
    glVertexAttribPointer(positionSlot, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<Vertex>.size), nil)
    glVertexAttribPointer(colorSlot, 4, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<Vertex>.size), UnsafePointer<Float>(bitPattern: 3 * MemoryLayout<Float>.size))
    
    glDrawElements(GLenum(GL_TRIANGLES), GLsizei(indices.count), GLenum(GL_UNSIGNED_BYTE), nil)
    
    context.presentRenderbuffer(Int(GL_RENDERBUFFER))
}

//    private func render() {
//        glClearColor(0, 104/255, 55/255, 1)
//        glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
//        context.presentRenderbuffer(Int(GL_RENDERBUFFER))
//    }

//

private func callSetups() {
    setupLayer()
    setupContext()
    setupRenderBuffer()
    setupFrameBuffer()
    
    compileShaders()
    setupVBOs()
    
    render()
}

private func setupLayer() {
    eaglLayer = self.layer as! CAEAGLLayer
    eaglLayer.isOpaque = true
}

private func setupContext() {
    context = EAGLContext(api: .openGLES3)
    if context == nil {
        context = EAGLContext(api: .openGLES2)
    }
    
    EAGLContext.setCurrent(context)
}

private func setupRenderBuffer() {
    glGenRenderbuffers(1, &colorRenderBuffer)
    glBindRenderbuffer(GLenum(GL_RENDERBUFFER), colorRenderBuffer)
    context.renderbufferStorage(Int(GL_RENDERBUFFER), from: eaglLayer)
}

private func setupFrameBuffer() {
    var frameBuffer: GLuint = 0
    glGenFramebuffers(1, &frameBuffer)
    glBindFramebuffer(GLenum(GL_FRAMEBUFFER), frameBuffer)
    glFramebufferRenderbuffer(GLenum(GL_FRAMEBUFFER), GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_RENDERBUFFER), colorRenderBuffer)
}

}

What I hope is exactly like your tutorial, a gradient image show on screen, but I just got a green image like the previous step. What’s wrong with my code?

If there is an update version for Swift will be awesome!

Thanks for great tutorial! Here is project for Objective-C, using XCode 8.2.1 https://gitlab.com/kirstone/OpenGL-ES-Tutorial-RayWenderlich

This tutorial is more than six months old so questions are no longer supported at the moment for it. We will update it as soon as possible. Thank you! :]