How To Export Blender Models to OpenGL ES: Part 1/3
Learn how to export blender models to OpenGL ES in this three part tutorial series! By Ricardo Rendon Cepeda.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How To Export Blender Models to OpenGL ES: Part 1/3
50 mins
- Getting Started
- A Simple Blender Cube
- The OBJ File Format
- Exporting an OBJ From Blender
- Analyzing Your OBJ File
- Building an OBJ to OpenGL ES Command Line Tool
- Project Setup
- Project Directory
- Input/Output, Files and Strings
- Command Line Arguments
- The Model Info
- The Model Data
- Generating the Header File (.h)
- Generating the Implementation File (.c)
- Building the Model Viewer iOS App
- Project Setup
- Adding a GLKit View Controller
- Using Your Storyboard
- Drawing a Gray Screen
- Creating a GLKBaseEffect
- Rendering Your Model: Geometry
- Rendering Your Model: Texture
- Rendering Your Model: Light
- Rendering Your Model: Animation
- Where to Go From Here?
Generating the Implementation File (.c)
The implementation file will do the heavy lifting of initializing your arrays with their OBJ data. There is considerably more code in this section than in the previous one, but now that you’re comfortable with the file output process, it should be a breeze the second time around!
Add the following function to main.cpp, just above main()
:
void writeCvertices(string fp, string name, Model model)
{
// Create C file
ofstream outC;
outC.open(fp);
if(!outC.good())
{
cout << "ERROR CREATING C FILE" << endl;
exit(1);
}
// Write to C file
outC << "// This is a .c file for the model: " << name << endl;
outC << endl;
// Header
outC << "#include " << "\"" << name << ".h" << "\"" << endl;
outC << endl;
// Vertices
outC << "const int " << name << "Vertices = " << model.vertices << ";" << endl;
outC << endl;
// Close C file
outC.close();
}
Then, add the following line inside main()
:
// Write C file
writeCvertices(filepathC, nameOBJ, model);
This should all be familiar. It’s a very similar function to writeH()
, except here you're writing an #include
statement and Vertices
to your C file.
Build and run! Now locate cube.c in Finder and open it with Xcode—it should look like this:
// This is a .c file for the model: cube
#include "cube.h"
const int cubeVertices = 36;
Perfect! Now you may have noticed that you named the previous function writeCvertices()
instead of just writeC()
. This is because you'll be writing each attribute array separately! Many models are humongous, for example the classic Stanford Bunny with over 200,000 vertices. Sometimes a model doesn’t need a texture, or its normals may be computed by the importing program, so reducing their file size is a smart move.
Let’s start by writing out the model’s positions to your C file. Add the following function to main.cpp, just above main()
:
// 1
void writeCpositions(string fp, string name, Model model, int faces[][9], float positions[][3])
{
// 2
// Append C file
ofstream outC;
outC.open(fp, ios::app);
// Positions
outC << "const float " << name << "Positions[" << model.vertices*3 << "] = " << endl;
outC << "{" << endl;
for(int i=0; i<model.faces; i++)
{
// 3
int vA = faces[i][0] - 1;
int vB = faces[i][3] - 1;
int vC = faces[i][6] - 1;
// 4
outC << positions[vA][0] << ", " << positions[vA][1] << ", " << positions[vA][2] << ", " << endl;
outC << positions[vB][0] << ", " << positions[vB][1] << ", " << positions[vB][2] << ", " << endl;
outC << positions[vC][0] << ", " << positions[vC][1] << ", " << positions[vC][2] << ", " << endl;
}
outC << "};" << endl;
outC << endl;
// Close C file
outC.close();
}
Let’s break it down...
- At a first glance, it might seem odd to pass
faces[][9]
into the function, but it is absolutely necessary. Recall that all 36 cube vertices must be written out, but there are only eight distinct positions for this particular model. The values infaces[][9]
are actually an index to these positions, which are shared amongst many vertices. With 12 faces in total and three positions per face, all 36 vertex positions are accounted for. -
ofstream
opens the C file for writing (output), but this time in the append mode to avoid creating a new file and overwriting existing data. - Recall that faces in an OBJ file are stored in the order v/vt/vnA v/vt/vnB v/vt/vnC. Thus, indices
0
,3
and6
offaces[][9]
correspond tovA
,vB
andvC
. You must then subtract1
from the resulting index, because OBJ indices start from 1, not 0. - Finally, write the x-, y-, and z-position coordinates to your C file, for each point of a face.
Add the following statement inside main()
:
writeCpositions(filepathC, nameOBJ, model, faces, positions);
Build and run! Open cube.c in Xcode and you should see the following lines appended to your file:
const float cubePositions[108] =
{
1, -1, -1,
1, -1, 1,
-1, -1, 1,
1, -1, -1,
-1, -1, 1,
-1, -1, -1,
1, 1, -0.999999,
-1, 1, -1,
-1, 1, 1,
1, 1, -0.999999,
-1, 1, 1,
0.999999, 1, 1,
1, -1, -1,
1, 1, -0.999999,
0.999999, 1, 1,
1, -1, -1,
0.999999, 1, 1,
1, -1, 1,
1, -1, 1,
0.999999, 1, 1,
-1, 1, 1,
1, -1, 1,
-1, 1, 1,
-1, -1, 1,
-1, -1, 1,
-1, 1, 1,
-1, 1, -1,
-1, -1, 1,
-1, 1, -1,
-1, -1, -1,
1, 1, -0.999999,
1, -1, -1,
-1, -1, -1,
1, 1, -0.999999,
-1, -1, -1,
-1, 1, -1,
};
You’re doing a great job and you’re almost done. I hope that knowledge puts you in the mood for a challenge, because you should now be able to implement similar functions for texels (writeCtexels()
) and normals (writeCnormals()
). I know you can do it!
Hint: Use the function writeCpositions()
as a starting template and add the new functions just above main()
.
[spoiler title="Writing Attributes"]
void writeCtexels(string fp, string name, Model model, int faces[][9], float texels[][2])
{
// Append C file
ofstream outC;
outC.open(fp, ios::app);
// Texels
outC << "const float " << name << "Texels[" << model.vertices*2 << "] = " << endl;
outC << "{" << endl;
for(int i=0; i<model.faces; i++)
{
int vtA = faces[i][1] - 1;
int vtB = faces[i][4] - 1;
int vtC = faces[i][7] - 1;
outC << texels[vtA][0] << ", " << texels[vtA][1] << ", " << endl;
outC << texels[vtB][0] << ", " << texels[vtB][1] << ", " << endl;
outC << texels[vtC][0] << ", " << texels[vtC][1] << ", " << endl;
}
outC << "};" << endl;
outC << endl;
// Close C file
outC.close();
}
void writeCnormals(string fp, string name, Model model, int faces[][9], float normals[][3])
{
// Append C file
ofstream outC;
outC.open(fp, ios::app);
// Normals
outC << "const float " << name << "Normals[" << model.vertices*3 << "] = " << endl;
outC << "{" << endl;
for(int i=0; i<model.faces; i++)
{
int vnA = faces[i][2] - 1;
int vnB = faces[i][5] - 1;
int vnC = faces[i][8] - 1;
outC << normals[vnA][0] << ", " << normals[vnA][1] << ", " << normals[vnA][2] << ", " << endl;
outC << normals[vnB][0] << ", " << normals[vnB][1] << ", " << normals[vnB][2] << ", " << endl;
outC << normals[vnC][0] << ", " << normals[vnC][1] << ", " << normals[vnC][2] << ", " << endl;
}
outC << "};" << endl;
outC << endl;
// Close C file
outC.close();
}
[/spoiler]
Finally, add the following lines to main()
:
writeCtexels(filepathC, nameOBJ, model, faces, texels);
writeCnormals(filepathC, nameOBJ, model, faces, normals);
Build and run! Open cube.c in Xcode. Its contents should look like this:
Congratulations, your blender2opengles tool is complete and your Blender model is now compatible with OpenGL ES! Copy your files cube.h and cube.c into your /Resources/ folder. This was no easy feat, so you’ve earned yourself a pat on the back and a quick break before moving on. :]