Showing posts with label Game Programming. Show all posts
Showing posts with label Game Programming. Show all posts

Friday, June 3, 2011

Thoughts on Unity3D

As I stated in my previous post on 3D engines, I'm going to do four blog posts giving my thoughts on each of the four engines that I looked into using for a project recently. Those four engines are Unity3D, Sio2, Ogre3D, and Cocos3D. I'm starting with Unity3D, which we selected for one of our recent projects.

Now, let me state up front, that all four of these are competent engines and I could see situations where I would recommend three of the four engines for client projects and could easily imagine situations where all four of them would be good choices.These four do not make an exhaustive list; there are other engines out there, including some really good ones, but these were the four that we looked at for this specific project. As much as I like the UDK, for example, I don't like it enough to spend quality time in Windows, so that one is off the table for me until the fine folks at Epic decide to port their dev and content tools to the Mac.

Unity Overview

Unity3D actually predates the iPhone, and of the four engines, it's the one that feels the most mature and has the most robust developer tools. It also has one of the most active developer communities. Unity3D is a closed-source commercial product that you must pay for, which might be a turn-off for some, but it is worth every penny of the license fee.

Unity Pros

Unity is actually fairly easy to learn, yet has a feature set that is compares favorably to most other engines. Unity's asset pipeline is really robust and their tools give you the ability to very quickly make changes and test those changes.

