Wednesday, June 24, 2009

Using 3D Models from Blender in OpenGL ES

One question I get asked a lot is "how do I load 3D models into OpenGL ES?".

Of course, the answer to that isn't a simple one. If you followed my my earlier posts on importing Wavefront OBJ files, you're probably aware of that already. There are many file formats, and none of them are ideal for loading into a resource-constrained device like the iPhone.

Apple recommends that 3D objects be stored in header files as static arrays. This obviates the need to do any loading or transforming or conversion at all. But… there's not really any easy way to create those header files from within 3D software packages.

Well, there is one way now. The open source 3D program called Blender has a very extensible architecture, making it relatively easy to write custom export modules. Blender's scripting architecture is based on Python, a language that I'm not particularly familiar with, but I hacked out something that works. So, let's say that you've got an object in Blender:

Click to see larger version


And you want to load it into a program you're writing for your iPhone:

Click to see larger version
Note, I know that the screenshots don't actually match - I cropped the texture after exporting from Blender to make it more iPhone sized - I didn't want to resize it for fear that the texture would get too small to see.


All you have to do is take this script and install it into the scripts folder in Blender.

There's a catch, however. The Mac OS X version of Blender doesn't follow the sames rules as it does for other platforms. If you look up how to install a script in Blender, it will tell you to add the script to ~/.blender/scripts/. That directory doesn't get created on the Mac, and if you manually create it, you will lose all the delivered scripts.

Instead, you have to actually install the script into the Blender.app bundle. To make things even more gnarley, the scripts are stored in an invisible folder inside of the Blender bundle. The easiest way to copy the script is to use Terminal.app and the unix cp command. The scripts folder is located at:
/path/to/Blender.app/Contents/MacOS/.blender/scripts
So, if Blender is installed in your Applications folder inside of a folder called Blender, you could use the following command to copy the unzipped script into Blender:
cp objc.py /Applications/Blender/Blender.app/Contents/MacOS/.blender/scripts/

Every time you upgrade your Blender install, you'll have to reinstall this script. The next time you start Blender after copying the script, you will find an entry in the Export menu called Objective-C Header (.h). This is intended to be used with texture-mapped objects, and only exports the active object, not all selected objects or all objects. I am going to create a separate version for non-texture mapped objects. This script will create an inefficient version of non-texture-mapped objects because of the way OpenGL ES uses texture coordinates.

I've also created a sample project that shows how to use the exported header file. It's actually quite easy. You just pass the arrays from the header into the various OpenGL calls, like this:

    glVertexPointer(3, GL_FLOAT, 0, CubeVertices);
glNormalPointer(GL_FLOAT, 0, CubeNormals);
glTexCoordPointer(2, GL_FLOAT, 0, CubeTexCoords);

You can then either use glDrawArrays() or glDrawElements() as you see fit. There are indices provided for glDrawElements(), but since the object is exported as triangles, glDrawArrays() works just fine too.

Before running the script, make sure you put the object into edit mode and select Mesh->Faces->Convert Quads to Triangles. This script will not convert to triangles for you, and OpenGL ES requires triangles. You also need to load and bind the texture you used in Blender, or one that you created based on Blender's exported UV template.

As I said before, I am not a very experienced Python programmer, so if you want to suggest improvements, I'm happy to hear those suggestions. You will not hurt my feelings one little bit. This is what I call a "brute force" script - it gets the job done, but perhaps not with the elegance it should have.

If you want to play around with the simple Blender project I used in the test Xcode project, you can find that right here (right-click and save to disk).

I hope to create a future version that interleaves the data as suggested by Apple, but for now, I was just thrilled to get something working.



46 comments:

Chaotic Box said...

Thanks for the script! Sorry mucked up my first post :)

You can create a "scripts" folder in the same directory as your blender.app and drop the script in there. No need to hack the app bundle.

Jeff LaMarche said...

Chaotic - thanks for the info, I'll try it out and if it works, I'll amend the post with that info. Thanks!

Jeff LaMarche said...

I'm not having luck getting it to work. The Blender 3D wikibooks page suggests adding the .blender folder to your home directory and replacing the version inside the app bundle to be a symlink to the one in your home folder.

If you have more information on getting this to work, I'd love to hear it.

Chaotic Box said...

