android-studiokotlin3dopengl-esopenglrenderer

Simple 3D Shape faces not rendering as expected - OpenGL ES in Android Studio


I am trying to make a rotating octahedron display correctly, I have successfully achieved other shapes such as a cube and tetrahedron, but I am experiencing some difficulty with this one.

Here is the simple obj file I am using:

v 0 -1 0
v 1 0 0
v 0 0 1
v -1 0 0
v 0 1 0
v 0 0 -1
#
f 1 2 3
f 4 1 3
f 5 4 3
f 2 5 3
f 2 1 6
f 1 4 6
f 4 5 6
f 5 2 6

My code is as follows:

class Shape(context: Context) {

    private var mProgram: Int = 0
    // Use to access and set the view transformation
    private var mMVPMatrixHandle: Int = 0

    //For Projection and Camera Transformations
    private var vertexShaderCode = (
            // This matrix member variable provides a hook to manipulate
            // the coordinates of the objects that use this vertex shader
            "uniform mat4 uMVPMatrix;" +
                    "attribute vec4 vPosition;" +
                    //"attribute vec4 vColor;" +
                    //"varying vec4 vColorVarying;" +
                    "void main() {" +
                    // the matrix must be included as a modifier of gl_Position
                    // Note that the uMVPMatrix factor *must be first* in order
                    // for the matrix multiplication product to be correct.
                    " gl_Position = uMVPMatrix * vPosition;" +
                    //"vColorVarying = vColor;"+
                    "}")


    private var fragmentShaderCode = (
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    //"varying vec4 vColorVarying;"+
                    "void main() {" +
                    //" gl_FragColor = vColorVarying;" +
                    " gl_FragColor = vColor;" +
                    "}")

    internal var shapeColor = arrayOf<FloatArray>(
        //front face (grey)
        floatArrayOf(0f, 0f, 0f, 1f), //black
        floatArrayOf(0f, 0f, 1f, 1f),
        floatArrayOf(0f, 1f, 0f, 1f),
        floatArrayOf(1f, 0f, 0f, 1f), // red
        floatArrayOf(1f, 1f, 0f, 1f),
        floatArrayOf(1f, 0f, 1f, 1f),
        floatArrayOf(1f, 0f, 1f, 1f),
        floatArrayOf(0f, 1f, 1f, 1f)
    )

    private var mPositionHandle: Int = 0
    private var mColorHandle: Int = 0

//        var objLoader = ObjLoader(context, "tetrahedron.txt")
//        var objLoader = ObjLoader(context, "cube.txt")
    var objLoader = ObjLoader(context, "octahedron.txt")

    var shapeCoords: FloatArray
    var numFaces: Int = 0
    var vertexBuffer: FloatBuffer
    var drawOrder: Array<ShortArray>
    lateinit var drawListBuffer: ShortBuffer

    init {
        //assign coordinates and order in which to draw them (obtained from obj loader class)
        shapeCoords = objLoader.vertices.toFloatArray()
        drawOrder = objLoader.faces.toTypedArray()
        numFaces = objLoader.numFaces

        // initialize vertex byte buffer for shape coordinates
        val bb = ByteBuffer.allocateDirect(
            // (# of coordinate varues * 4 bytes per float)
            shapeCoords.size * 4
        )
        bb.order(ByteOrder.nativeOrder())
        vertexBuffer = bb.asFloatBuffer()
        vertexBuffer.put(shapeCoords)
        vertexBuffer.position(0)

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram()

        val vertexShader = loadShader(
            GLES20.GL_VERTEX_SHADER,
            vertexShaderCode
        )
        val fragmentShader = loadShader(
            GLES20.GL_FRAGMENT_SHADER,
            fragmentShaderCode
        )
        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader)
        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader)
        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram)
    }

    var vertexStride = COORDS_PER_VERTEX * 4 // 4 bytes per vertex

    fun draw(mvpMatrix: FloatArray) { // pass in the calculated transformation matrix
        for (face in 0 until numFaces) {
            // Add program to OpenGL ES environment
            GLES20.glUseProgram(mProgram)

            // get handle to vertex shader's vPosition member
            mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition")
            // get handle to fragment shader's vColor member
            mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor")
            // Enable a handle to the cube vertices
            GLES20.glEnableVertexAttribArray(mPositionHandle)
            // Prepare the cube coordinate data
            GLES20.glVertexAttribPointer(
                mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer
            )

            GLES20.glUniform4fv(mColorHandle, 1, shapeColor[face], 0)
            // get handle to shape's transformation matrix
            mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix")
            // Pass the projection and view transformation to the shader
            GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0)

            // initialize byte buffer for the draw list
            var dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                drawOrder[face].size * 2
            )
            dlb.order(ByteOrder.nativeOrder())
            drawListBuffer = dlb.asShortBuffer()
            drawListBuffer.put(drawOrder[face])
            drawListBuffer.position(0)

            GLES20.glDrawElements(
                GLES20.GL_TRIANGLES,
                dlb.capacity(),
                GLES20.GL_UNSIGNED_SHORT,
                drawListBuffer //position indices
            )
        }

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mMVPMatrixHandle)
    }

    companion object {
        // number of coordinates per vertex in this array
        internal var COORDS_PER_VERTEX = 3
    }
}

class MyGLRenderer1(val context: Context) : GLSurfaceView.Renderer {
    private lateinit var mShape: Shape

    @Volatile
    var mDeltaX = 0f