One of the best things about Unity3D is the fact that it supports both Windows and Mac OS equally as development platforms. Regardless of which platform you develop on, you can generate games that runs on every platform Unity supports (assuming you're licensed for it). You can even have part of your team developing on Macs and the other part on Windows with no problems.

At the time I'm writing this, it is possible to generate games from Unity that run on Mac OS X and Windows (both native apps as well as games playable through a web plug-in), iOS, Android, Wii, Xbox 360, and PS3. The Unity folks are also actively working on adding Linux support. Console licenses are negotiated on an individual basis and are likely quite expensive, but it's nice to have the option to take a successful game to so many platforms without having to do a full port each time.

Having had to port OpenGL ES apps from iOS to Android more than once (which is no fun), I can honestly state that the Android support should be a huge selling point you're giving any thought to supporting both iOS and Android.

Unity Cons

The downsides to Unity3D are relatively few, actually.It is the most expensive of the engines I looked at, and the costs are completely front-loaded. You pay a flat per-developer license per platform up front, but then you can create as many games as you want for the platforms you're licensed for. For the basic iOS license, it's $400 per seat (one developer using up to two machines), for the Pro license, you're looking at $3,000 (because the $1,500 iOS Pro requires the $1,500 Unity Pro), which might sound like a lot of money, but it's really a pittance compared to the amount of development time it can save you. The "Basic" version of Unity, which allows you to generate Mac, Windows, and Web games (though excludes some of the more advanced features) is available completely free of charge.

The other potential downside in some situations is that the underlying C++ source code is not available. You work primarily in either JavaScript or C# (MonoScript) and at a higher level of abstraction. Unity supports a few other languages besides Javascript and C#, but only those two languages work for mobile development.

After about two weeks of spending my evenings with Unity, I actually came to the conclusion that not having access to the source isn't really much of a drawback, and for many developers, working this way will be better than working in C++. The engine takes care of almost all the low-level stuff you'd need to do, but even if it doesn't, Unity has a shader language that lets you write code that runs on the GPU and anything that needs to run on the CPU can be done by scripting inside Unity.

Although the programming in Unity3D is done with scripting languages, the scripts you write are actually compiled, so there's not a huge performance overhead to using them. In the exceedingly rare situation where C# or Javascript isn't sufficient, it is possible to send messages into and out of Unity from your application's C or Objective-C code.

Learning Curve

I found Unity surprisingly easy to learn. There are some really good resources out there, including lots of tutorials and instructional videos. I actually had a game functioning after about two hours of playing. It was ugly and the game mechanics were simple, but it was playable. Unity also has a fairly active developer community forum where you can go and get help when you get stuck.

If you don't already know something about graphics programming, you still don't need to be too scared or intimidated. Most of the gnarly stuff is squirreled away where you won't see it until you need it, and you can do a surprising amount by just configuring things in the development GUI. You can create, for example, a fairly full-featured physics simulations without ever writing a line of code. Want to stack up a bunch of crates and roll a ball into them and watch them all fall? You can do it without ever opening a text editor. Heck, you can even do that without opening a 3D program.

Complexity

I was scared of one thing going into learning Unity: I was concerned that because it was so easy (at least I had heard it was), that it was going to be a dumbed down game maker that sacrificed more advanced features for the sake of lowering the obstacles to entry. My fears were completely misplaced, though. That's not the case at all. The Unity folks have done a really great job of making their tools easy to use while still giving you the ability to do most anything you'd ever need to do. It's even possible (though the process is a bit convoluted) to integrate UIKit and Unity3D within the same application.

Asset Pipeline

The thing that actually impressed me most about Unity is what they call their "asset pipeline", which is the process by which you get 3d models, textures, and other assets into your game. If you've ever developed a game or game mod for the UDK, a Valve Source game, or other commercial engine, usually there's kind of a convoluted process you have to go through to get your game assets including characters, textures, props, and environments, into the game. You usually have to use separate applications to specify shaders and physics options, sometimes write a compile script or other text document to define certain trains, and then compile the object and package it all up into some kind of bundle or package. While developing a single mod, you typically iterate through this process many, many times. It can be a bit tedious and hard to learn and usually requires the use of multiple tools and lots of trial and error.

Unity bypasses almost all of this tedium. When you save your assets from your 3D program or Photoshop, you simply save them in your Unity project's Assets folder. When you launch Unity, or navigate back to it if it's already open, Unity detects the new file, or any changes you've made to an existing file. It imports it and adds it to the list of available assets.Anything you need to do that can't be done in your 3D program, such as specify a shader or identify the physics engine properties for the object, you do right in Unity, and most of that can be done without writing code (though everything you can configure without code can also be changed from code).

Which 3D program do you need to use? Pretty much any one you're comfortable with. Unity3D has direct import support for the native file formats from several programs including Blender , but any package that can export to Autodesk FBX or Collada can be used with full support for all features like bones, textures, animations, etc.It also supports native Photoshop files (.psd) for textures, flattening layers in a non-destructive manner when you build your app.Assets can even be given different characteristics for different platforms. You could have, for example, a 2048x2048 texture asset for the desktop version of your game but tell Unity to use a 1024x1024 version for game consoles and a 512x512 version on iOS and Android.

The Bottom Line

As I stated earlier, there are situations where I could see any of the four engines I looked at being a good choice, as could many of the other engines I didn't look at. But, honestly, if I had to make an engine decision without detailed information, Unity3D would be my first recommendation. The tools are solid, the company and community support is awesome, it supports many platforms, and getting assets into your game couldn't be easier. It's relatively easy to start using for both experienced developers and those who aren't.

A good, experienced graphics programmer working in mobile right now can easily ask $200 per hour or more because the demand far outstrips the supply. Figure it out. Unity's full iOS license is basically the equivalent of 15 hours of a graphics programmer's time. Yet, Unity will easily cut five times that many hours off of any decent size software game project's schedule, probably a lot more.

I'm giving Unity3D two big thumbs up. I'm using it on a project now and hope to use it on many more in the future because it's fun to use and removes much of the tedium associated with 3D programming without removing power.


1 Many of the more advanced features require the more expensive "Pro" license, however, and really cutting edge features from the latest games generally take a little while to show up in the tools

Thursday, January 27, 2011

Garage Games

I noticed that Garage Games, the makers of the Torque game engine have recently had a near-death experience. As of the end of last year, it looked like they were shutting down operations. However, they appear to be in the midst of a phoenix-like return to life with, perhaps, some changes in focus and priority. The bad news for iOS folks, is that they've removed the mobile version of their 3D engine from their product list. I don't know if this means it's abandoned, or that they're just not ready to announce/release it. They are actively hiring developers and designers, and from the job descriptions they've posted, it sounds to me like they've got their sights set firmly on mobile.

To celebrate their revival, they're having a sale on all their products - you can get any of their engines, including a full source license for $99.

Now, I honestly don't know how Torque compares to, say, Unity, Sio2, or the UDK in terms of features or ease of development, but there have been a lot of very solid games created with Torque over the years and the demos show the engine is versatile and fairly powerful.

What I do know that $99 is a hell of a price for a source license to a game engine of this size and complexity. For me, it's worth that much money to get to peek around their code. There's a lot to be learned from looking at other people's code.

No word on how long the sale is for, but you can check it out here.

Note: I had an e-mail conversation with Garage Games' community manager about iTorque 3D. Unfortunately, right now the official word is that the product is off their roadmap. They have no announced plans to release it, so if you're targeting the iPhone or mobile devices in general, you're out of luck for now and should probably look at another engine.

That being said, they didn't rule out the possibility of re-introducing iTorque 3D at some future point. Frankly, if you're interested in writing games, I still think it's well worth dropping $99 for a license to be able to see the choices they've made in designing their engine as well as how they've implemented them. It's a pretty good example of a complex cross-platform project.

I have to say, I like the new public face of Garage Games. Their responses to my questions were prompt and candid. I think the new management knows exactly what they want to do and I'm looking forward to seeing where they take things.

Wednesday, October 28, 2009

Unity Indie Now Free

The 3D game-making toolset called Unity is now available for free to independent developers who gross less than $100k on products made with the tools. This is an interesting move on the part of Unity3D. I haven't used the tool myself, but I've heard mostly good things about it. Now that it's free for indie use, there's no excuse for not checking it out.

Tuesday, October 13, 2009

Gamasutra on Android Game Development

Gamasutra, a great website devoted to "the art and business of making games", has an article today on developing games for Android. Of course, about half of the article is spent discussing the success of the App Store and making comparisons to it.

Although I like the article, there are a few things in it that I take exception to. On the first page, for example, the article says that Android uses the "more developer friendly Java". What the hell does that mean? I've done both Java and Objective-C for a living, and neither one has ever waved to me or gotten me a cup of coffee. They're programming languages. Sure, there are more people who already know Java than Objective-C, but if that's what he meant, there were far more accurate ways of saying it.

It's also just plain wrong in terms of game programming. Objective-C is a superset of C and you can write entire games in C or C++ with the exception of a small amount of code to set up and configure your OpenGL ES view. Please don't even try to tell me that, from the perspective of a game programmer, Java is more "user friendly" than writing OpenGL code in C or C++.

Overall, the tone of the article is very optimistic, almost like a recruitment article hoping get people interested in Android development. They point to statistics to show that Android is gaining momentum and the iPhone is losing it. Well, you know what Disraeli said about statistics1.

If you follow the link in the last paragraph, the iPhone chart clearly does not show a decline in anything except in an artificial comparison to Android in terms of "percentage of projects started" (a dubious metric, at best). The iPhone is showing a slowdown in growth when looking at new projects being started, but there is still growth, and considering just how big and successful the store has been, that's kind of amazing. On the other hand, look at the Y-axis label on both charts. They are using completely different scales. If they were charted together on the same graph, the Android "flurry" of project starts would barely get over the first line, and it would do that in only one of the months charted. Also, while the iPhone may be experiencing a slowdown in new project starts, that's more than being offset by an increase in interest from the large gaming interests like EA and the fact that many more big, long-term projects are being started.

I don't want to come across as too down on Android. I think right now, of all the smart phones in the race, it's in the best position to be a real competitor to the iPhone, and a strong competitor is definitely something the iPhone needs. If I had to bet on which platform might overtake the iPhone someday, it would be Android, but I would not be betting a lot of money. There's still an open field for second place and a long distance between the pack and the current leader. Nobody currently jockeying for second place has begun to move substantially away from the pack toward the leader. At some point, somebody undoubtedly will break away and gain ground on the iPhone, but nobody - including Android - has started the kick yet. They might have a little better odds on placing, but they're still a long shot for winning.

Androids greatest advantage in that field also be its Achille's heel in terms of game development, which is the fact that Android is hardware agnostic. It will run on a variety of devices from different manufacturers with different CPUs and different GPUs. It will run on phones with touch screens and phones with keyboards. That's going to introduce a lot of complexity and make it hard to write apps that work perfectly on all Android devices that ever have been made or ever will be made, or even to identify how well it will run on any potential customer's phone. The only way for a developer to know if their game will perform well on any particular phone is to buy them all and test. That just isn't a possibility for the stereotypical garage game developer. This hardware agnosticism also means that optimizing game code for performance might result in improvements on one platform and then unexpectedly cause degradation on another. Those are issues that big game companies have the resources and experience to deal with, but are not so easy for smaller independent game developers.

Despite the issues, despite the difficulty competing in an increasingly large App Store that's beginning to get populated with big-budget games, I feel strongly that the iPhone is still the best game in town for the small, independent game developer, and it will continue to be well into the foreseeable future. Yes, you will still have to market your game. Yes, there is a chance of failure. The App Store is not magic, it's just a great opportunity.

Now, this equation will change over time, and this what I'm saying not be true a few years from now, but it's going to take time for Android to build momentum and gain ground. Right now, the games in the top ten on the Android Store are averaging well less than $100 a day income, and that's just not enough potential money for most people - big companies and independent developers alike - to see it as a viable opportunity proposition yet.



1: "There are three kinds of lies: lies, damned lies, and statistics." It was originally said by British Prime Minister Benjamin Disraeli. Sometimes it's wrongly attributed to Mark Twain. Though he certainly popularized it in America, the quote does not originate from him.

Wednesday, September 30, 2009

Critical Thinking from Critical Thought

This is an excellent blog post by David Whatley of Critical Thought Games. I was already bummed about not being at 360 iDev (the first was awesome), but I really would have liked to have been in the audience for David's talk, especially to watch the crowd's reaction.

I've been known to grouse about things from time to time, but I also try to keep a sense of perspective and not lose sight of just how amazing this thing that Apple has enabled with the App Store and iPhone SDK is. David does it better. Well worth a few minutes of your time to read this.

Sunday, June 7, 2009

OpenGL ES from the Ground Up Part 8: Interleaving Vertex Data

Technote 2230 makes many suggestions for improving the performance of your iPhone apps that use OpenGL ES. You're now far enough along in your understanding of OpenGL ES that you should read it. No, really. Go read it, I'll wait.

Okay, done? Under the section titled Optimizing Vertex Data, there's a somewhat cryptic recommendation to "submit strip-ordered indexed triangles with per vertex data interleaved". When Apple makes a recommendation, they usually have a good reason for it, so let's look at how we comply with this one.

First of all, let's look at what it means. Let's break it down:

Strip Ordered: In other words, if your model has adjacent triangles, submit them as triangle strips rather than submitting each triangle individually. We've talked about using triangle-strips in earlier installments, so you already know a little about doing that. It's not always possible to use triangle strips, but for a good many objects you will be able to, and whenever you can, you should because using triangle strips greatly decreases the amount of vertex data you have to push into OpenGL ES every frame.

indexed: This is also nothing new. We've been using vertex indices for a while now. Our spinning icosahedron uses them to create twenty faces with only twelve vertices. glDrawElements() draws based an indices rather than vertices.

Heck, we're doing great so far, aren't we? So far, we seem to be doing all the right things! Let's look at the last part of the recommendation, however:

with per vertex data interleaved: Okay, hmm.. What the hell does that mean?

Okay, time to test out your memory. Do you remember in several of the past installments when we time we talked about functions like glVertexPointer(), glNormalPointer(), glColorPointer(), or glTexCoordPointer ? In earlier installments, I told you not to worry about the parameter called stride and to just set it to 0.

Well, now you can start worrying about stride, because that's the key to interleaving your per vertex data.

Per Vertex Data

So, you might be wondering what "per vertex data" is and how you would interleave it.

You remember, of course, that in OpenGL ES we always pass geometry in using a vertex array, which is an array containing sets of three GLfloats that define the points that make up our objects. Along with that, we also sometimes specify other data. For example, if we use lighting and need vertex normals, we have to specify one normal per vertex in our normal array. if we use texture coordinates, we have to make sure that our texture coordinate array has one set of texture coordinates per vertex. And if we use a color array, we have to specify one color per vertex. Do you notice how I keep saying "per vertex"? Well, these types of data are what Apple is referring to when they say "per vertex data" in that Technote. It's anything that you pass as an array into OpenGL ES that supplies any kind of data that applies to the vertices in the vertex array.

Interleaving

Up until now in this series, we've created one array to hold the vertex data, and additional separate arrays to hold the normal data, color data, and/or texture coordinate data, like so:

separatearrays.png


What we're going learn how to do today is to smush all this data together into a single contiguous chunk of memory:

interleaved.png


Don't worry if you can't read the code in that illustration. When it becomes important, I'll give you the code listing again, that's just to illustrate the point that we're going to have all of our vertex data in a single glob of memory. What that's going to do is put all the data describing a single vertex together in one place in memory. That will allow OpenGL faster access to the information about each vertex. In today's installment, we're going to interleave vertices, normals, and color data, though the same exact technique would work for texture coordinates, or for just interleaving vertices and normals. In fact, in the accompanying Xcode Project, there are data structures defined to handle all three of those interleaving scenarios.

Defining a Vertex Node

In order for this to work, we need a new data structure. In order to interleave vertices, normals, and color data, we need a structure that looks like this:

typedef struct {
Vertex3D vertex;
Vector3D normal;
Color3D color;
}
ColoredVertexData3D;


Pretty straightforward, huh? You just create a struct with each piece of per-vertex data that we're using.

Next, of course, we need to populate our vertex data, so we need to combine those three static const arrays into a single one. Here's what the same icosahedron data looks like specified using a static array of our new datatype:

static const ColoredVertexData3D vertexData[] = {
{
{0, -0.525731, 0.850651}, // Vertex |
{0.000000, -0.417775, 0.675974}, // Normal | Vertex 0
{1.0, 0.0, 0.0, 1.0} // Color |
}
,
{
{0.850651, 0, 0.525731}, // Vertex |
{0.675973, 0.000000, 0.417775}, // Normal | Vertex 1
{1.0, 0.5, 0.0, 1.0} // Color |
}
,
{
{0.850651, 0, -0.525731}, // Vertex |
{0.675973, -0.000000, -0.417775}, // Normal | Vertex 2
{1.0, 1.0, 0.0, 1.0} // Color |
}
,
{
{-0.850651, 0, -0.525731}, // Vertex |
{-0.675973, 0.000000, -0.417775}, // Normal | Vertex 3
{0.5, 1.0, 0.0, 1.0} // Color |
}
,
{
{-0.850651, 0, 0.525731}, // Vertex |
{-0.675973, -0.000000, 0.417775}, // Normal | Vertex 4
{0.0, 1.0, 0.0, 1.0} // Color |
}
,
{
{-0.525731, 0.850651, 0}, // Vertex |
{-0.417775, 0.675974, 0.000000}, // Normal | Vertex 5
{0.0, 1.0, 0.5, 1.0} // Color |
}
,
{
{0.525731, 0.850651, 0}, // Vertex |
{0.417775, 0.675973, -0.000000}, // Normal | Vertex 6
{0.0, 1.0, 1.0, 1.0} // Color |
}
,
{
{0.525731, -0.850651, 0}, // Vertex |
{0.417775, -0.675974, 0.000000}, // Normal | Vertex 7
{0.0, 0.5, 1.0, 1.0} // Color |
}
,
{
{-0.525731, -0.850651, 0}, // Vertex |
{-0.417775, -0.675974, 0.000000}, // Normal | Vertex 8
{0.0, 0.0, 1.0, 1.0}, // Color |
}
,
{
{0, -0.525731, -0.850651}, // Vertex |
{0.000000, -0.417775, -0.675973}, // Normal | Vertex 9
{0.5, 0.0, 1.0, 1.0} // Color |
}
,
{
{0, 0.525731, -0.850651}, // Vertex |
{0.000000, 0.417775, -0.675974}, // Normal | Vertex 10
{1.0, 0.0, 1.0, 1.0} // Color |
}
,
{
{0, 0.525731, 0.850651}, // Vertex |
{0.000000, 0.417775, 0.675973}, // Normal | Vertex 11
{1.0, 0.0, 0.5, 1.0} // Color |
}

}
;


Here is how we pass the information into OpenGL. Instead of passing in the pointer to the appropriate array, we pass the address of the appropriate member of the first vertex in the array, and provide the size of that struct as the stride argument.

    glVertexPointer(3, GL_FLOAT, sizeof(ColoredVertexData3D), &vertexData[0].vertex);
glColorPointer(4, GL_FLOAT, sizeof(ColoredVertexData3D), &vertexData[0].color);
glNormalPointer(GL_FLOAT, sizeof(ColoredVertexData3D), &vertexData[0].normal);


The the last parameter in each of those calls a points to the data corresponding to the first vertex. So, for example, &vertexData[0].color points to the color information for the first vertex. The stride parameter identifies how many bytes of data need to be skipped before the same type of data for the next vertex can be found. That might make a little more sense if you look at this diagram (sorry, it's wide, you may have to expand your browser to see all of this one:

stridediagram.png


What could be easier, right? If you don't feel like typing it all in, you can download the interleaved version of the spinning icosahedron. I've also updated my OpenGL ES Xcode Template with these new data structures<.

We're still not using triangle strips, but merging triangles into triangle strips is going to have to be a subject for a future installment, because it's time to go meet some people at WWDC.

Thursday, June 4, 2009

OpenGL ES From the Ground Up, Part 7: Transformations and Matrices

Okay, this is the posting that I've been dreading. Conceptually speaking, today's topic is the most difficult part of 3D programming, and it's one I've struggled with.

At this point, you should understand 3D geometry and the cartesian coordinate system. You should understand that objects in OpenGL's virtual world are built out of triangles made up of vertices, and that each vertex defines a specific point in three-dimensional space, and you know how to use that information to do basic drawing using OpenGL ES on the iPhone. If not, you should probably go back and reread the first six installments in this series before tackling this monstrosity.

In order for the objects in your virtual world to be at all useful for interactive programs like games, there has to be a way to change the position of objects in relation to each other and in relation to the viewer. There has to be a way to not only move, but rotate and scale objects. There also has to be way to translate that virtual three-dimensional world onto a two-dimensional computer screen. All of these are accomplished using something called transformations. The underlying mechanism that enables transformations are matrices (or matrixes if you prefer).

Although you can do a fair amount in OpenGL without ever really understanding matrices and the mathematics of the matrix, it is a really good idea to have at least a basic understanding of the mechanism.

Built-In Transformations and the Identity Matrix


You've already seen some of the OpenGL's stock transformations. One call that you've seen in every application we've written is glLoadIdentity(), which we've called at the beginning of the drawView: method to reset the state of the world.

You've also seen glRotatef(), which we used to make our icosahedron spin, and glTranslatef() which was used to move objects around in the virtual world.

Let's look at the call to glLoadIdentity() first. This call loads the identity matrix. We'll talk about this special matrix later, but loading the identity matrix basically resets the virtual world. It gets rid of any transformations that have been previously performed. It is standard practice to call glLoadIdentity() at the beginning of your drawing method so that your transformations have predictable results because you always know your starting point - the origin.

To give you an idea of what would happen if you didn't call glLoadIdentity(), grab the Xcode project from Part 4, comment out the call to glLoadIdentity() in drawView: and run the application. Go ahead and do it, I'll wait. What happens?

The icosahedron, which used to just slowly spin in place, scoots away from us doesn't it? Like Mighty Mouse, it flies away and then up into the sky, exit stage right.1

The reason why that happens is because we were using two transformations in that project. The vertices of our icosahedron were defined around the origin, so we used a translate transformation to move it three units away from the viewer so the whole thing could be seen. The second transformation we used was a rotation transformation to spin the cube in place. When the call to glLoadIdentity() was still in place, we started fresh each frame back at the origin looking straight down the Z-axis. Back then, when we translated 3 units away from the viewer, it always ended up at the same location of z == -3.0. Similarly, the rotation value, which we constantly increase based on the amount of time elapsed, caused the icosahedron to spin at an even pace. Because earlier rotations were removed by the call to glLoadIdentity() before it was rotated, the rotation was at a consistent speed.

Without the call to glLoadIdentity(), the first time through, the icosahedron is translated three units away from us and the icosahedron rotates a small amount. The next frame (a fraction of a second later), the icosahedron moves back another three frames, and the adds the value of rot to the amount the icosahedron was already rotated. This happens each frame, meaning the icosahedron moves away from us three units every frame, and the speed of rotation increases every frame.

It would be possible to not call glLoadIdentity() and compensate for this behavior, but we can't predict or compensate for transformations done in other code, so the best bet is to start from a known position, which is the origin, with no scaling or rotation, which is why we always call glLoadIdentity()

The Stock Transformations

In addition to glTranslatef() and glRotatef(), there is also glScalef(), which will cause the size of objects drawn to be increased or decreased. There are some other transformation functions available in OpenGL ES, but these three (combined with glLoadIdentity() are the ones that you'll use the most. The other ones are used primarily in the process of converting the three-dimensional virtual world into a two-dimensional representation, a process known as projection. We'll touch on projection a little in this article, but in most scenarios, you don't have to be directly involved with that process other then setting up your viewport.

The stock transformations can get you a long way. You could conceivably create an entire game using just these four calls to manipulate your geometry. There are times when you might want to take transformations into your own hands, however. One reason you might want to handle the transformations yourself is that these stock transformations have to be called sequentially as a separate function call, each resulting in a somewhat computationally costly matrix multiplication (something we'll talk more about later). If you do the transformations yourself rather than using the transformations provided by OpenGL, you can often combine multiple transformations into a single matrix, reducing the number of matrix multiplication operations that have to be performed every frame.

It's also possible to eke out better performance by doing your own matrices because you can vectorize your matrix multiplication calls. As far as I can tell, the iPhone's behavior in this regard is not documented, but as a general rule, OpenGL ES will hardware accelerate multiplication between a vector or vertex and a matrix, but not between two transformation matrices. By vectorizing matrix multiplication, you can actually get better performance than you can by letting OpenGL do the matrix multiplication. This won't give you a huge performance boost, as there are generally far less matrix by matrix multiplication calls than vector/vertex by matrix multiplication calls, but in complex 3D programs, every little bit of extra performance can help.

Enter the Matrix


Obviously, I have to make a reference to the movie "The Matrix", since we're going to spend the next few thousand words talking about matrices. It's sort of a geek requirement, so let me get it out of the way:
Unfortunately, nobody can be told what The Matrix is.
Only, it's not true in this case; matrices are really not that big of a deal. A matrix is just a two-dimensional array of values. That's it. Nothing mystical here. Here's a simple example of a matrix:

simplematrix.png


That's a 3x3 matrix, because it has three columns and three rows. Vectors and Vertices can actually be represented in a 1x3 matrix (remember this for later, it's kind of important):

vertexmatrix.png


A vertex could also be represented by a 3x1 array instead of a 1x3 array, but for our purposes, we're going to represent them using the 1x3 format (you'll see why later). Even a single data element is technically a 1x1 matrix, although that's not a very useful matrix.

You know what else can be represented in an array? Coordinate systems. Watch this, it's kind of cool. You remember vectors, right? Vectors are imaginary lines running from the origin to a point in space. Now, remember that the Cartesian coordinate system has three axes:

cartesian.png


So, what would a normalized vector that ran down the X axis look like? Remember: A normalized vector has a length of one, so, a normalized vector that runs up the X axis would look like this:

xaxisvector.png


Notice that we're representing the vector as a 3x1 matrix rather than a 1x3 matrix as we did with the vertex. Again, it doesn't actually matter, as long as we use the opposite for vertices and these vectors. All three of the values in this vector apply to the same axis. I know, it probably doesn't make sense yet, but bear with me, this will clear in up in a second. A vector that runs up the Y axis would look like this:

yaxisvector.png


And one that runs up the Z axis looks like this:

zaxisvector.png


Now, if we put these three vector matrices together in the same order as they are represented in a vertex (x then y then z), it would look like this:

3x3identity.png


That's a special matrix called the identity matrix. Sound familiar? When you call glLoadIdentity(), you are loading that matrix right there2. Here's why this is a special matrix. Matrices can be multiplied together, and multiplying matrices is how you combine them. If you multiply any matrix by the identity matrix, the result is the original matrix. Just like multiplying a number by one. You can always calculate the identity matrix for any given size matrix by setting all the values to 0.0 except where the row and column number are the same, in which case you set the value to 1.0.

Matrix Multiplication


Matrix multiplication is the key to combining matrices. If you have a one matrix that defines a translate, and another that defines a rotate, if you multiply them together, you get a single matrix that defines both a rotate and a translate. Let's look at a simple example of matrix multiplication. Imagine these two matrices:

simplemultiply.png


The result of a matrix multiplication is another matrix that is exactly the same size as the matrix on the left side of the equation. Matrix multiplication is not commutative. The order matters. The result of multiplying matrix a by matrix b is not necessarily the same as the result from multiplying matrix b by matrix a (although it could be in some situations).

Here's another thing about matrix multiplication: Not every pair of matrices can be multiplied together. They don't have to be the same size, but the matrix on the right side of the equation has to have the same number of rows as the number of columns that the matrix on the left side of the equation has. So, you can multiply a 3x3 matrix with another 3x3 matrix, or you can multiply a 1x3 matrix with a 3x6 matrix, but you can't multiply a 2x4 matrix with, say, another 2x4 matrix because the number of columns in a 2x4 matrix is not the same as the number of rows in a 2x4 matrix.

To figure the result of a matrix multiplication, we make an empty matrix of the same size as the matrix on the left side of the equation:

empty3x3matrix.png


Now, for each spot in this matrix, we take the corresponding row from the left-hand matrix and the corresponding column from the right hand matrix. So, for the top left position in the result matrix, we take the top row of the left side of the equation and the first column of the right side of the equation, like so:

multbreakdown.png


Then we multiply the first value in the row from the left-hand matrix by the first value in the right-hand column, multiply the second value in the left-hand row by the second value in the right-hand column, multiply the third value in the left-hand row by the third value in the right-hand column, then add them all together. So, it would be:

firstspotcalc.png


If you repeat this process for every spot in the result matrix, then you get the result of the matrix multiplication:

simplematrixfinal.png


And look at that. We multiplied a matrix (the blue one) by the identity matrix (the red one) and the result is exactly the same as the original matrix. If you think about it, it makes sense since the identity matrix represents our coordinate system with no transformations. This also works with vertices. We can multiply a vertex by a matrix, and the same thing happens:

vertexmult.png


Now, let's say that we wanted to rotate an object. What we do is define a matrix that describes a coordinate system that is rotated. In a sense, we actually rotate the world, and then draw the object into it. Let's say we want to spin an object along the Z axis. To do that, the Z axis is going to remain unchanged, but the X axis and the Y axis need to change. Now, this is a little hard to imagine, and it's not really vital that you understand the underlying math, but to define a coordinate system rotated on the z axis, we would adjust the x and y vectors in our 3x3 matrix, in other words, we have to make changes to the first and second column.

vectoraxes.png



So, the X value of the X-axis vector and the Y value of the Y-axis vector need to be adjusted by the cosine of the rotation angle. The cosine, remember, is the length of the side adjacent to the angle in a triangle. We also need to adjust the Y value of the X-axis vector by an amount equal to minus the sine of the angle and the X value of the Y-axis vertex by the sine of the angle. The sine of an angle is the length of the opposite side of the angle in a triangle. That's hard to follow, it might be easier to understand expressed as a matrix:

zrotation.png


Now, if you take ever vertex in every object in your world and multiply them by this matrix, you get the new location of the vertex in the rotated world. Once you've applied this matrix to every vertex in your object, you have an object that has been rotated along the Z-axis by n degrees.

If that doesn't make sense, it's okay. You really don't need to understand the math to use matrices. These are all solved problems, and you can find the matrices for any transformation using google. In fact, you can find most of them in the OpenGL man pages. So don't beat yourself up if you're not fully understanding why that matrix results in a Z-axis rotation.

A 3x3 matrix can describe the world rotated at any angle on any axis. However, we actually need a fourth row and column in order to be able to represent all the transformations we might need. We need a fourth column to hold translation information, and a fourth row which is needed to do the perspective transformation. I don't want to get into the math underlying the perspective transformation because they require understanding homogenous coordinates and projective space, and it's not really important to becoming a good OpenGL programmer. In order to multiply a vertex by a 4x4 matrix, we just pad it with an extra value, usually referred to as W. W should be set to 1. After the multiplication is complete, ignore the value W. We're not actually going to look at vector by matrix multiplication in this installment because OpenGL already hardware accelerates that, so there's usually not a need to handle that manually, but it's a good idea to understand the basic process.

OpenGL ES's Matrices


OpenGL ES maintains two separate matrices, both are 4x4 matrices of GLfloats. One of these matrices, called the modelview matrix is the one you'll be interacting with most of the time. This is the one that you use to apply transformations to the virtual world. To rotate, translate, or scale objects in your virtual world, you do it by making changes to the model view matrix.

The other matrix is used in creating the two-dimensional representation of the world based on the viewport you set up. This second matrix is called the projection matrix. The vast majority of the time, you won't touch the projection matrix.

Only one of these two matrices is active at a time, and all matrix-related calls, including those to glLoadIdentity(), glRotatef(), glTranslatef(), and glScalef() affect the active matrix. When you call glLoadIdentity, you set the active matrix to the identity matrix. When you call the other three, OpenGL ES creates an appropriate translate, scale, or rotate matrix and multiplies the active matrix by that matrix, replacing the results of the active matrix with the result of the matrix multiplication operation.

For most practical purposes, you'll just set the modelview matrix as the active matrix early on and then leave it that way. In fact, if you look at my OpenGL ES template, you'll se that I do that in the setupView: method, with this line of code:

    glMatrixMode(GL_MODELVIEW);


OpenGL ES's matrix are defined as an array of 16 GLfloats, like this:

        GLfloat     matrix[16];

They could also be represented as two-dimensional C arrays like this:

        GLfloat     matrix[4][4];


Both of those declarations result in the same amount of memory being allocated, so it's a matter of personal preference which you use, though the former seems to be more common.

Let's Play


Okay, at this point, I'm sure you've had enough theory and want to see some of this in action, so create a new project using my OpenGL ES template, and replace the drawView: and setupView: with the versions below:

- (void)drawView:(GLView*)view;
{

static GLfloat rot = 0.0;
static GLfloat scale = 1.0;
static GLfloat yPos = 0.0;
static BOOL scaleIncreasing = YES;

// This is the same result as using Vertex3D, just faster to type and
// can be made const this way
static const Vertex3D vertices[]= {
{0, -0.525731, 0.850651}, // vertices[0]
{0.850651, 0, 0.525731}, // vertices[1]
{0.850651, 0, -0.525731}, // vertices[2]
{-0.850651, 0, -0.525731}, // vertices[3]
{-0.850651, 0, 0.525731}, // vertices[4]
{-0.525731, 0.850651, 0}, // vertices[5]
{0.525731, 0.850651, 0}, // vertices[6]
{0.525731, -0.850651, 0}, // vertices[7]
{-0.525731, -0.850651, 0}, // vertices[8]
{0, -0.525731, -0.850651}, // vertices[9]
{0, 0.525731, -0.850651}, // vertices[10]
{0, 0.525731, 0.850651} // vertices[11]
}
;

static const Color3D colors[] = {
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.5, 0.0, 1.0},
{1.0, 1.0, 0.0, 1.0},
{0.5, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.5, 1.0},
{0.0, 1.0, 1.0, 1.0},
{0.0, 0.5, 1.0, 1.0},
{0.0, 0.0, 1.0, 1.0},
{0.5, 0.0, 1.0, 1.0},
{1.0, 0.0, 1.0, 1.0},
{1.0, 0.0, 0.5, 1.0}
}
;

static const GLubyte icosahedronFaces[] = {
1, 2, 6,
1, 7, 2,
3, 4, 5,
4, 3, 8,
6, 5, 11,
5, 6, 10,
9, 10, 2,
10, 9, 3,
7, 8, 9,
8, 7, 0,
11, 0, 1,
0, 11, 4,
6, 2, 10,
1, 6, 11,
3, 5, 10,
5, 4, 11,
2, 7, 9,
7, 1, 0,
3, 9, 8,
4, 8, 0,
}
;

static const Vector3D normals[] = {
{0.000000, -0.417775, 0.675974},
{0.675973, 0.000000, 0.417775},
{0.675973, -0.000000, -0.417775},
{-0.675973, 0.000000, -0.417775},
{-0.675973, -0.000000, 0.417775},
{-0.417775, 0.675974, 0.000000},
{0.417775, 0.675973, -0.000000},
{0.417775, -0.675974, 0.000000},
{-0.417775, -0.675974, 0.000000},
{0.000000, -0.417775, -0.675973},
{0.000000, 0.417775, -0.675974},
{0.000000, 0.417775, 0.675973},
}
;


glLoadIdentity();
glTranslatef(0.0f,yPos,-3);
glRotatef(rot,1.0f,1.0f,1.0f);
glScalef(scale, scale, scale);

glClearColor(0.0, 0.0, 0.05, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnable(GL_COLOR_MATERIAL);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);
glNormalPointer(GL_FLOAT, 0, normals);
glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisable(GL_COLOR_MATERIAL);
static NSTimeInterval lastDrawTime;
if (lastDrawTime)
{
NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
rot+=50 * timeSinceLastDraw;

if (scaleIncreasing)
{
scale += timeSinceLastDraw;
yPos += timeSinceLastDraw;
if (scale > 2.0)
scaleIncreasing = NO;
}

else
{
scale -= timeSinceLastDraw;
yPos -= timeSinceLastDraw;
if (scale < 1.0)
scaleIncreasing = YES;

}

}

lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}

-(void)setupView:(GLView*)view
{
const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0;
GLfloat size;
glEnable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
CGRect rect = view.bounds;
glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
(rect.size.width / rect.size.height), zNear, zFar);
glViewport(0, 0, rect.size.width, rect.size.height);
glMatrixMode(GL_MODELVIEW);

// Enable lighting
glEnable(GL_LIGHTING);

// Turn the first light on
glEnable(GL_LIGHT0);

// Define the ambient component of the first light
static const Color3D light0Ambient[] = {{0.3, 0.3, 0.3, 1.0}};
glLightfv(GL_LIGHT0, GL_AMBIENT, (const GLfloat *)light0Ambient);

// Define the diffuse component of the first light
static const Color3D light0Diffuse[] = {{0.4, 0.4, 0.4, 1.0}};
glLightfv(GL_LIGHT0, GL_DIFFUSE, (const GLfloat *)light0Diffuse);

// Define the specular component of the first light
static const Color3D light0Specular[] = {{0.7, 0.7, 0.7, 1.0}};
glLightfv(GL_LIGHT0, GL_SPECULAR, (const GLfloat *)light0Specular);

// Define the position of the first light
// const GLfloat light0Position[] = {10.0, 10.0, 10.0};
static const Vertex3D light0Position[] = {{10.0, 10.0, 10.0}};
glLightfv(GL_LIGHT0, GL_POSITION, (const GLfloat *)light0Position);

// Calculate light vector so it points at the object
static const Vertex3D objectPoint[] = {{0.0, 0.0, -3.0}};
const Vertex3D lightVector = Vector3DMakeWithStartAndEndPoints(light0Position[0], objectPoint[0]);
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, (GLfloat *)&lightVector);

