If you’ve been following my tutorials on OpenGL ES 2 for iOS, by the time you finish Texturing (Part 6 or so) you’ll have a lot of code crammed into a single UIViewController. This is intentional: the tutorials are largely self-contained, and only create classes and objects where OpenGL itself uses class-like-things.
…but most of the code is re-used from tutorial to tutorial, and it’s getting in the way. It’s time for some refactoring.
UIViewController vs GLKViewController
Recap: Both these classes are provided by Apple; GLKViewController is a UIViewController that’s been modified for OpenGL apps. It has the simplest possible interface – a single “update” method that you override, which is called (automatically) once per frame.
As I explained in the first GL ES 2 tutorial, everything in OpenGL revolves around the Draw call. Most engines recognize this, so we’ll subclass GLKViewController and replace:
- Once per frame: call “update”; no intelligence
…with:
- In viewDidLoad: Correct setup of EAGLContext (required)
- In viewDidLoad: Pre-Create all the Draw calls needed for the app
- Once per frame: For each Draw call in the list:
- …update all the OpenGL per-Draw settings
- …update the OpenGL Shader settings
- …render the Draw call
- …reset any OpenGL state that “must” be turned off immediately after use (e.g. GL_SCISSOR)
Most of this is identical for all apps – we can create a single base class “GLK2DrawCallViewController”, and put the code in there. A few items will be unique from app to app (e.g. the list of Draw calls actually rendered!); we do those with their own public methods so that you can write a subclass per-app which only overrides the methods it needs to change.
GLK2DrawCallViewController.h:
[objc]
@interface GLK2DrawCallViewController : GLKViewController
@property(nonatomic,retain) EAGLContext* localContext;
@property(nonatomic, retain) NSMutableArray* drawCalls;
[/objc]
This class manages the EAGLContext correctly, and populates the drawCalls – most subclasses can ignore these properties. But some special effects will require you to access the super-class’s data here.
[objc]
-(NSMutableArray*) createAllDrawCalls;
-(void) willRenderFrame;
-(void) willRenderDrawCallUsingVAOShaderProgramAndDefaultUniforms:(GLK2DrawCall*) drawCall;
@end
[/objc]
One callback to create the Draw calls – this is essential, if you don’t implement it, nothing will be drawn.
The other two are optional, although the one about “willRender….Using…Uniforms” is needed for most non-trivial shaders (any shader where a Uniform is changing from frame to frame – e.g. whenever your 3D objects are moving).
Internally, it’s all existing code that I’ve simply moved around.
Usage
Usage is exceptionally simple: change your ViewController.h in your app project to extend the new class:
ViewController.h:
[objc]
@interface ViewController : GLK2DrawCallViewController
[/objc]
…and override the special method for creating Draw calls. Move all your tutorial / logic there:
ViewController.h:
[objc]
-(NSMutableArray*) createAllDrawCalls
{
/** All the local setup for the ViewController */
NSMutableArray* result = [NSMutableArray array];
/** — Draw Call 1: clear the background
*/
GLK2DrawCall* simpleClearingCall = [[GLK2DrawCall new] autorelease];
simpleClearingCall.shouldClearColorBit = TRUE;
[simpleClearingCall setClearColourRed:0.5 green:0 blue:0 alpha:1];
[result addObject: simpleClearingCall];
… etc
return result;
}
[/objc]
Source files on Github:
Re-using the “draw one triangle” code
The tutorials frequently use “draw a single triangle” to demonstrate new code. It would be great to re-use this code – we use it a lot for debugging, not just for tutorials. But it can’t live inside the OpenGL API: to keep the API small and easy to learn, it only includes commands for talking to the GPU efficiently.
With desktop GL they solved this problem by creating a separate, “optional” library called GLU. Because GLU is purely software – it has no access to hardware, instead using GL to do all the GPU work – you can copy/paste GLU code and use it with different versions of GL, often with little or no changes.
NOTE: I’m not including this code in the GLKit-Extended library – it’s stuff that would be written differently for different 3D engines, and I want the library to be pure and simple. Instead, I’m including it in the GLKit-Extended Demo project, and you can copy/paste the file direct to your own projects if you choose
Sadly, most GLU code won’t work with GL ES unless you tweak it – GLU uses lots of outdated commands that were stripped to make GL ES cheaper to implement. But we can copy the idea…
CommonGLEngineCode.h:
[objc]
@interface CommonGLEngineCode : NSObject
+(GLK2DrawCall*) drawCallWithUnitTriangleAtOriginUsingShaders:(GLK2ShaderProgram*) shaderProgram;
+(GLK2DrawCall*) drawCallWithUnitCubeAtOriginUsingShaders:(GLK2ShaderProgram*) shaderProgram;
… over time, we’ll add more and more "re-usable" methods here…
@end
[/objc]
Each method assumes that you’ve NOT CHANGED ANYTHING in the camera-setup of OpenGL – you’re using an unconfigured, default frustum etc. i.e. the projection is Orthogonal, and only shows things from (-1,-1,-1) to (1,1,1). It sticks a 1-unit wide triangle (or cube) roughly in the middle of the screen.
If the methods were 100% generic, they’d take another argument: a dictionary of GLK2Attributes, each one “tagged” so the method knows which ones to fill with 3D positions, which to fill with 2D texture co-ordinates, etc. For simplicity, I’ve assumed that you pass in a pre-compiled GLK2ShaderProgram containing a shader pair with:
- REQUIRED: an Attribute named “position” which you’ll read to get the 3D position of each vertex
- OPTIONAL: an Attribute named “textureCoordinate” which you’ll read if you’re using Texturing to get a texture-co-ordinate ranging (0-1) in X (i.e. “u”) and (0-1) in Y (i.e. “v”)
Usage
Combining this with the new GLK2DrawCallViewController above, we get something like:
ViewController.h:
[objc]
-(NSMutableArray*) createAllDrawCalls
{
/** All the local setup for the ViewController */
NSMutableArray* result = [NSMutableArray array];
GLK2DrawCall* dcTri = [CommonGLEngineCode drawCallWithUnitTriangleAtOriginUsingShaders:
[GLK2ShaderProgram shaderProgramFromVertexFilename:@"VertexProjectedWithTexture" fragmentFilename:@"FragmentWithTexture"]];
GLK2Uniform* samplerTexture1 = [dcTri.shaderProgram uniformNamed:@"s_texture1"];
…OPTIONAL (if you’re using texture-mapping in your shaders):
GLK2Texture* texture = [GLK2Texture textureNamed:@"tex2"];
[dcTri setTexture:texture forSampler:samplerTexture1];
[result addObject:dcTri];
…
return result;
}
[/objc]
Source files on Github: