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! :]