From Blender to iPhone

February 10, 2011

My first iPhone game, Shufflepuck, was written in the dark ages – before the iPhone SDK was even available – and I modeled the 3D world using the tools I had: basic geometry equations applied to generate all of the vertices programmatically. And it worked! For a good while it was the sexiest table shuffleboard game on the App Store.

I was able to get by on programmatic models for that game, but if I wanted to be able to do something more interesting I was going to need to step things up. I write this now not as anything near an expert, but as a developer who has forced himself to wade into unfamiliar territory and figure out how to put the pieces together. My goal was to create a simple, textured model in a 3D modeling package and display that model in all of its textured glory on the iPhone.

Before I describe the steps I took, there is of course an easier path: using a game engine. There are several for iOS, most of them with their own model loaders and probably tutorials on how to do things end-to-end. For a few reasons, some more valid than others, I decided not to use any of these. They might make loading my textured model ridiculously easy, and there’s certainly value to that, but my research told me I could probably do it without them. Perhaps I just wanted a challenge.

Step 1: Learn Blender

Blender is a popular open source, cross-platform 3D modeling app. If your budget resembles mine, rockstar 3D apps like Autodesk’s Maya simply aren’t an option. So you will suffer with Blender, app of bewildering UI conventions. (I looked into Cheetah 3D briefly but at that stage I had already spent enough time with Blender that I thought there might be hope for it.) I used Blender 2.49b; it is my understanding that quite a bit has changed in 2.5x.

You don’t need to learn all of Blender before you export a model into your app. You just need to have a feeling for how to create objects, select vertices, move vertices, and so forth. Here are my recommendations on how to get there:

  1. Dust off your Mighty Mouse. The developers of Blender did not design it with your Magic Trackpad in mind.
  2. If at all possible, use a full size keyboard with a numeric keypad. The keypad is an important part of 3D view navigation.
  3. Force yourself to complete Unit 1 of Blender 3D: Noob to Pro, as well as Unit 2 sections 2A and 2B.

It will not be easy. Many times you will suspect that you will never feel comfortable in this unsettlingly open sourcy soup of window types and button panels. Particularly if you try to soldier on with your trackpad, as I did, unwarned.

Step 2: Learn Blender UV Map Basics

Now that you know your way around Blender (you do know how to create an icosphere and select a few of its vertices, yes?), skip to UV Map Basics, a later section of Noob to Pro. Frankly this section is not on the same quality level as prior sections, but I managed to cobble together some sense out of it.

Create a little Earth icosphere and setup the UV mapping as described. You should end up with a crude looking earth in Blender’s 3D view.

UV Mapping Earth in Blender 2.49b

Step 3: Get Jeff LaMarche’s Blender Objective-C Export Plugin

Next we need to get that Earth’s vertices, normals and UV texture coordinates out of Blender. There are two ways we could do this:

  1. Export the file as a common 3D object format, such as .obj. If we choose this route however we need some code to read that format in and turn it into vertices, normals, etc. Jeff LaMarche (iphonewavefrontloader) and Bill Dudney (link) have written code to do this, but it sounds like this is not the favored approach given differences in how normals are expressed (vertex vs surface).
  2. Use Blender’s Python API to export your models in a custom format.

Jeff LaMarche has created a script for Blender that does just this: it exports your object as an Objective-C header file. It is perhaps most conveniently used with his OpenGL ES project template, which provides the TexturedVertexData3D structure.

Copy the script in Blender’s scripts folder. On Mac you will find it in blender.app/Contents/MacOS/.blender/scripts. Unless you have Finder configured to show dot-files you can get there by opening the folder containing the Blender app bundle in Finder, then Shift-Command-G and enter the aforementioned path.

Now if you switch one of your Blender windows to “Scripts,” you will be able to click Scripts, Export, Objective-C Header and enter a .h file to save the currently selected object to.

Step 4: Incorporate The Model Into Your Project

You should now have a .h file, which contains the vertices, normals and texture coordinates for your icosphere, as well as a texture image, most likely of Earth.

If you followed the UV Map tutorial precisely, your texture image size will be 4096 x 2048, which is both quite large and not square. So you will need to make it smaller and squarer. I made a 1024x1024 texture. Keep in mind that texture coordinates are in the range 0.0-1.0, so when you make the texture square you will want to stretch it vertically so that the V texture coordinates are correct.

If you are using LaMarche’s template, edit GLViewController to:

  1. Instantiate the texture. You will only need to do this once:
texture = [[OpenGLTexture3D alloc] initWithFilename:@"Earth.png"
                                              width:0
                                             height:0];
  1. Bind the texture and draw the vertices. You will find, at the bottom of the generated .h file, the lines necessary to draw the object. You may need to fiddle with the translate and scale values used here, depending on your object.
- (void)drawView:(UIView *)theView
{
    glColor4f(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glColor4f(1, 1, 1, 1);
    glEnable(GL_TEXTURE_2D);
    [self.texture bind];
    glEnable(GL_CULL_FACE);
    
    glLoadIdentity();
    glTranslatef(0, 0, -2);
    glScalef(0.5, 0.5, 1);
    
    // Quick and dirty hack to make the model rotate:
    static float t = 0.0;
    glRotatef(t, 1, 1, 0);
    t += 1;
    
    // Drawing code from the header:
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glVertexPointer(3, GL_FLOAT, sizeof(TexturedVertexData3D), &SphereVertexData;[0].vertex);
    glNormalPointer(GL_FLOAT, sizeof(TexturedVertexData3D), &SphereVertexData;[0].normal);
    glTexCoordPointer(2, GL_FLOAT, sizeof(TexturedVertexData3D), &SphereVertexData;[0].texCoord);
    glDrawArrays(GL_TRIANGLES, 0, kSphereNumberOfVertices);
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
	
}

Step 5: Enjoy

You should now have an Earth happily spinning away in your iOS Simulator; at right is my Earth.

1024x1024 is a pretty large texture; you should be able to get away with something smaller, depending on your application and how the model is used. (I chose that size because that’s what it took to look reasonable as a still.)

Hopefully you now have a pretty clear idea of how to use 3D models in your application. You can probably imagine some enhancements. For example, it would be nice to store models as app bundle resources rather than compiling them in as a header file. This is one of the nice things about Blender’s API: if you know a little Python you can write your own exporter.

Perhaps that will be the subject of my next blog post.

Tags: , , ,