Basic ios development The minimal ios appication "Keep it as small and simple as possible" - me, all the time Xcode, New Project -> Application -> Window Based Application, name it Minimal, and Save. What you see in xcode now is the plain iphone application. But there are still too many unnecessary things there. We are real programmers, we won't need the interface builder descriptor, so right click on MainWindow.xib, select delete, and move to trash. Open Minimal-Info.plist as plain text file, and delete the last key-string node from it ( with MSMainNibFile and MainWindow ). Let's remove Coregraphics.framework from frameworks, we don't need it. Right click on it, delete, move to trash. Let's check main.m. Delete NSAutoreleasePool initialization and the release lines, we are real programmers, we want total control, not autorelease sh*t. :) Without these two lines we can simplify this function, move UIApplicationMain init after return, and remove UIKit import from the top, its already in the prefix header. We also have to tell UIApplicationMain which is our Delegate class. The result should look like this : int main ( int countx, char *wordsx[ ] )! return UIApplicationMain( countx, wordsx, nil, @"MinimalAppDelegate" ); Let's check MinimalAppDelegate.h. Delete the @property line, we don't want to set, get and synthesize the window. Also remove UIKit import. MinimalAppDelegate.m comes. Remove @synthesize window. Let's create it "manually". Normally it is synthesized based on the interface builder file, but we deleted that. Let's create an UIWindow instance with the screen's dimensions : window = [ [ UIWindow alloc ] initwithframe : [ [ UIScreen mainscreen ] bounds ] ]; The result : @implementation MinimalAppDelegate - ( void ) applicationdidfinishlaunching : ( UIApplication* ) application! window = [ [ UIWindow alloc ] initwithframe : [ [ UIScreen mainscreen ] bounds ] ];! [ window makekeyandvisible ]; - ( void ) dealloc! [ window release ];! [ super dealloc ];
You can debug and run the application. Let's check the project's folder. We have three files in the root, the prefix, the plist and main.m, and two files for MinimalAppDelegate class under classes folder. If you do a release build, you will find that the size of the binary is 17704 bytes, so this seems the minimal size of a compiled application, of course you can short it with one-char method and attribute names, but it won't be readable then, so it won't be that simple to work with, and our first and only rule would be harmed. The minimal iphone opengl ES application We will need an opengl ES framework. Right click on Frameworks folder, Add -> Existing Frameworks, select an OpenGLES framework, click Add. If you don't know how to locate one, create a new opengles project, right click on OpenGLES.framework under frameworks, and check the full path. We also need the QuartzCore framework for CAEAGLLayer. Do as above. To show an opengl output, we need a core animation gl layer to bind the renderbuffer to, and UIView has this. So we need an UIView instance. But its not that simple, by default UIView's layer is a CALayer instance, but opengl context needs a CAEAGLLayer instance to work, so we have to mimic it. We need a new class which extends UIView, and we have to override UIView's "layerclass" class method definition. Right click on Classes, Add -> New File -> Objective-C class -> Next -> name it GLView.m, click Finish. Delete the #import from the header file. Extend it from UIView. @interface GLView : UIView Only one thing is needed in the implementation file, the layerclass override : @implementation GLView + ( Class ) layerclass return [ CAEAGLLayer class ]; So from now on it will tell that its layer is a caeagllayer instance. Great. We made two additional files for almost nothing. Quite a shitty solution from Apple. Yesokay, lets get back to MinimalAppDelegate. We have to instantiate our layer. We need a new property in the header :
GLView* glview; don't forget to import we also need to import the opengl ES1 extensions #import <OpenGLES/ES1/glext.h> The result : #import <OpenGLES/ES1/glext.h> @interface MinimalAppDelegate : NSObject! UIWindow*! window;! GLView*!! glview; And in the implementation we instantiate our newly created GLView class: glview = [ [ GLView alloc ] initwithframe : [ [ UIScreen mainscreen ] bounds ] ]; Lets add this view as a subview to the main window. [ window addsubview : glview ]; now we can create our opengl ES1 context. EAGLContext* context = [ [ EAGLContext alloc ] initwithapi : keaglrenderingapiopengles1 ]; Set it as the active context : [ EAGLContext setcurrentcontext : context ]; From now on we can work with the context. We need a color and a frame buffer. Let's generate names for them. GLuint colorbuffer; GLuint framebuffer; glgenframebuffersoes( 1, &framebuffer ); glgenrenderbuffersoes( 1, &colorbuffer ); Bind framebuffer as framebuffer, colorbuffer as renderbuffer. glbindframebufferoes( GL_FRAMEBUFFER_OES, framebuffer ); glbindrenderbufferoes( GL_RENDERBUFFER_OES, colorbuffer ); We have to attach the colorbuffer to the framebuffer as color attachment. glframebufferrenderbufferoes( GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, colorbuffer );
The only thing left is binding glview's layer to opengl's renderbuffer. [ context renderbufferstorage : GL_RENDERBUFFER_OES fromdrawable : ( CAEAGLLayer * ) glview.layer ]; Now we can do something spectacular, for example, setting the background color to dark green. glclearcolor( 0,.3, 0, 1.0 ); glclear( GL_COLOR_BUFFER_BIT ); And display the render buffer : [ context presentrenderbuffer : GL_RENDERBUFFER_OES ]; Yaaaay! If you run/debug the application, you will see a beautiful dark green color on your opengl layer. ios opengl ES basics - textures as point sprites Let's go to the point in MinimalAppDelegate where we bind glview's layer to opengl's renderbuffer. [ context renderbufferstorage : GL_RENDERBUFFER_OES fromdrawable : ( CAEAGLLayer * ) glview.layer ]; So we want some texture. The simplest form of a texture in opengl is a point sprite. OpenGL maps the given texture to the wanted point with the wanted size. Sounds simple, and it is. First we have to load an image we want to use as a texture. Create a gradient sphere with alpha in a photo editor, save it as a transparent png. We use UIImage for loading the image. CGImageRef brushimage = [ UIImage imagenamed : @"Particle.png" ].CGImage; We have to extract the raw byte data from the image, we use Core Foundation's DataGetBytePointer function to get the pointer to the byte array provided by CGImage's dataprovider. GLubyte* brushdata = ( GLubyte * ) CFDataGetBytePtr( CGDataProviderCopyData ( CGImageGetDataProvider ( brushimage ) ) ); We have it now, texture generation comes GLuint brushtexture; glgentextures( 1, &brushtexture ); glbindtexture( GL_TEXTURE_2D, brushtexture ); Let's set up a linear filter for the texture gltexparameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); And finally create the texture from our bytearray
glteximage2d( GL_TEXTURE_2D, 0, GL_RGBA, CGImageGetWidth( brushimage ), CGImageGetHeight( brushimage ), 0, GL_RGBA, GL_UNSIGNED_BYTE, brushdata ); Yaaay. Don't forget to free brushdata array. We have the texture, let's draw it as point sprites.we want to see smooth alpha transitions, so we have to enable alpha blending. glenable(gl_blend); glblendfunc(gl_src_alpha, GL_ONE); Let's set up the screen's dimensions for orthographic projection and for the viewport. glmatrixmode( GL_PROJECTION ); glorthof( 0, 320, 0, 480, -1, 1 ); glviewport( 0, 0, 320, 480 ); Enable 2D textures and point sprites. glenable( GL_TEXTURE_2D ); glenable( GL_POINT_SPRITE_OES ); gltexenvf(gl_point_sprite_oes, GL_COORD_REPLACE_OES, GL_TRUE ); Set up points for the sprites glpointsize( 100 ); GLfloat points [ ] = 130, 200, 100, 204 ; Enable vertex array for writing glenableclientstate(gl_vertex_array); Assign vertex pointer glvertexpointer( 2, GL_FLOAT, 0, points ); And finally, draw vertexes. gldrawarrays( GL_POINTS, 0, 2 ); And display renderbuffer. [ context presentrenderbuffer : GL_RENDERBUFFER_OES ]; Run/debug it, you'll see two beautiful intersecting clouds :D ios opengl ES basics - animation Lets move our point sprites around. We need a few instance variables for this in the header. We need an array of GLfloats to store coordinates, two float for storing the two particles directions, the EAGLContext, and a NSTimer for simulation/screen refresh. #import <OpenGLES/ES1/glext.h> @interface MinimalAppDelegate : NSObject
! NSTimer*!timer;! UIWindow*! window;! GLView*! glview;! GLfloat*!points;! EAGLContext*! context;! float! anglea;! float! angleb; - ( void ) update; Go back to MinimalAppDelegate implementation. We have to remove the drawing part from the bottom of applicationdidfinishlaunching method. We have to setup movement variables and the timer there instead. anglea = ( float ) rand( ) / RAND_MAX * M_PI * 2; angleb = ( float ) rand( ) / RAND_MAX * M_PI * 2; points = malloc( sizeof( GLfloat ) * 4 ); points[ 0 ] = ( GLfloat ) rand( ) / RAND_MAX * 320; points[ 1 ] = ( GLfloat ) rand( ) / RAND_MAX * 480; points[ 2 ] = ( GLfloat ) rand( ) / RAND_MAX * 320; points[ 3 ] = ( GLfloat ) rand( ) / RAND_MAX * 480; // START TIMER timer = [ NSTimer scheduledtimerwithtimeinterval! : ( NSTimeInterval ) 1.0 / 20.0 target!!!!! : self selector!!!! : @selector ( update ) userinfo!!!! : nil repeats!!!! : YES ]; We have to move the drawing part to a separate function, which will be called by our NSTimer simultaneously. This will be "update". First we need to refresh particle positions. // modify direction anglea += -.5 + ( float ) rand( ) / RAND_MAX * 1; angleb += -.5 + ( float ) rand( ) / RAND_MAX * 1; // update position points[ 0 ] += sin( anglea ) * 10; points[ 1 ] += cos( anglea ) * 10; points[ 2 ] += sin( angleb ) * 10; points[ 3 ] += cos( angleb ) * 10; // check borders if ( points[0] > 320 points[0] < 0 points[1]> 480 points[1] < 0 ) anglea -= M_PI; if ( points[2] > 320 points[2] < 0 points[3]> 480 points[3] < 0 ) angleb -= M_PI; Then let's move the texture sprite drawing code below
// enable texturing glenable( GL_TEXTURE_2D ); // assig vertex pointer glvertexpointer( 2, GL_FLOAT, 0, points ); // set point size glpointsize( 150 ); // draw vertexes gldrawarrays( GL_POINTS, 0, 2 ); Then, to make things more spectacular, we darken the whole scene, and we get a nice "glowing trail" effect. We will draw a simple square with the help of a triangle strip, we will use vertex coloring, so we have to switch off textures, and enable color array access. The last part is : // disable texturing gldisable( GL_TEXTURE_2D ); // enable color array for writing glenableclientstate( GL_COLOR_ARRAY ); // define screen sized square with transparency GLfloat square [ ] = 0, 0, 0, 480, 320, 0, 320, 480 ; GLubyte colors [ ] = 0, 0, 0, 200, 0, 0, 0, 200, 0, 0, 0, 200, 0, 0, 0, 200 ; // assign vertex and color pointer glvertexpointer( 2, GL_FLOAT, 0, square ); glcolorpointer( 4, GL_UNSIGNED_BYTE, 0, colors ); // draw gldrawarrays( GL_TRIANGLE_STRIP, 0, 4 ); // disable color array before using texture gldisableclientstate( GL_COLOR_ARRAY ); // display renderbuffer content [ context presentrenderbuffer : GL_RENDERBUFFER_OES ]; If you move the darkening part before the sprite drawing, you get brighter glows, check it in the source. 2010.01.15-18.