// Define a cutoff angle. This defines a 90° field of vision, since the cutoff
// is number of degrees to each side of an imaginary line drawn from the light's
// position along the vector supplied in GL_SPOT_DIRECTION above
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 25.0);

glLoadIdentity();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}

This creates a simple program with our friend, the icosahedron. It rotates, as it did before, but it also moves up and down along the Y axis using a translate transform, and it increases and decreases in size using a scale transform. This uses all the common modelview transforms; we load the identity matrix, do a scale, a rotate, and a translate using the stock OpenGL ES transform functions.

Let's replace each of stock functions with our own matrices. Before proceeding, build and run the application so you know what the correct behavior for our application looks like.

Defining a Matrix


Let's define our own object to hold a matrix. This is just to make our code easier to read:

typedef GLfloat Matrix3D[16];


Our Own Identity Matrix


For our first trick, let's create our own identity matrix. The identity matrix for a 4x4 matrix needs to look like this:

4x4identity.png


Here's a simple function to populate an existing Matrix3D with the identity matrix.

static inline void Matrix3DSetIdentity(Matrix3D matrix)
{
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0;
matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
}

Now, this probably looks wrong at first glance. It looks like we're passing Matrix3D by value, which wouldn't work. However we're using a typedef3 array, and due to C99's array-pointer equivalency, arrays are passed by reference and not by value, so we can just assign the individual values of the arrays and don't have to pass pointers.

