Friday, July 15, 2011

Android - Rendering 3D Blender Models (Part 1)



I decided I to create a simple OpenGL ES example for Android. I found many examples with hard coded values for a model, but I wanted to create the model in a 3D modeling tool and import it into my app. I wanted to do this using a model made in Blender due to my experience with the tool and its FREE! Here are the steps I took to accomplish this.

Exporting a 3D Model from Blender

1) Once your model is ready to export, select only the objects you want.
2) Go to: File -> Export -> Wavefront (.obj)
3) Next you will see a screen with an Export OBJ tab on the left. I found the most success only the following items selected:

- Selection Only
- Normals
- UVs
- Triangulate
- Objects as OBJ Objects
- Keep Vertex Order

4) Export your Wavefront file
5) Place the .obj files into your Android App's assets folder

Creating your OpenGL ES Android App Structure

After some research into OpenGL ES I became most comfortable with the following structure:




I will work from the bottom up while walking through this example.


Parsing a Wavefront File

Your Wavefront file should have the following format (learn more):
 // Vertices  
 v 1.797824 0.168228 5.381136  
 ...  
   
 // Normals  
 vn 0.000000 1.000000 0.000000  
 ...  
   
 // Faces  
 f 21//1 1//2 2//3  
 ...  

I found a great example of parsing a Wavefront file in the Earth Live Wallpaper project. The following is a stripped down version of their parsing method. I've stripped this down because my example does not include the object's textures.

1:  private void LoadObjFile() {  
2:              
3:      try   
4:      {  
5:           AssetManager am = mContext.getAssets();  
6:        String str;  
7:        String[] tmp;  
8:        String[] ftmp;  
9:        float v;  
10:        ArrayList<Float> vlist = new ArrayList<Float>();  
11:        ArrayList<Float> nlist = new ArrayList<Float>();  
12:        ArrayList<Fp> fplist = new ArrayList<Fp>();  
13:          
14:        BufferedReader inb = new BufferedReader(new InputStreamReader(am.open("androidmodel.obj")), 1024);  
15:          
16:        while ((str = inb.readLine()) != null)   
17:        {  
18:             tmp = str.split(" ");  
19:               
20:             //Parse the vertices  
21:             if (tmp[0].equalsIgnoreCase("v"))   
22:             {  
23:                  for (int i = 1; i < 4; i++)   
24:                  {  
25:                       v = Float.parseFloat(tmp[i]);  
26:              vlist.add(v);  
27:            }  
28:             }  
29:             //Parse the vertex normals  
30:          if (tmp[0].equalsIgnoreCase("vn"))   
31:          {  
32:               for (int i = 1; i < 4; i++)   
33:               {  
34:                    v = Float.parseFloat(tmp[i]);  
35:              nlist.add(v);  
36:            }  
37:          }  
38:          //Parse the faces/indices  
39:          if (tmp[0].equalsIgnoreCase("f"))   
40:          {  
41:               for (int i = 1; i < 4; i++)   
42:               {  
43:                    ftmp = tmp[i].split("/");  
44:                      
45:                    long chi = Integer.parseInt(ftmp[0]) - 1;  
46:                    int cht = 0;  
47:                    if(!ftmp[1].equals(""))  
48:                         cht = Integer.parseInt(ftmp[1]) - 1;  
49:              int chn = Integer.parseInt(ftmp[2]) - 1;  
50:    
51:              fplist.add(new Fp(chi, cht, chn));  
52:            }  
53:               NBFACES++;  
54:          }  
55:        }  
56:          
57:        ByteBuffer vbb = ByteBuffer.allocateDirect(fplist.size() * 4 * 3);  
58:        vbb.order(ByteOrder.nativeOrder());  
59:        mVertexBuffer = vbb.asFloatBuffer();  
60:    
61:        ByteBuffer nbb = ByteBuffer.allocateDirect(fplist.size() * 4 * 3);  
62:        nbb.order(ByteOrder.nativeOrder());  
63:        mNormBuffer = nbb.asFloatBuffer();  
64:    
65:        for (int j = 0; j < fplist.size(); j++)   
66:        {  
67:             mVertexBuffer.put(vlist.get((int) (fplist.get(j).Vi * 3)));  
68:          mVertexBuffer.put(vlist.get((int) (fplist.get(j).Vi * 3 + 1)));  
69:          mVertexBuffer.put(vlist.get((int) (fplist.get(j).Vi * 3 + 2)));  
70:    
71:          mNormBuffer.put(nlist.get(fplist.get(j).Ni * 3));  
72:          mNormBuffer.put(nlist.get((fplist.get(j).Ni * 3) + 1));  
73:          mNormBuffer.put(nlist.get((fplist.get(j).Ni * 3) + 2));  
74:        }  
75:          
76:        mIndexBuffer = CharBuffer.allocate(fplist.size());  
77:        for (int j = 0; j < fplist.size(); j++)   
78:        {  
79:             mIndexBuffer.put((char) j);  
80:        }  
81:          
82:        mVertexBuffer.position(0);  
83:        mNormBuffer.position(0);  
84:        mIndexBuffer.position(0);  
85:    
86:      } catch (FileNotFoundException e)   
87:      {  
88:           e.printStackTrace();  
89:      } catch (IOException e)   
90:      {  
91:           e.printStackTrace();  
92:      }  
93:       }