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.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 6 of this article. Click here to view the first page.

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...

  1. 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 in faces[][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.
  2. 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.
  3. 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 and 6 of faces[][9] correspond to vA, vB and vC. You must then subtract 1 from the resulting index, because OBJ indices start from 1, not 0.
  4. 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. :]

Ricardo Rendon Cepeda

Contributors

Ricardo Rendon Cepeda

Author

Over 300 content creators. Join our team.