Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android OpenGL ES 2.0: Cube model is not only distorted (perspective is wrong?), but also the faces are loaded incorrectly (vertices not correct?)

I have come across some problems I'm not able to explain very well without you guys trying it out.

I'm unable to get a cube to load correctly. I was able to make it rotate nicely on all axes, though. (plural of "axis" is "axes"?)

I haven't ventured on with lighting and textures, so I'm sorry if you can't seem to make out the model yet.

This is what it looks like right now (snapshot of a freely-spinning model):

Snapshot of the cube. No depth shown yet.

This is the expected outcome:

Blender model. Expectations are too high, sir.

This is the code for my GLSurfaceView.Renderer:

package dd.ww;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.Matrix;

public class Render implements Renderer {

    private Context context;
    private Cube cube;

    private float[] modelViewProjectionMatrix = new float[16];
    private float[] projectionMatrix = new float[16];
    private float[] viewMatrix = new float[16];
    private float[] rotationMatrix = new float[16];
    private float angle = 0f;

    public Render(Context context) {
        this.context = context;
    }

    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        GLES20.glClearColor(1f, 1f, 1f, 1f);
        cube = new Cube(context);
    }

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
        float ratio = (float) width / (float) height;
        Matrix.frustumM(projectionMatrix, 0, -3f, 3f, -3f, 3f, 1f, 10f);
    }

    @Override
    public void onDrawFrame(GL10 unused) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        //Camera position
        Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, -4f, 0f, 0f, 0f, 0f, 1f, 0f);
        // projection x view = modelView
        Matrix.multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
        //Creating rotation matrix
        Matrix.setRotateM(rotationMatrix, 0, angle, 0f, 0f, -1f);
        //rotation x camera = modelView
        Matrix.multiplyMM(modelViewProjectionMatrix, 0, rotationMatrix, 0, modelViewProjectionMatrix, 0);
        Matrix.setRotateM(rotationMatrix, 0, angle, 0f, -1f, 0f);
        Matrix.multiplyMM(modelViewProjectionMatrix, 0, rotationMatrix, 0, modelViewProjectionMatrix, 0);
        Matrix.setRotateM(rotationMatrix, 0, angle, -1f, 0f, 0f);
        Matrix.multiplyMM(modelViewProjectionMatrix, 0, rotationMatrix, 0, modelViewProjectionMatrix, 0);

        cube.draw(modelViewProjectionMatrix);

        angle += 0.7f;
        if (angle > 360f)
            angle = 0f;
    }

}

This is the code for the Cube class, along with its OBJ loader. The OBJ Loader is used for loading the OBJ model that was exported from Blender (which is the expected outcome of the Cube, shown in Blender.):

package dd.ww;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import android.content.Context;
import android.content.res.AssetManager;
import android.opengl.GLES20;
import android.util.Log;

public class Cube {
    private Context context;
    private FloatBuffer vertexBuffer;
    private ShortBuffer indexBuffer;

    private int shaderProgram;

    //TODO: Go to Google Code, find OpenGL ES 2.0 Programming Guide source code, Android,
    //check in the ESShapes.java, and study the FloatBuffers...

    public Cube(Context c) {
        context = c;
        loadCube("cube/cube.obj");
    }