I'm using inline functions to eliminate the overhead of a function call. This involves a trade-off (primarily increased code size) and all the code in this article will work just as well as regular C functions. To use them that way, just remove the static inline keywords and place them in a .c or .m file instead of a .h file. Note that the static keyword is correct (and a good idea) in C and Objective-C programs, but if you're using C++ or Objective-C++, then you should probably exclude it. The GCC Manual recommends making C inline functions like this one static because doing so allows the compiler to remove the generated assembly for unused inline functions. However, if you're using C++ or Objective-C++ the static keyword can potentially impact linkage behavior and offers no real benefit.

Okay, let's use this new function to replace the call to glLoadIdentity(). Delete the call to glLoadIdentity() and replace it with the following code:

    static Matrix3D    identityMatrix;
Matrix3DSetIdentity(identityMatrix);
glLoadMatrixf(identityMatrix);

So, we declare a Matrix3D, populate it with the identity matrix, and then we load that matrix using glLoadMatrixf(), which replaces the active matrix (in our case, the modelview matrix) with the identity matrix. That's exactly the same thing as calling glLoadIdentity(). Exactly. Build and run the program now, and it should look exactly the same as before. No difference.

Now, you know really know what glLoadIdentity() does since you've done it manually. Let's continue.

Matrix Multiplication


