Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

  • g
  • v
  • vt
  • vn
  • f

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.

Thanks!

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

like image 468
Björn Kechel Avatar asked Dec 07 '16 08:12

Björn Kechel


People also ask

Can Android run OpenGL?

Android includes support for high performance 2D and 3D graphics with the Open Graphics Library (OpenGL®), specifically, the OpenGL ES API. OpenGL is a cross-platform graphics API that specifies a standard software interface for 3D graphics processing hardware.

What is Obj file in Android?

An OBJ file is a standard 3D image format that can be exported and opened by various 3D image editing programs. It contains a three-dimensional object, including 3D coordinates, texture maps, polygonal faces, and other object information.

What is Open GL in Android?

OpenGL, which is short for Open Graphics Library, is a platform-independent API that allows you to create hardware-accelerated 3D graphics. OpenGL ES, short for OpenGL for Embedded Systems, is a subset of the API. OpenGL ES is a very low-level API.


2 Answers

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);
        }
    }
}
like image 77
Björn Kechel Avatar answered Oct 17 '22 01:10

Björn Kechel


Try with this project that found me on Github. https://github.com/WenlinMao/android-3d-model-viewer

This is a demo of OpenGL ES 2.0. It is an android application with a 3D engine that can load Wavefront OBJ, STL, DAE & glTF files. The application is based on andresoviedo's project which can be found here with an additional function of loading and rendering glTF format.

The purpose of this application is to learn and share how to draw using OpenGLES and Android. As this is my first android app, it is highly probable that there are bugs; but I will try to continue improving the app and adding more features.

This project is open source, and contain classes that can solve your problem!

like image 39
Angel Valay Avatar answered Oct 17 '22 01:10

Angel Valay