    private void loadCube(String filename) {

        ArrayList<Float> tempVertices = new ArrayList<Float>();
        //ArrayList<Float> tempNormals = new ArrayList<Float>();
        ArrayList<Short> vertexIndices = new ArrayList<Short>();
        //ArrayList<Short> normalIndices = new ArrayList<Short>();

        try {
            AssetManager manager = context.getAssets();
            BufferedReader reader = new BufferedReader(new InputStreamReader(manager.open(filename)));
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("v")) {
                    tempVertices.add(Float.valueOf(line.split(" ")[1])); //vx
                    tempVertices.add(Float.valueOf(line.split(" ")[2])); //vy
                    tempVertices.add(Float.valueOf(line.split(" ")[3])); //vz
                }
                //              else if (line.startsWith("vn")) {
                //                  tempNormals.add(Float.valueOf(line.split(" ")[1])); //nx
                //                  tempNormals.add(Float.valueOf(line.split(" ")[2])); //ny
                //                  tempNormals.add(Float.valueOf(line.split(" ")[3])); //nz
                //              }
                else if (line.startsWith("f")) {

                    /*
                    vertexIndices.add(Short.valueOf(tokens[1].split("/")[0])); //first point of a face
                    vertexIndices.add(Short.valueOf(tokens[2].split("/")[0])); //second point
                    vertexIndices.add(Short.valueOf(tokens[3].split("/")[0])); //third point
                    normalIndices.add(Short.valueOf(tokens[1].split("/")[2])); //first normal
                    normalIndices.add(Short.valueOf(tokens[2].split("/")[2])); //second normal
                    normalIndices.add(Short.valueOf(tokens[3].split("/")[2])); //third
                     */
                    //                  for (int i = 1; i <= 3; i++) {
                    //                      //String[] s = tokens[i].split("/");
                    //                      vertexIndices.add(Short.valueOf());
                    //                      //normalIndices.add(Short.valueOf(s[2]));
                    //                  }

                    vertexIndices.add(Short.valueOf(line.split(" ")[1]));
                    vertexIndices.add(Short.valueOf(line.split(" ")[2]));
                    vertexIndices.add(Short.valueOf(line.split(" ")[3]));
                }
            }

            float[] vertices = new float[tempVertices.size()];
            for (int i = 0; i < tempVertices.size(); i++) {
                Float f = tempVertices.get(i);
                vertices[i] = (f != null ? f : Float.NaN);
            }

            short[] indices = new short[vertexIndices.size()];
            for (int i = 0; i < vertexIndices.size(); i++) {
                Short s = vertexIndices.get(i);
                indices[i] = (s != null ? s : 1);
            }

            vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
            vertexBuffer.put(vertices).position(0);

            indexBuffer = ByteBuffer.allocateDirect(indices.length * 2).order(ByteOrder.nativeOrder()).asShortBuffer();
            indexBuffer.put(indices).position(0);


            int vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
            GLES20.glShaderSource(vertexShader, vertexCode);
            GLES20.glCompileShader(vertexShader);
            int fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
            GLES20.glShaderSource(fragmentShader, fragmentCode);
            GLES20.glCompileShader(fragmentShader);

            shaderProgram = GLES20.glCreateProgram();
            GLES20.glAttachShader(shaderProgram, vertexShader);
            GLES20.glAttachShader(shaderProgram, fragmentShader);
            GLES20.glLinkProgram(shaderProgram);

            int[] linked = new int[1];
            GLES20.glGetProgramiv(shaderProgram, GLES20.GL_LINK_STATUS, linked, 0);
            if (linked[0] == 0){
                Log.d("DEBUG", "Shader code error.");
                Log.d("DEBUG", GLES20.glGetProgramInfoLog(shaderProgram));
                GLES20.glDeleteProgram(shaderProgram);
                return;
            }

            GLES20.glDeleteShader(vertexShader);
            GLES20.glDeleteShader(fragmentShader);




        }
        catch (Exception e) {
            Log.d("DEBUG", "Error.", e);
        }
    }

    private String vertexCode = "" +
            "attribute vec4 a_position;  \n" +
            "uniform mat4 mvpMatrix;     \n" +
            "void main() {               \n" +
            "   gl_Position = a_position * mvpMatrix;\n" +
            "}                           \n";

    private String fragmentCode = "" +
            "precision mediump float;                   \n" +
            "void main() {                              \n" +
            "   gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n" +
            "}                                          \n";

    private int attribute_Position;
    private int uniform_mvpMatrix;

    public void draw(float[] mvpMatrix){
        GLES20.glUseProgram(shaderProgram);
        attribute_Position = GLES20.glGetAttribLocation(shaderProgram, "a_position");
        GLES20.glVertexAttribPointer(attribute_Position, 3, GLES20.GL_FLOAT, false, 3 * 4, vertexBuffer);
        GLES20.glEnableVertexAttribArray(attribute_Position);
        uniform_mvpMatrix = GLES20.glGetUniformLocation(shaderProgram, "mvpMatrix");
        GLES20.glUniformMatrix4fv(uniform_mvpMatrix, 1, false, mvpMatrix, 0);
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, indexBuffer.capacity(), GLES20.GL_UNSIGNED_SHORT, indexBuffer);
        GLES20.glDisableVertexAttribArray(attribute_Position);
    }
}