Before we can implement any more transformations, we need to write a function to multiply two matrices. Remember, multiplying matrices is how we combine two matrices into a single matrix. We could write a generic matrix multiplication method that would work with any size array and that used for loops to do the calculation, but let's just do it without the loop. Loops have a tiny bit of overhead associated with them, and unrolling loops in functionality that gets called a lot can make a difference. Since OpenGL ES's matrices are always 4x4, the fastest multiplication is to just do each calculation. Here's our matrix multiplication:

static inline void Matrix3DMultiply(Matrix3D m1, Matrix3D m2, Matrix3D result)
{
result[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3];
result[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3];
result[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3];
result[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3];

result[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7];
result[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7];
result[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7];
result[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7];

result[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11];
result[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11];
result[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11];
result[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11];

result[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15];
result[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15];
result[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
result[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];
}


Again, this function doesn't allocate any memory, it just populates an existing result array (result) by multiplying the other two arrays. The result array should not be one of the two values being multiplied, however, because that would yield incorrect results since values are changed that will be used again.

But, wait… this can actually be faster. at least when you run the program on the iPhone instead of the simulator. The iPhone has four vector processors that are able to do floating point math much faster than the iPhone's CPU can. Taking advantage of these vector processors, however, requires writing ARM6 assembly because there are no libraries for accessing the vectors from C.

Fortunately, somebody's already figured out how to a matrix multiply using the vector processors. The VFP Math Library contains a lot of vectorized functionality, and it's released under a fairly permissive license. So, I took the VFP Math Library vectorized matrix multiply and incorporated it into my method, so that when it's run on the device, the vectorized version is used, but the regular version is used when run on the simulator (note that I've included the original comments with ownership and licensing information and identified that the code has been modified in order to comply with VFP Math Library license):

