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: }