And finally, here's the APK attachment (Uploaded to Mediafire, should not get removed. It's non-licensed freeware). The attached APK file is signed, exported straight from my project, and can only be run on Gingerbread or above. (This is what OpenGL ES 2.0 is for...): Mediafire download link to the APK file.

If anyone is willing to help me realize what I'm doing wrong, I'll be glad for the rest of my life. This question here is the closest that I find when searching on SO that has a 40% chance my problem is related to. Unfortunately, he still has his model distorted. All the rest of the questions I found seems to be about textures not rendering correctly, translating the model around, etc. But I will try my best to find questions with similar problems as mine.

like image 680
tom_mai78101 Avatar asked Oct 21 '22 13:10

tom_mai78101


1 Answers

Holy Cow...

I finally got it to work.

Snapshot 1

Snapshot 2

The problem is how OpenGL ES 2.0 matrices work.

Quote from SO user, Tim:

I believe it should be mvpMatrix * mRotationMatrix, but you're not supposed to use the same matrix as the input and output to that function, you need to use a temporary matrix. Android.opengl.Matrix " The same float array may be passed for result, lhs, and/or rhs. However, the result element values are undefined if the result elements overlap either the lhs or rhs elements." If that doesn't help then post your entire code.

The bolded text means that if you do this:

//Creating rotation matrix
Matrix.setRotateM(rotationMatrix, 0, angle, 0f, 0f, -1f);

//rotation x camera = modelView    
Matrix.multiplyMM(modelViewProjectionMatrix, 0, modelViewProjectionMatrix, 0, rotationMatrix, 0);

Matrix.setRotateM(rotationMatrix, 0, angle, 0f, -1f, 0f);
Matrix.multiplyMM(modelViewProjectionMatrix, 0, modelViewProjectionMatrix, 0, rotationMatrix, 0);

Matrix.setRotateM(rotationMatrix, 0, angle, -1f, 0f, 0f);
Matrix.multiplyMM(modelViewProjectionMatrix, 0, modelViewProjectionMatrix, 0, rotationMatrix, 0);

cube.draw(modelViewProjectionMatrix);

Which seems like normal, the cube will look skewed. Reason is, lhs and rhs shouldn't be the same as your resulting matrix.

But, if you do this:

//Creating rotation matrix
Matrix.setRotateM(rotationMatrix, 0, angle, 0f, 0f, -1f);

//rotation x camera = modelView
float[] duplicateMatrix = Arrays.copyOf(modelViewProjectionMatrix, 16);

Matrix.multiplyMM(modelViewProjectionMatrix, 0, duplicateMatrix, 0, rotationMatrix, 0);

Matrix.setRotateM(rotationMatrix, 0, angle, 0f, -1f, 0f);
duplicateMatrix = Arrays.copyOf(modelViewProjectionMatrix, 16);
Matrix.multiplyMM(modelViewProjectionMatrix, 0, duplicateMatrix, 0, rotationMatrix, 0);

Matrix.setRotateM(rotationMatrix, 0, angle, -1f, 0f, 0f);
duplicateMatrix = Arrays.copyOf(modelViewProjectionMatrix, 16);
Matrix.multiplyMM(modelViewProjectionMatrix, 0, duplicateMatrix, 0, rotationMatrix, 0);

cube.draw(modelViewProjectionMatrix);

It will display correctly.

Where I found the helpful hint from.

And this is where I realized why 3 people voted to close this question off. They must've known the answer to this question in the first place, and wanted me to find the solution by myself. Hats off to them...

I swear to God, this is a hard problem I have finally conquered. There goes 2 years of wasted life down the drain...

like image 141
tom_mai78101 Avatar answered Oct 29 '22 18:10

tom_mai78101