/* 
These define the vectorized version of the
matrix multiply function and are based on the Matrix4Mul method from
the vfp-math-library. This code has been modified, but is still subject to
the original license terms and ownership as follow:

VFP math library for the iPhone / iPod touch

Copyright (c) 2007-2008 Wolfgang Engel and Matthias Grundmann
http://code.google.com/p/vfpmathlibrary/

This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.
*/

#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
#define VFP_CLOBBER_S0_S31 "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", \
"s9", "s10", "s11", "s12", "s13", "s14", "s15", "s16", \
"s17", "s18", "s19", "s20", "s21", "s22", "s23", "s24", \
"s25", "s26", "s27", "s28", "s29", "s30", "s31"

#define VFP_VECTOR_LENGTH(VEC_LENGTH) "fmrx r0, fpscr \n\t" \
"bic r0, r0, #0x00370000 \n\t" \
"orr r0, r0, #0x000" #VEC_LENGTH "0000 \n\t" \
"fmxr fpscr, r0 \n\t"

#define VFP_VECTOR_LENGTH_ZERO "fmrx r0, fpscr \n\t" \
"bic r0, r0, #0x00370000 \n\t" \
"fmxr fpscr, r0 \n\t"

#endif
static inline void Matrix3DMultiply(Matrix3D m1, Matrix3D m2, Matrix3D result)
{
#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
__asm__ __volatile__ ( VFP_VECTOR_LENGTH(3)

// Interleaving loads and adds/muls for faster calculation.
// Let A:=src_ptr_1, B:=src_ptr_2, then
// function computes A*B as (B^T * A^T)^T.

// Load the whole matrix into memory.
"fldmias %2, {s8-s23} \n\t"
// Load first column to scalar bank.
"fldmias %1!, {s0-s3} \n\t"
// First column times matrix.
"fmuls s24, s8, s0 \n\t"
"fmacs s24, s12, s1 \n\t"

// Load second column to scalar bank.
"fldmias %1!, {s4-s7} \n\t"

"fmacs s24, s16, s2 \n\t"
"fmacs s24, s20, s3 \n\t"
// Save first column.
"fstmias %0!, {s24-s27} \n\t"

// Second column times matrix.
"fmuls s28, s8, s4 \n\t"
"fmacs s28, s12, s5 \n\t"

// Load third column to scalar bank.
"fldmias %1!, {s0-s3} \n\t"

"fmacs s28, s16, s6 \n\t"
"fmacs s28, s20, s7 \n\t"
// Save second column.
"fstmias %0!, {s28-s31} \n\t"

// Third column times matrix.
"fmuls s24, s8, s0 \n\t"
"fmacs s24, s12, s1 \n\t"

// Load fourth column to scalar bank.
"fldmias %1, {s4-s7} \n\t"

"fmacs s24, s16, s2 \n\t"
"fmacs s24, s20, s3 \n\t"
// Save third column.
"fstmias %0!, {s24-s27} \n\t"

// Fourth column times matrix.
"fmuls s28, s8, s4 \n\t"
"fmacs s28, s12, s5 \n\t"
"fmacs s28, s16, s6 \n\t"
"fmacs s28, s20, s7 \n\t"
// Save fourth column.
"fstmias %0!, {s28-s31} \n\t"

VFP_VECTOR_LENGTH_ZERO
: "=r" (result), "=r" (m2)
: "r" (m1), "0" (result), "1" (m2)
: "r0", "cc", "memory", VFP_CLOBBER_S0_S31
);
#else
result[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3];
result[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3];
result[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3];
result[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3];

result[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7];
result[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7];
result[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7];
result[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7];

result[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11];
result[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11];
result[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11];
result[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11];

result[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15];
result[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15];
result[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
result[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];
#endif
}

Now that we have the ability to multiply matrices together we can combine multiple matrices. Since our matrix multiply is hardware accelerated and OpenGL ES does not hardware accelerate matrix by matrix multiplication, our version should actually be a tiny bit faster than using the stock transformations4. Let's add the translate transformation now.

Our Own Translate


If you recall earlier, one of the reasons why we need to use a 4x4 matrix instead of a 3x3 matrix was because we needed an extra column for translation information. Indeed, this is what a translation matrix looks like:

translatematrix.png


We can turn that into a function like this:

static inline void Matrix3DSetTranslation(Matrix3D matrix, GLfloat xTranslate, GLfloat yTranslate, GLfloat zTranslate)
{
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0;
matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
matrix[11] = 0.0;
matrix[12] = xTranslate;
matrix[13] = yTranslate;
matrix[14] = zTranslate;
}

Now, how do we incorporate that into our drawView: method? Well, we can delete the call to glTranslatef(), and replace it with code that declares another matrix, populates it with the appropriate translation values, multiplies that matrix by the existing matrix and then load the result into the OpenGL, right?

    static Matrix3D    identityMatrix;
Matrix3DSetIdentity(identityMatrix);
static Matrix3D translateMatrix;
Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
static Matrix3D resultMatrix;
Matrix3DMultiply(identityMatrix, translateMatrix, resultMatrix);
glLoadMatrixf(resultMatrix);


Well, yeah, that'll work, but it's doing unnecessary work. Remember, if you multiply any matrix by the identity matrix, the result is itself. So when working with our own matrices, we no longer need to load the identity matrix first if we're using any other transformation. Instead, we can just create the translation matrix and load that:

    static Matrix3D    translateMatrix;
Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
glLoadMatrixf(translateMatrix);


Since we don't have to load the identity matrix, we saved ourself a little tiny bit of work each time through this method. Also notice that I'm declaring the Matrix3Ds as static. We don't want to constantly allocate and deallocate memory. We know we're going to need this matrix several times every second all while the program is running, so by declaring it static, we cause it to stick around and be reused rather than having the overhead of constant memory allocation and deallocation.

Our Own Scaling Transformation


A matrix to change the size of objects looks like this:

scalematrix.png


A value of 1.0 for x, y, or z indicates that there is no change in scale in that direction. A 1.0 for all three would result in (you guessed it) the identity matrix. If you pass a 2.0, it will double the size of the object along that axis. We can turn the scaling matrix into an OpenGL ES matrix like this:

static inline void Matrix3DSetScaling(Matrix3D matrix, GLfloat xScale, GLfloat yScale, GLfloat zScale)
{
matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
matrix[0] = xScale;
matrix[5] = yScale;
matrix[10] = zScale;
matrix[15] = 1.0;
}

Now, we are going to have to multiply matrices because we want to apply more than one trasnformation. To apply both a scaling and a rotation ourself, we need to multiply those two matrices together. Delete the call to glScalef() and the previous code we wrote and replace with this:

    static Matrix3D    translateMatrix;
Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
static Matrix3D scaleMatrix;
Matrix3DSetScaling(scaleMatrix, scale, scale, scale);
static Matrix3D resultMatrix;
Matrix3DMultiply(translateMatrix, scaleMatrix, resultMatrix);
glLoadMatrixf(resultMatrix);

We create a matrix and populate it with the appropriate translate values. Then we create a scaling matrix and populate it with the appropriate values. Then we multiply those two together and load them into the model view matrix. Now for the tough one. Rotation.

Our Own Rotation


Rotation is a little tougher. We can create matrices for rotation around each of the axes. We already know what Z-axis rotation looks like:

zrotation.png


X-axis rotation looks similar:

xaxisrotation.png


And so does Y-axis rotation:

yaxisrotation.png


These three rotations can be turned into OpenGL matrices with these functions:

static inline void Matrix3DSetXRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
matrix[0] = matrix[15] = 1.0;
matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
matrix[7] = matrix[8] = 0.0;
matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;

matrix[5] = cosf(degrees);
matrix[6] = -fastSinf(degrees);
matrix[9] = -matrix[6];
matrix[10] = matrix[5];
}

