androidopengl-esopengl-es-2.0

How to load and display .obj file in Android with OpenGL-ES 2


I am trying to load an .obj file into my Android application and display it using OpenGL 2.

You can find the file here: EDIT: I removed the file, you can use any .obj file that contains the values mentiones below for testing.

There are a lot of similar questions on stackoverflow but I did not find a simple solution that does not require some large library.

The file only contains the following value types:

I tried libgdx, which worked ok, but it is a bit overkill for what I need.

I tried the oObjLoader https://github.com/seanrowens/oObjLoader without the LWJGL. The parsing seems to work, but how can I display the values in a simple scene?

The next step is to attach an image as a texture to the object. But for now I would be happy to display the file as it is.

I am open to different solutions like pre-converting the file, because it will only be this one ever within the application.

Status update Basic loading and displaying works now, as shown in my own answer.


Solution

  • I ended up writing a new parser, it can be used like this to build FloatBuffers to use in your Renderer:

    ObjLoader objLoader = new ObjLoader(context, "Mug.obj");
    
    numFaces = objLoader.numFaces;
    
    // Initialize the buffers.
    positions = ByteBuffer.allocateDirect(objLoader.positions.length * mBytesPerFloat)
            .order(ByteOrder.nativeOrder()).asFloatBuffer();
    positions.put(objLoader.positions).position(0);
    
    normals = ByteBuffer.allocateDirect(objLoader.normals.length * mBytesPerFloat)
            .order(ByteOrder.nativeOrder()).asFloatBuffer();
    normals.put(objLoader.normals).position(0);
    
    textureCoordinates = ByteBuffer.allocateDirect(objLoader.textureCoordinates.length * mBytesPerFloat)
            .order(ByteOrder.nativeOrder()).asFloatBuffer();
    textureCoordinates.put(objLoader.textureCoordinates).position(0);
    

    and here's the parser:

    public final class ObjLoader {
    
        public final int numFaces;
    
        public final float[] normals;
        public final float[] textureCoordinates;
        public final float[] positions;
    
        public ObjLoader(Context context, String file) {
    
            Vector<Float> vertices = new Vector<>();
            Vector<Float> normals = new Vector<>();
            Vector<Float> textures = new Vector<>();
            Vector<String> faces = new Vector<>();
    
            BufferedReader reader = null;
            try {
                InputStreamReader in = new InputStreamReader(context.getAssets().open(file));
                reader = new BufferedReader(in);
    
                // read file until EOF
                String line;
                while ((line = reader.readLine()) != null) {
                    String[] parts = line.split(" ");
                    switch (parts[0]) {
                        case "v":
                            // vertices
                            vertices.add(Float.valueOf(parts[1]));
                            vertices.add(Float.valueOf(parts[2]));
                            vertices.add(Float.valueOf(parts[3]));
                            break;
                        case "vt":
                            // textures
                            textures.add(Float.valueOf(parts[1]));
                            textures.add(Float.valueOf(parts[2]));
                            break;
                        case "vn":
                            // normals
                            normals.add(Float.valueOf(parts[1]));
                            normals.add(Float.valueOf(parts[2]));
                            normals.add(Float.valueOf(parts[3]));
                            break;
                        case "f":
                            // faces: vertex/texture/normal
                            faces.add(parts[1]);
                            faces.add(parts[2]);
                            faces.add(parts[3]);
                            break;
                    }
                }
            } catch (IOException e) {
                // cannot load or read file
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        //log the exception
                    }
                }
            }
    
            numFaces = faces.size();
            this.normals = new float[numFaces * 3];
            textureCoordinates = new float[numFaces * 2];
            positions = new float[numFaces * 3];
            int positionIndex = 0;
            int normalIndex = 0;
            int textureIndex = 0;
            for (String face : faces) {
                String[] parts = face.split("/");
    
                int index = 3 * (Short.valueOf(parts[0]) - 1);
                positions[positionIndex++] = vertices.get(index++);
                positions[positionIndex++] = vertices.get(index++);
                positions[positionIndex++] = vertices.get(index);
    
                index = 2 * (Short.valueOf(parts[1]) - 1);
                textureCoordinates[normalIndex++] = textures.get(index++);
                // NOTE: Bitmap gets y-inverted
                textureCoordinates[normalIndex++] = 1 - textures.get(index);
    
                index = 3 * (Short.valueOf(parts[2]) - 1);
                this.normals[textureIndex++] = normals.get(index++);
                this.normals[textureIndex++] = normals.get(index++);
                this.normals[textureIndex++] = normals.get(index);
            }
        }
    }