Sorry - it's been so long since I did this that I thought it was default behaviour..

You can actually use any folder for scripts - just go into the blender preference panel, hit the "file paths" tab then set the "Python Scripts" path to your preferred folder. Be sure to hit "File->Save Default Settings" afterwards to make the change stick.

Jeff LaMarche said...

Ah.. cool. But then you need to move all the scripts there, right? It doesn't combine the two folders does it?

Chaotic Box said...

The scripts from the app bundle show up even with a modified path in the preferences.

Jeff LaMarche said...

Oh, that's good to know - if you create a folder at the default location (~/.blender/scripts/) then you lose all the shipped ones.

Jim Schmidt said...

I just popped in to see if this had been noted -- now that it has, what are the chances someone here knows how to quickly translate this to...Maya?

(If not, I'll attempt it -- but I, too, have zero python experience)

Stuart said...

Great work, Jeff. Look forward to playing with this in the future!

Charlie said...

Jeff, thanks for all the great posts. Lovin' this OpenGL ES series!

I've been thinking about this statement: "Apple recommends that 3D objects be stored in header files as static arrays." I've been leaning towards creating dense binary files of my 3D models that could be quickly loaded in, or just mmap'd to be really fast to load. While this will take some cycles, I think it would leave me with a more flexible app that could, for example, dynamically get new models from the web.

I'm curious what other tradeoffs might exist between compiled-in vs. loaded-in models that I might be missing. Your thoughts?

Jeff LaMarche said...

Jim:

I don't personally use Maya. Since I'm not a 3D graphics professional, a free and open source product appeals to me, and for my uses Blender works great.

I know that Brian Greentstone's game development book which I believe can now be downloaded for free in PDF form from Pangea's website has a chapter on writing export scripts for Maya, so you might want to check that out. I believe there's a full-featured exporter there that you can probably use as a starting point.

Charlie:

I honestly don't know the extent of the tradeoffs. Static arrays in header files are not the only option, and there are times when they won't work, such as your example where you want to provide new downloaded models. To the extent that you can store models this way in header, it's not a bad idea, but I don't think it's necessarily bad or wrong to use other mechanisms. It would be an interesting experiment to try loading the same model both ways and then profiling to see what the extent of the differences are, but I haven't done that and am basically just passing on a recommendation from an Apple Engineer made over a year ago.

Most file formats out there are not optimized for viewing and displaying, and contain additional information that is just noise as far as an iPhone program would be concerned (like cameras, background images, etc).

But if you're willing to put the time in to design a custom binary format tailored to your needs, I'm going to guess that your performance will be fine. I would just stay away from general format files like Wavefront because you end up burning a fair amount of memory and processing power to extract and convert the data to the format that OpenGL ES needs.

If you're using Blender and know a little Python, you could write a script to export to a custom file format quite easily. Same goes for other 3D packages, I just don't know the scripting architecture for any 3D program except blender.

Jeff

warmi said...

Or simply use the POD format from Imagination Technologies.
They offer an entire SDK which includes classes for reading/writing of POD files, PVRT textures etc ...
The format itself is very lightweight ... essentially not much more than vertex/index arrays ready to be fed to the OpenGL driver ( it also supports skeletal animations)

Furthermore , the SDK contains exporters for 3dMAX, Maya and a generic one for Collada files.

The whole things comes with full source code so you can pick and choose whatever you want or simply compile the whole things as a static library and link with your project ( that's what I do)

http://www.imgtec.com/powervr/insider/sdk/KhronosOpenGLES1xMBX.asp

http://www.imgtec.com/powervr/insider/powervr-utilities.asp

warmi said...

The correct link for the SKD:

http://www.imgtec.com/powervr/insider/powervr-sdk.asp

eric said...

Does your Exporter script correctly export models more complex than a Cube? I tried applying this to a Model Lego Brick but only a few hundred faces get drawn in the Xcode Simulator. Since the Lego Brick is comprised of 8 cylinders on a rectangular cube the Vertex/Face count is 528/780. What am I doing wrong?

Mark said...

Tested the script with just 1 cylinder in blender and it doesn't render correctly. Anyone else had any luck with this?

Conor McCluskey said...

Thanks for the script Jeff. I was wondering if you had any tips about texturing on more complex objects, e.g. I draw a cube with a different texture on each face. I bind the texture and draw using drawArrays with 4 vertices per face.

I am trying to replace that with a rounded cube I created in blender but I only want to add textures to the 6 main faces. I draw each of the 47 triangles specifically using drawArrays with 3 vertices then try to enable texturing/bind the texture before I draw the 2 triangles for a face but the texture doesn't appear.

Does the TexCoordArray not work for trying to draw a square texture over 2 triangles?

Also, the faces don't seem to get generated correctly for me. Even though I convert quads to triangles, I still seem to get some 4-sided faces that cause compile errors.

BTW if you are looking for more topics, I would love to hear your thoughts on shadows and reflections. Many thanks and keep up the great work!

Chris said...

That script was a nice addition. In my current project we used the OBJ/MTL formats, but found it essential to create a compressed format and store that format into a sqlite database. Our loading time of 1000-1800 vertices is around .03 - .2 seconds.

Nice article Jeff.

Regards,
Chris
MaxPowerSoft, LLC

Claire Harrison said...

I'm really enjoying your blogs - particularly these on openGL. But, when I use this export script from blender the resulting .h file has only a small portion of the faces in my model. The overall shape is roughly the same, but the level of detail is not even close. I have converted them all the triangles and double checked all the steps but think perhaps the script isn't finding all the faces in the model.

Claire Harrison said...

I think I figured it out ... the Blender tutorial shows how to 'crease edges' which lets you make a more complex, smoother shape but it doesn't actually create new faces so the export script doesn't recognize them.

I was able to make a more complex shape without using this method and it exported just fine!

Cameron said...

For some reason when I try to export it blender gives me an error saying "Script error, check log" or something similar. Am I doing something wrong?

Dan said...

Thanks for the script.

I am new to the 3D game programming world so this may sound dumb. How would you translate different objects from this script if they are all in one vertex array?

Thanks

croustibapt said...

Hye,

I tried to load your blend file and make the export without changing anything and I don't have the same header file as you give in your project folder (cube.h).

I only have one array.

Thanks for your help.

Ekke said...

Hi, tanks for the tutorial!
It does not work for me thow.. I have the same problem as "croustibapt". When i generate the header file i get a file that only contains one array. Any ideas?

Thanks!

HeikoB said...

An alternative could be obj2opengl. It's a script that converts wavefront obj files to C/C++ arrays that can be used by OpenGL ES directly. It is not limited to blender and does not rely on it to be installed.

joshua said...

data entry india applications let you recognize forms filled in by hand in order to automate data entry tasks. Handprint recognition has come a long way in the past few years. Cost and complexity have gone down while accuracy gets increasingly higher. This has finally made this technology available to small and mid-sized businesses.

Zephyr said...
This comment has been removed by the author.
Zephyr said...

good informative article.

Thanks
Vidyadhar Kudumula

all3dmode said...

Now Free Download Hight Quality 3d models 3d objects for 3DSMAX, Maya, Softimage, Lightwave. (3D Cars, 3D People, 3D Furnitures, etc). For use in all your 3D Animation and multimedia works. animation | art | vr | tutorials | 3d models | 2d | software | plug-ins | textures | materials | scripts | poser and find out new 3d jobs and hot 3d video. http://www.all3dmodels.com

all3dmode said...

free download all 3d models, 3d Video Tutorials, 3D Materials, 3D Textures, Vray Materials, 3D Girls, and latest 3d jobs
http://www.all3dmodel.com

biv said...

Hi jeff, please if I am not asking for too much can you give a step by step tutorial on exporting the 3d model from blender to the iphone....I am new to this and after looking at your Export test project I became confused...I should appreciate it if you could give like a tutorial on the steps to follow or take when exporting from blender to the iphone.
Thanks

cigumo said...

First of all, thanks for the tutorial.

I've found a problem. The textures appear flipped vertically. I believe this is because the script exports the UV coordinate with origin in the bottom-left corner of the texture, but in the Xcode project, the textures are loaded with the UV coordinate in the top-left corner.

An easy fix is changing line 54 of the script to:
out.write('/*t:*/{%f, %f}' % ( uvert.x, 1.0-uvert.y ) )

Haille Selssie said...

I'm having the same problem as Dan, when I export everything ends up in one vertex array called ModelNameVertexData...

Is there something I should do in Blender to get the Vertices, Faces and Normals?

Solano said...
This comment has been removed by the author.
Solano said...

Thank you very much, your tutorials are great!!!
But i have also the problem with the single array. Could it be that we are using a newer version of blender? Which version did you use?

Solano

xnav said...

What looks like a single array is actually multi-dimensional. Don't use this as described in the tutorial:
glVertexPointer(3, GL_FLOAT, 0, CubeVertices);
glNormalPointer(GL_FLOAT, 0, CubeNormals);
glTexCoordPointer(2, GL_FLOAT, 0, CubeTexCoords);

But rather this:
glVertexPointer(3, GL_FLOAT, sizeof(TexturedVertexData3D), &CubeVertexData[0].vertex);
glNormalPointer(GL_FLOAT, sizeof(TexturedVertexData3D), &CubeVertexData[0].normal);
glTexCoordPointer(2, GL_FLOAT, sizeof(TexturedVertexData3D), &CubeVertexData[0].texCoord);

as documented in the comments generated at the bottom of the exported .h file.

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

Lucas Mendes said...
This comment has been removed by the author.
Lucas Mendes said...
This comment has been removed by the author.
antywong said...

The rounded shape of speedy 30 features textile fake louis vuitton lining and leather trimmings with shiny Louis Vuitton Monogram ldylle Romance Encre golden brass. Sized at 11.8" x 8.3" x 6.7", the large capacity Hermes Original Python Birkin 30 Grey of this bag is enough for handbags review daily essentials; you can put bags wholesale everything into this city bag. It also fits for Hermes Clemence Jypsiere 34 Purple every occasion and perfectly goes with any outfits mfakng100910.

antywong said...

The rounded shape of speedy 30 features textile fake louis vuitton lining and leather trimmings with shiny Louis Vuitton Monogram ldylle Romance Encre golden brass. Sized at 11.8" x 8.3" x 6.7", the large capacity Hermes Original Python Birkin 30 Grey of this bag is enough for handbags review daily essentials; you can put bags wholesale everything into this city bag. It also fits for Hermes Clemence Jypsiere 34 Purple every occasion and perfectly goes with any outfits mfakng100910.

Esteban said...

Hi! This is a great blog. I've learnt so much from it. I do have a question regarding this particular ExportTest Xcode project. In the -drawView: method, the line "glDrawElements..." has been commented out, and the cube is currently being drawn with "glDrawArrays...". That works fine. I checked all the faces to make sure vertices 0, 1, 2; 3, 4, 5; ... etc made up all each one of the 12 triangular faces of our cube. However, just commenting out the "glDrawArrays..." line and drawing the cube with "glDrawElements..." renders the wrong shape.

So far it's very clear to me that, in this case, both ways of passing on the vertex information should render the exact same image, but this is not the case!

Why is glDrawElements(GL_TRIANGLES, kCubeNumberOfVertices, GL_UNSIGNED_BYTE, CubeFaces) not working?

I hope you can help me out with this. Thank you!

Esteban said...

Never mind! I figured it out. I just needed to change glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, CubeFaces) to glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, CubeFaces).

After reviewing all the code, I found out that Face3D is defined as a structure of three GLushorts, and after doing that, the cube displays correctly.

Thank you for your postings!

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels

Jeroen said...

Hi,

I have tried a couple of things, but I can't get the add-on to show up in both the add-on settings, nor the export menu.

I'm using Blender 2.5 beta, r34076.

I have tried:

1) user settings > add-ons > install add-on

The script does not show up in the add-on list to activate or deactivate it. The script also did not show up in File > Export. I tried restarting the app, no effect.

2) create .blender/scripts in the package


The script does not show up in the add-on list to activate or deactivate it. The script also did not show up in File > Export. I tried restarting the app, no effect.

Any ideas?

Jeroen

Jeroen said...

OK, I found your GIT project in another post and the script there is loading in Blender 2.56. Well, not completely.

I can now see it in the disabled add-ons list, but for some reason it won't enable. I've enabled other add-ons before, but for some reason this one refuses.

Thanks in advance for your advice.

jmerk said...

The one vertex/array problem doesn't seem to have been address? I am also not getting the same thing as the project file that you posted.

Has anyone figured out why?