    @Volatile
    var mDeltaY = 0f

    @Volatile
    var mTotalDeltaX = 0f

    @Volatile
    var mTotalDeltaY = 0f

    private val mMVPMatrix  = FloatArray(16)
    private val mProjectionMatrix  = FloatArray(16)
    private val mViewMatrix  = FloatArray(16)
    private val mRotationMatrix  = FloatArray(16)

    private val mAccumulatedRotation   = FloatArray(16)
    private val mCurrentRotation   = FloatArray(16)
    private val mTemporaryMatrix   = FloatArray(16)

    override fun onDrawFrame(gl: GL10?) {
        // Redraw background color

        // Redraw background color
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)

        val scratch = FloatArray(16)

        // Create a rotation transformation for the square
        Matrix.setIdentityM(mRotationMatrix, 0)

        Matrix.setIdentityM(mCurrentRotation, 0)



        Matrix.rotateM(mCurrentRotation, 0, mDeltaX, 0.0f, 1.0f, 0.0f)
//        Matrix.rotateM(mCurrentRotation, 0, mDeltaY, 1.0f, 0.0f, 0.0f)


        // Multiply the current rotation by the accumulated rotation, and then set the accumulated
        // rotation to the result.
        Matrix.multiplyMM(
            mTemporaryMatrix,
            0,
            mCurrentRotation,
            0,
            mAccumulatedRotation,
            0
        )

        System.arraycopy(mTemporaryMatrix, 0, mAccumulatedRotation, 0, 16)

        // Rotate the cube taking the overall rotation into account.
        Matrix.multiplyMM(
            mTemporaryMatrix,
            0,
            mRotationMatrix,
            0,
            mAccumulatedRotation,
            0
        )
        System.arraycopy(mTemporaryMatrix, 0, mRotationMatrix, 0, 16)

        // Set the camera position (View matrix)
        Matrix.setLookAtM(mViewMatrix, 0, 2f, 2f, -5f, 0f, 0f, 0f, 0f, 1.0f, 0.0f)

        //Calculate the projection and view transformation
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0)

        // Combine the rotation matrix with the projection and camera view
        // Note that the mMVPMatrix factor *must be first* in order
        // for the matrix multiplication product to be correct.
        Matrix.multiplyMM(scratch, 0, mMVPMatrix, 0, mRotationMatrix, 0)

        gl?.glDisable(GL10.GL_CULL_FACE)
        // Draw shape
        mShape.draw(scratch)

    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        GLES20.glViewport(0, 0, width, height);

        val ratio: Float = width.toFloat() / height.toFloat()

        // this projection matrix is applied to object coordinates
        // in the onDrawFrame() method
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1.0f, 1.0f, 3.0f, 7.0f)

    }

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        GLES20.glEnable(GLES20.GL_DEPTH_TEST)

        // initialize a square
        mShape = Shape(context)

        // Initialize the accumulated rotation matrix
        Matrix.setIdentityM(mAccumulatedRotation, 0)
    }
}


fun loadShader(type: Int, shaderCode: String): Int {
    return GLES20.glCreateShader(type).also { shader ->

        GLES20.glShaderSource(shader, shaderCode)
        GLES20.glCompileShader(shader)
    }
}

class ObjLoader(context: Context, file: String) {
    var numFaces: Int = 0
    var vertices = Vector<Float>()
    var normals = Vector<Float>()
    var textures = Vector<Float>()
    var faces = mutableListOf<ShortArray>()

    init {
        val reader: BufferedReader
        val isr = InputStreamReader(context.assets.open(file))
        reader = BufferedReader(isr)
        var line = reader.readLine()

        // read file until EOF
        while (line != null) {

            val parts = line.split((" ").toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
            when (parts[0]) {
                "v" -> {
                    var part1 = parts[1].toFloat()
                    var part2 = parts[2].toFloat()
                    var part3 = parts[3].toFloat()

                    // vertices
                    vertices.add(part1)
                    vertices.add(part2)
                    vertices.add(part3)

                }
                "vt" -> {
                    // textures
                    textures.add(parts[1].toFloat())
                    textures.add(parts[2].toFloat())
                }
                "vn" -> {
                    // normals
                    normals.add(parts[1].toFloat())
                    normals.add(parts[2].toFloat())
                    normals.add(parts[3].toFloat())
                }
                "f" -> {
                    // faces: vertex/texture/normal
                    faces.add(shortArrayOf(parts[1].toShort(), parts[2].toShort(), parts[3].toShort()))
                    println("dbg: points are "+ parts[1]+" "+parts[2]+" "+parts[3])
                }
            }
            line = reader.readLine()
        }

        numFaces = faces.size
}}

The shape produced can be seen in the following screenshots, it is also visible on the black surface that there is possibly some sort of z fighting taking place? The black triangle flickers red and yellow: Shape screenshots

Sometimes the following shapes are produced, flickering in and out of existence, in different colours: Strange flickering shape screenshots

Any help is much appreciated, thanks in advance.

Edit: I have managed to make the vertices plot correctly thanks to the below answer however there is still this flickering going on, I really appreciate the help.

Flickering


Solution

  • Array indices start at 0, but Wavefront (.obj) indices start at 1:

    faces.add(shortArrayOf(parts[1].toShort(), parts[2].toShort(), parts[3].toShort()))

    faces.add(shortArrayOf(
        parts[1].toShort()-1, parts[2].toShort()-1, parts[3].toShort()-1))