I’m working on an application that needs to use OpenGL ES v2.0 on iOS and Android. And one problem I’m running into is getting a good in-depth discussion of OpenGL ES shaders on iOS: one book I have covers shaders well but sample code was written for Microsoft Windows. Another book was rated highly on Amazon–but the discussion seems to be geared to people who have never worked with OpenGL or with iOS before.
The basic problem I’m running into is getting a basic OpenGL ES app running. I finally have something, so I’m posting it here for future reference, and in case it works well for other people.
This basically marries the OpenGL ES sample code from both books; incorporating shaders from one with the iOS base of the other.
This relies on GLKit; at this point, with iOS 6 on most devices and iOS 5 on most of the rest, there is no reason not to use GLKit. I’m only using GLKView, however; the types of applications I’m working on do not require constant rendering (like a OpenGL game), so I’m not using GLKViewController, which provides a timer loop which constantly renders frames for continuous smooth animation. (To plug in GLKViewController you just change GSViewController’s parent to GLKViewController, and remove the delegate assignment to self.view in viewDidLoad.
Also note I’m releasing resources on viewDidDisappear rather than on viewDidUnload; iOS 6 deprecates viewDidUnload.
GSViewController nib
This is actually very simple: the GSViewController nib contains one view: a GLKView. Not posted here because it’s so simple.
Note if you have other views and you want to move the GLKView to a different location in the hierarchy, modify the GSViewController.m/h class to provide an outlet to the view.
GSViewController.h
// // GSViewController.h // TestOpenGL // // Created by William Woody on 6/12/13. // Copyright (c) 2013 Glenview Software. All rights reserved. // #import #import @interface GSViewController : UIViewController { EAGLContext *context; GLuint vertexBufferID; GLuint programObject; } @end
This implements the basic example out of the book OpenGL ES 2.0 Programming Guide. Note, however, that instead of creating a ‘UserData’ object and storing that in an ‘ESContext’ (which isn’t on iOS AFAIK), instead, I keep the contents of the ‘UserData’ record (the programObject field), along with a reference to the EAGLContext (the ‘ESContext’ of iOS), and a reference to the vertex buffer I’m using.
GSViewController.m
// // GSViewController.m // TestOpenGL // // Created by William Woody on 6/12/13. // Copyright (c) 2013 Glenview Software. All rights reserved. // #import "GSViewController.h" typedef struct { GLKVector3 postiionCoords; } SceneVertex; static const SceneVertex vertices[] = { { { 0.0f, 0.5f, 0.0f } }, { { -0.5f, -0.5f, 0.0f } }, { { 0.5f, -0.5f, 0.0f } } }; @implementation GSViewController GLuint LoadShader(GLenum type, const char *shaderSrc) { GLuint shader; GLint compiled; shader = glCreateShader(type); if (shader == 0) return 0; glShaderSource(shader, 1, &shaderSrc, NULL); glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (!compiled) { GLint infoLen = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen > 1) { char *infoLog = malloc(sizeof(char) * infoLen); glGetShaderInfoLog(shader, infoLen, NULL, infoLog); NSLog(@"Error compiling shader: %s",infoLog); free(infoLog); } glDeleteShader(shader); return 0; } return shader; } - (BOOL)internalInit { const char vShaderStr[] = "attribute vec4 vPosition; n" "void main() n" "{ n" " gl_Position = vPosition; n" "} n"; const char fShaderStr[] = "precision mediump float; n" "void main() n" "{ n" " gl_FragColor = vec4(1.0,0.0,0.0,1.0); n" "} n"; GLuint vertexShader; GLuint fragmentShader; GLint linked; vertexShader = LoadShader(GL_VERTEX_SHADER,vShaderStr); fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fShaderStr); programObject = glCreateProgram(); if (programObject == 0) return NO; glAttachShader(programObject, vertexShader); glAttachShader(programObject, fragmentShader); glBindAttribLocation(programObject, 0, "vPosition"); glLinkProgram(programObject); glGetProgramiv(programObject, GL_COMPILE_STATUS, &linked); if (!linked) { GLint infoLen = 0; glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen); if (infoLen > 1) { char *infoLog = malloc(sizeof(char) * infoLen); glGetProgramInfoLog(programObject, infoLen, NULL, infoLog); NSLog(@"Error linking shader: %s",infoLog); free(infoLog); } glDeleteProgram(programObject); programObject = 0; return NO; } return YES; } - (void)viewDidLoad { [super viewDidLoad]; GLKView *view = (GLKView *)self.view; NSAssert([view isKindOfClass:[GLKView class]],@"View controller's view is not a GLKView"); context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; view.context = context; view.delegate = self; [EAGLContext setCurrentContext:context]; glClearColor(0.0f,0.0f,0.0f,1.0f); [self internalInit]; // Generate, bind and initialize contents of a buffer to be used in GLU memory glGenBuffers(1, &vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; GLKView *view = (GLKView *)self.view; [EAGLContext setCurrentContext:view.context]; if (0 != vertexBufferID) { glDeleteBuffers(1, &vertexBufferID); vertexBufferID = 0; } view.context = nil; [EAGLContext setCurrentContext:nil]; glDeleteProgram(programObject); programObject = 0; [context release]; context = nil; } - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { glClear(GL_COLOR_BUFFER_BIT); glUseProgram(programObject); glEnableVertexAttribArray(GLKVertexAttribPosition); glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(SceneVertex), NULL + offsetof(SceneVertex, postiionCoords)); glDrawArrays(GL_TRIANGLES, 0, 3); } @end
Note a few things. First, I’m setting up a GLKView for rendering; this is all handled in -viewDidLoad. I’m also setting up a vertex buffer in viewDidLoad; the OpenGL ES 2.0 Programming Guide example puts that initialization in Init() instead. The -viewDidLoad method also replaces some of the setup in the example’s main() method.
Also note that -(BOOL)internalInit replaces most of the rest of Init()’s functionality. Specifically we handle compiling the shaders and creating a program there.
I handle cleanup in -viewDidDisappear; keep in mind the example OpenGL ES application doesn’t do any cleanup. We do it here because our application may continue to run even after our view controller disappears, so we need to be a good citizen.
And our draw routine (glkView:drawInRect:) delegate doesn’t set up the viewport, nor does it need to call to swap buffers.
Yes, there are a lot of problems with this code. It’s a quick and dirty application that I’m using to understand shaders in OpenGL ES 2.0.
But I do get a triangle.
When glCreateShader returns a 0, do you know what might be going wrong and how I might fix it? I’ve been trying to implement a simple “Hello Triangle” and keep getting this error.
LikeLike