static inline void Matrix3DSetXRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
Matrix3DSetXRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}

static inline void Matrix3DSetYRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
matrix[0] = cosf(degrees);
matrix[2] = fastSinf(degrees);
matrix[8] = -matrix[2];
matrix[10] = matrix[0];
matrix[1] = matrix[3] = matrix[4] = matrix[6] = matrix[7] = 0.0;
matrix[9] = matrix[11] = matrix[13] = matrix[12] = matrix[14] = 0.0;
matrix[5] = matrix[15] = 1.0;
}

static inline void Matrix3DSetYRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
Matrix3DSetYRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}

static inline void Matrix3DSetZRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
matrix[0] = cosf(degrees);
matrix[1] = fastSinf(degrees);
matrix[4] = -matrix[1];
matrix[5] = matrix[0];
matrix[2] = matrix[3] = matrix[6] = matrix[7] = matrix[8] = 0.0;
matrix[9] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
matrix[10] = matrix[15] = 1.0;
}

static inline void Matrix3DSetZRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
Matrix3DSetZRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}


There's two methods for each axis' rotation, one to set by radians and another to set by degrees. These three matrices represent what are called Eular angles The problem with Eular angles is that we have to apply rotations along multiple axes sequentially, and when we set rotation on all three angles, we'll end up experiencing a phenomenon called gimbal lock, which results in the loss of rotation on one axis. In order to avoid this problem, we need to create a single matrix that can handle rotation on multiple axes. In addition to eliminating problem gimbal lock, this will also save processing overhead when rotations are needed on more than one axis.

Now, honestly, I don't pretend to understand the math behind this one. I've read doctoral theses on this (a matrix representation of quaternions) but the math just doesn't fully sink in, so you and I are just going to take it on faith that this multi-rotation matrix works (it does). This matrix assumes a single angle designated N, and a vector expressed as three floating point values. Each component of the vector will be multiplied by N to result in the angle of rotation about that axis:

multirotatematrix.png


This matrix requires that the vector passed in be a unit vector (aka a normalized vector), so we have to ensure that before populating the matrix. Expressed as an OpenGL matrix, it would be:

static inline void Matrix3DSetRotationByRadians(Matrix3D matrix, GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
{
GLfloat mag = sqrtf((x*x) + (y*y) + (z*z));
if (mag == 0.0)
{
x = 1.0;
y = 0.0;
z = 0.0;
}

else if (mag != 1.0)
{
x /= mag;
y /= mag;
z /= mag;
}


GLfloat c = cosf(angle);
GLfloat s = fastSinf(angle);
matrix[3] = matrix[7] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
matrix[15] = 1.0;


matrix[0] = (x*x)*(1-c) + c;
matrix[1] = (y*x)*(1-c) + (z*s);
matrix[2] = (x*z)*(1-c) - (y*s);
matrix[4] = (x*y)*(1-c)-(z*s);
matrix[5] = (y*y)*(1-c)+c;
matrix[6] = (y*z)*(1-c)+(x*s);
matrix[8] = (x*z)*(1-c)+(y*s);
matrix[9] = (y*z)*(1-c)-(x*s);
matrix[10] = (z*z)*(1-c)+c;

}

static inline void Matrix3DSetRotationByDegrees(Matrix3D matrix, GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
{
Matrix3DSetRotationByRadians(matrix, angle * M_PI / 180.0, x, y, z);
}


This multi-rotation version works exactly the same way as glRotatef().

Now that we've replaced all three of the stock functions, here's the new drawView: method using only our own matrices and no stock transformation. The new matrix code is bold:

- (void)drawView:(GLView*)view;
{

static GLfloat rot = 0.0;
static GLfloat scale = 1.0;
static GLfloat yPos = 0.0;
static BOOL scaleIncreasing = YES;

// This is the same result as using Vertex3D, just faster to type and
// can be made const this way
static const Vertex3D vertices[]= {
{0, -0.525731, 0.850651}, // vertices[0]
{0.850651, 0, 0.525731}, // vertices[1]
{0.850651, 0, -0.525731}, // vertices[2]
{-0.850651, 0, -0.525731}, // vertices[3]
{-0.850651, 0, 0.525731}, // vertices[4]
{-0.525731, 0.850651, 0}, // vertices[5]
{0.525731, 0.850651, 0}, // vertices[6]
{0.525731, -0.850651, 0}, // vertices[7]
{-0.525731, -0.850651, 0}, // vertices[8]
{0, -0.525731, -0.850651}, // vertices[9]
{0, 0.525731, -0.850651}, // vertices[10]
{0, 0.525731, 0.850651} // vertices[11]
}
;

static const Color3D colors[] = {
{1.0, 0.0, 0.0, 1.0},
{1.0, 0.5, 0.0, 1.0},
{1.0, 1.0, 0.0, 1.0},
{0.5, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.0, 1.0},
{0.0, 1.0, 0.5, 1.0},
{0.0, 1.0, 1.0, 1.0},
{0.0, 0.5, 1.0, 1.0},
{0.0, 0.0, 1.0, 1.0},
{0.5, 0.0, 1.0, 1.0},
{1.0, 0.0, 1.0, 1.0},
{1.0, 0.0, 0.5, 1.0}
}
;

static const GLubyte icosahedronFaces[] = {
1, 2, 6,
1, 7, 2,
3, 4, 5,
4, 3, 8,
6, 5, 11,
5, 6, 10,
9, 10, 2,
10, 9, 3,
7, 8, 9,
8, 7, 0,
11, 0, 1,
0, 11, 4,
6, 2, 10,
1, 6, 11,
3, 5, 10,
5, 4, 11,
2, 7, 9,
7, 1, 0,
3, 9, 8,
4, 8, 0,
}
;

static const Vector3D normals[] = {
{0.000000, -0.417775, 0.675974},
{0.675973, 0.000000, 0.417775},
{0.675973, -0.000000, -0.417775},
{-0.675973, 0.000000, -0.417775},
{-0.675973, -0.000000, 0.417775},
{-0.417775, 0.675974, 0.000000},
{0.417775, 0.675973, -0.000000},
{0.417775, -0.675974, 0.000000},
{-0.417775, -0.675974, 0.000000},
{0.000000, -0.417775, -0.675973},
{0.000000, 0.417775, -0.675974},
{0.000000, 0.417775, 0.675973},
}
;

static Matrix3D translateMatrix;
Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
static Matrix3D scaleMatrix;
Matrix3DSetScaling(scaleMatrix, scale, scale, scale);
static Matrix3D tempMatrix;
Matrix3DMultiply(translateMatrix, scaleMatrix, tempMatrix);
static Matrix3D rotationMatrix;
Matrix3DSetRotationByDegrees(rotationMatrix, rot, 1.0f, 1.0f, 1.0f);
static Matrix3D finalMatrix;
Matrix3DMultiply(tempMatrix, rotationMatrix, finalMatrix);
glLoadMatrixf(finalMatrix);


glClearColor(0.0, 0.0, 0.05, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnable(GL_COLOR_MATERIAL);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glColorPointer(4, GL_FLOAT, 0, colors);
glNormalPointer(GL_FLOAT, 0, normals);
glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);

glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisable(GL_COLOR_MATERIAL);
static NSTimeInterval lastDrawTime;
if (lastDrawTime)
{
NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
rot+=50 * timeSinceLastDraw;

if (scaleIncreasing)
{
scale += timeSinceLastDraw;
yPos += timeSinceLastDraw;
if (scale > 2.0)
scaleIncreasing = NO;
}

else
{
scale -= timeSinceLastDraw;
yPos -= timeSinceLastDraw;
if (scale < 1.0)
scaleIncreasing = YES;

}

}

lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}


Wacky and Wonderful Custom Matrices


Are you still with me? This has been a doozy of an installment, hasn't it? We can't stop quite yet, though. And here's the reason why - so far I've only shown you how to recreate existing functionality in OpenGL. Yeah, what we've done can give a tiny performance boost thanks to the fact that our matrix multiplication is hardware accelerated, but that's not really enough of a justification in 99% of the cases to recreate the wheel like this.

But, there are other benefits to handling the matrix transformations yourself. You can, for example, create transformations that OpenGL ES doesn't have built-in to it. For example, we could do a shear transformation. Shearing is basically skewing an object along two axes. If you applied a shear axis to a tower, you'd get the leaning tower of Pisa. Here is what the shear matrix looks like:

shearmatrix.png


Here's what it looks like in code:

static inline void Matrix3DSetShear(Matrix3D matrix, GLfloat xShear, GLfloat yShear)
{
matrix[0] = matrix[5] = matrix[10] = matrix[15] = 1.0;
matrix[1] = matrix[2] = matrix[3] = 0.0;
matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
matrix[1] = xShear;
matrix[4] = yShear;
}



And if we add the shear matrix, we get:

iPhone SimulatorScreenSnapz001.jpg


Try doing that with stock calls. You can also combine matrix calls. So we could, for example, create a single function to create a translate and scale matrix without having to do even a single matrix multiplication.

Exit the Matrix



Matrices are a huge and often misunderstood topic, one that many people (including me) struggle with understanding. Hopefully this gives you enough of an understand of what's going on under the hood, and it also gives you a library of matrix-related functions you can use in your own applications. If you want to download the project and try it out yourself, please feel free. I've defined two constants that you can change to switch between using stock transforms and custom transforms, and also to turn on and off the shear transformation. You can find these in GLViewController.h:

#define USE_CUSTOM_MATRICES 1
#define USE_SHEAR_TRANSFORM 1


Setting them to 1 turns them on, setting them to 0 turns them off. I've also updated the OpenGL ES Xcode Template with the new Matrix functions, including the vectorized matrix multiply function. Best of luck with it, and don't worry if you don't fully grok, this is hard stuff, and for 99% of what most people do in OpenGL ES, you don't need to fully understand projective space, homogeneous coordinates, or linear transformations, so as long as you get the big picture, you should be fine.

With great thanks to Noel Llopis of Snappy Touch for his help and patience. If you haven't checked out his awesome Flower Garden, you really should - it's an absolute treat.


Footnotes
  1. No, that's not an error. Stage right is what the audience would perceive as going to the left. Our icosahedron goes off to the left, so it is existing "stage right".
  2. Actually, not quite. When you call glLoadIdentity(), you're loading the 4x4 identity matrix, that illustration shows the 3x3 identity matrix.
  3. You might want to use a struct instead of a typedef to gain type safety. If you do that, then you'll have to make sure that you specifically pass parameters in by reference because structs are NOT automatically passed by reference, unlike arrays.
  4. Using Shark, the drawView: method went from being .7% of processing time to .1% of processing time. A substantial improvement in the speed of that method, but not in the overall application performance.