1

I am working on a Android app with openglES 3.0 and I want to create a obj loader system and display a 3D model with his texture. My code display correctly a 3D meshe without texture. If I try to add a texture, it will display the texture and some part of the texture will be empty triangles.

Example :

my meshe with a texture

my meshe obj file

my texture png file

I can not find what is the problem.

My fragment shader

precision mediump float;
uniform vec4 vColor;
uniform sampler2D uTexture;
varying vec2 oTexCoordinate;

void main() {
    gl_FragColor = texture2D(uTexture, oTexCoordinate);
    //gl_FragColor = vec4(1, 0.5, 0, 1.0);

}

My vertex shader

attribute vec4 position;
uniform mat4 matrix;

attribute vec2 vTexCoordinate;
varying vec2 oTexCoordinate;


void main() {
    oTexCoordinate = vTexCoordinate;
    gl_Position = matrix * position;

}

Thanks in advance.


Update :

Thanks.

I have changed my code to fit your idea, I have activated the face culling and add a buffer for the normal, I do not use it for now. I put my new code below. But the problem is not entirely resolved, see the picture below.

I think I need to pass the normal information to the shaders but I am not sure how to do it properly or if it is the solution.

public class MeshLoader {

    private int program;

    private List<String> facesVertexList;
    private List<String> facesTextureList;
    private List<String> facesNormalList;

    private List<String> verticesList;
    private List<String> textureList;
    private List<String> normalList;

    private FloatBuffer verticesBuffer;
    private FloatBuffer verticesBufferTemp;

    private FloatBuffer facesVertexBuffer;
    private FloatBuffer facesTextureBuffer;
    private FloatBuffer facesNormalBuffer;

    private FloatBuffer textureBuffer;
    private FloatBuffer textureBufferTemp;

    private FloatBuffer normalBuffer;
    private FloatBuffer normalBufferTemp;

    private Context contextMeshLoader;
    final int[] textureHandle = new int[1];


    public MeshLoader(Context context) {
        contextMeshLoader = context;
        textureList = new LinkedList<>();
        verticesList = new LinkedList<>();
        normalList = new LinkedList<>();
        facesVertexList = new LinkedList<>();
        facesTextureList = new LinkedList<>();
        facesNormalList = new LinkedList<>();

        openObjFile(0);

        String vertexShaderCode = "";
        try{
            InputStream vertexShaderStream = context.getResources().openRawResource(R.raw.vertex_shader);
            vertexShaderCode = IOUtils.toString(vertexShaderStream, Charset.defaultCharset());
            vertexShaderStream.close();
        }
        catch (Exception e){
            Log.e("MeshReaderActivity", "Error reading vertex shader", e);
        }

        String fragmentShaderCode = "";
        try{
            InputStream fragmentShaderStream = context.getResources().openRawResource(R.raw.fragment_shader);
            fragmentShaderCode = IOUtils.toString(fragmentShaderStream, Charset.defaultCharset());
            fragmentShaderStream.close();
        }
        catch(Exception e){
            Log.e("MeshReaderActivity", "Error reading fragment shader", e);
        }

        int vertexShader = GLES30.glCreateShader(GLES30.GL_VERTEX_SHADER);
        GLES30.glShaderSource(vertexShader, vertexShaderCode);

        int fragmentShader = GLES30.glCreateShader(GLES30.GL_FRAGMENT_SHADER);
        GLES30.glShaderSource(fragmentShader, fragmentShaderCode);


        GLES30.glCompileShader(vertexShader);
        GLES30.glCompileShader(fragmentShader);


        program = GLES30.glCreateProgram();
        GLES30.glAttachShader(program, vertexShader);
        GLES30.glAttachShader(program, fragmentShader);
        GLES30.glLinkProgram(program);
        GLES30.glUseProgram(program);

    }

    public void openObjFile(int value)
    {
        InputStream is;
        value = 0;
        if(value == 0)
           is  = contextMeshLoader.getResources().openRawResource(R.raw.objface);
        else
            is = contextMeshLoader.getResources().openRawResource(R.raw.objship);

        if(verticesBufferTemp != null)
            verticesBufferTemp.clear();
        if(facesVertexBuffer != null)
            facesVertexBuffer.clear();
        if(textureBuffer != null)
            textureBuffer.clear();
        if(verticesList != null)
            verticesList.clear();
        if(facesVertexList != null)
            facesVertexList.clear();
        if(textureList != null)
            textureList.clear();

        try{
            byte[] buffer = new byte[is.available()];
            is.read(buffer);
            String data = new String(buffer);

            parseData(data);



            ByteBuffer buffer2 = ByteBuffer.allocateDirect(facesVertexList.size() * 3 * 4);
            buffer2.order(ByteOrder.nativeOrder());
            facesVertexBuffer = buffer2.asFloatBuffer();

            ByteBuffer buffer3 = ByteBuffer.allocateDirect(facesTextureList.size() * 3 * 4);
            buffer3.order(ByteOrder.nativeOrder());
            facesTextureBuffer = buffer3.asFloatBuffer();

            ByteBuffer buffer6 = ByteBuffer.allocateDirect(facesTextureList.size() * 3 * 4);
            buffer6.order(ByteOrder.nativeOrder());
            facesNormalBuffer = buffer6.asFloatBuffer();


            for(String face: facesVertexList) {
                String vertexIndices[] = face.split("\\s+");
                float vertex1 = Float.parseFloat(vertexIndices[1]);
                float vertex2 = Float.parseFloat(vertexIndices[2]);
                float vertex3 = Float.parseFloat(vertexIndices[3]);
                facesVertexBuffer.put((vertex1 - 1));
                facesVertexBuffer.put((vertex2 - 1));
                facesVertexBuffer.put((vertex3 - 1));
            }
            facesVertexBuffer.position(0);


            for(String texture: facesTextureList){
                String textureIndice[] = texture.split("\\s+");
                float texture1 = Float.parseFloat(textureIndice[1]);
                float texture2 = Float.parseFloat(textureIndice[2]);
                float texture3 = Float.parseFloat(textureIndice[3]);
                facesTextureBuffer.put((texture1 - 1));
                facesTextureBuffer.put((texture2 - 1));
                facesTextureBuffer.put((texture3 - 1));
            }
            facesTextureBuffer.position(0);

            for(String normal: facesNormalList) {
                String normalIndice[] = normal.split("\\s+");
                float normal1 = Float.parseFloat(normalIndice[1]);
                float normal2 = Float.parseFloat(normalIndice[2]);
                float normal3 = Float.parseFloat(normalIndice[3]);
                facesNormalBuffer.put((normal1 - 1));
                facesNormalBuffer.put((normal2 - 1));
                facesNormalBuffer.put((normal3 - 1));
            }
            facesNormalBuffer.position(0);


            ByteBuffer buffer1 = ByteBuffer.allocateDirect(verticesList.size() * 3 * 4);
            buffer1.order(ByteOrder.nativeOrder());
            verticesBufferTemp = buffer1.asFloatBuffer();

            ByteBuffer buffer5 = ByteBuffer.allocateDirect(textureList.size() * 2 * 4);
            buffer5.order(ByteOrder.nativeOrder());
            textureBufferTemp = buffer5.asFloatBuffer();

            ByteBuffer buffer7 = ByteBuffer.allocateDirect(textureList.size() * 3 * 4);
            buffer7.order(ByteOrder.nativeOrder());
            normalBufferTemp = buffer7.asFloatBuffer();

            for(String vertex: verticesList) {
                String coords[] = vertex.split("\\s+");
                float x = Float.parseFloat(coords[1]);
                float y = Float.parseFloat(coords[2]);
                float z = Float.parseFloat(coords[3]);
                verticesBufferTemp.put(x);
                verticesBufferTemp.put(y);
                verticesBufferTemp.put(z);
            }
            verticesBufferTemp.position(0);

            for (String texture:textureList)
            {
                String textureIndices[] = texture.split("\\s+");
                float texture1 = Float.parseFloat(textureIndices[1]);
                float texture2 = Float.parseFloat(textureIndices[2]);
                textureBufferTemp.put(texture1);
                textureBufferTemp.put(texture2);
            }
            textureBufferTemp.position(0);

            for (String normal:normalList)
            {
                String normalIndices[] = normal.split("\\s+");
                float normal1 = Float.parseFloat(normalIndices[1]);
                float normal2 = Float.parseFloat(normalIndices[2]);
                normalBufferTemp.put(normal1);
                normalBufferTemp.put(normal2);
            }
            normalBufferTemp.position(0);


            System.out.println("size remaining " + facesVertexBuffer.remaining());

            ByteBuffer bufferV = ByteBuffer.allocateDirect(facesVertexBuffer.remaining() * 3 * 4);
            bufferV.order(ByteOrder.nativeOrder());
            verticesBuffer = bufferV.asFloatBuffer();

            ByteBuffer bufferT = ByteBuffer.allocateDirect(facesVertexBuffer.remaining() * 2 * 4);
            bufferT.order(ByteOrder.nativeOrder());
            textureBuffer = bufferT.asFloatBuffer();

            ByteBuffer bufferN = ByteBuffer.allocateDirect(facesVertexBuffer.remaining() * 3 * 4);
            bufferN.order(ByteOrder.nativeOrder());
            normalBuffer = bufferN.asFloatBuffer();

            int size = facesVertexBuffer.remaining();

            for(int i = 0; i < size;i++)
            {
                int faceVertex = Math.round(facesVertexBuffer.get(i)) ;
                int faceTexture = Math.round(facesTextureBuffer.get(i));
                int faceNormal = Math.round(facesNormalBuffer.get(i));

                float x = verticesBufferTemp.get((faceVertex)*3);
                float y = verticesBufferTemp.get(((faceVertex)*3)+1);
                float z = verticesBufferTemp.get(((faceVertex)*3)+2);
                verticesBuffer.put( i*3,   x);
                verticesBuffer.put( (i*3)+1, y);
                verticesBuffer.put( (i*3)+2, z);

                float u = textureBufferTemp.get((faceTexture)*2);
                float v = -textureBufferTemp.get(((faceTexture)*2)+1);
                textureBuffer.put( i*2,   u);
                textureBuffer.put( (i*2)+1, v);

                float xn = normalBufferTemp.get((faceNormal*3));
                float yn = normalBufferTemp.get((faceNormal*3)+1);
                float zn = normalBufferTemp.get((faceNormal*3)+2);
                normalBuffer.put(i*3,xn);
                normalBuffer.put((i*3)+1,yn);
                normalBuffer.put((i*3)+2,zn);

            }
            verticesBuffer.position(0);
            textureBuffer.position(0);
            normalBuffer.position(0);

            is.close();
            loadTexture();
        }
        catch (Exception e) {
            Log.e("MeshReaderActivity", "Error reading objfile", e);
        }
    }

    public void parseData(String dataToParse)
    {
        Log.i("parse data method", "parse data method");
        String[] data = dataToParse.split("\n");
        for (int i = 0;i < data.length;i++)
        {
            String line = data[i];
            if(line.startsWith("v "))
            {
                // Add vertex line to list of vertices
                verticesList.add(line);
            }
            else if(line.startsWith("vt "))
            {
                textureList.add(line);
            }
            else if(line.startsWith("vn "))
            {
                normalList.add(line);
            }
            else if(line.startsWith("f "))
            {
                // Add face line to faces list
                triangulate(line);
            }
        }

    }

    public void triangulate(String lineToTriangulate)
    {
        String lineSplit[] = lineToTriangulate.split("\\s+");
        if(lineSplit.length > 4)
        {
            String line1="";
            String line2="";
            if (lineToTriangulate.contains("/"))
            {
                line1 = lineSplit[0] + " " + lineSplit[1].split("/")[0] + " " + lineSplit[2].split("/")[0] + " " + lineSplit[3].split("/")[0];
                line2 = lineSplit[0] + " " + lineSplit[1].split("/")[0] + " " + lineSplit[2].split("/")[0] + " " + lineSplit[4].split("/")[0];
            }
            else
            {
                line1 = lineSplit[0] + " " + lineSplit[1] + " " + lineSplit[2] + " " + lineSplit[3];
                line2 = lineSplit[0] + " " + lineSplit[1] + " " + lineSplit[2] + " " + lineSplit[4];
            }
            facesVertexList.add(line1);
            facesVertexList.add(line2);
        }
        else
        {
            if(lineToTriangulate.contains("/"))
            {
                String[] splitElement1 = lineSplit[1].split("/");
                String[] splitElement2 = lineSplit[2].split("/");
                String[] splitElement3 = lineSplit[3].split("/");
                String line = lineSplit[0] + " " + splitElement1[0] + " " + splitElement2[0] + " " + splitElement3[0];
                facesVertexList.add(line);
                line = lineSplit[0] + " " + splitElement1[1] + " " + splitElement2[1] + " " + splitElement3[1];
                facesTextureList.add(line);
                line =  lineSplit[0] + " " + splitElement1[2] + " " + splitElement2[2] + " " + splitElement3[2];
                facesNormalList.add(line);

            }
            else
            {
                facesVertexList.add(lineToTriangulate);
            }
        }
    }


    public void draw(float scratch[],float zoom){


        int position = GLES30.glGetAttribLocation(program, "position");
        GLES30.glEnableVertexAttribArray(position);
        GLES30.glVertexAttribPointer(position, 3, GLES30.GL_FLOAT, false, 3 * 4, verticesBuffer);


        int mTextureUniformHandle = GLES30.glGetUniformLocation(program, "uTexture");
        int mTextureCoordinateHandle = GLES30.glGetAttribLocation(program, "vTexCoordinate");

        GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
        GLES30.glUniform1i(mTextureUniformHandle, 0);
        GLES30.glEnableVertexAttribArray(mTextureCoordinateHandle);
        GLES30.glVertexAttribPointer(mTextureCoordinateHandle, 2, GLES30.GL_FLOAT, false, 2*4, textureBuffer);

        int normalHandle = GLES30.glGetAttribLocation(program,"normal");
        GLES30.glEnableVertexAttribArray(normalHandle);
        GLES30.glVertexAttribPointer(normalHandle,3,GLES30.GL_FLOAT,false,3*4,normalBuffer);

        float[] projectionMatrix = new float[16];
        float[] viewMatrix = new float[16];
        float[] productMatrix = new float[16];

        Matrix.frustumM(projectionMatrix, 0,
                -1, 1,
                -1, 1,
                1, 11);
        Matrix.setLookAtM(viewMatrix, 0,
                0, 0, zoom,
                0, 0, 0,
                0, 1, 0);
        Matrix.multiplyMM(productMatrix, 0,
                projectionMatrix, 0,
                viewMatrix, 0);

        float[] finalMatrix = new float[16];

        Matrix.multiplyMM(finalMatrix, 0,
                productMatrix, 0,
                scratch, 0);

        Matrix.rotateM(finalMatrix, 0, 180, 0.0f, 1.0f, 0.0f);


        int matrix = GLES30.glGetUniformLocation(program, "matrix");
        GLES30.glUniform1i(matrix,0);
        //GLES30.glUniformMatrix4fv(matrix, 1, false, productMatrix, 0);
        GLES30.glUniformMatrix4fv(matrix, 1, false, finalMatrix, 0);


        int size = facesVertexBuffer.remaining();

        GLES30.glEnable(GLES30.GL_CULL_FACE);
        GLES30.glCullFace(GLES30.GL_BACK);


        GLES30.glDrawArrays(GLES30.GL_TRIANGLES,0,size);

        GLES30.glDisableVertexAttribArray(position);
        GLES30.glDisableVertexAttribArray(mTextureCoordinateHandle);
    }

    public void loadTexture()
    {

        GLES30.glGenTextures(1, textureHandle,0);

        if (textureHandle[0] == 0)
        {
            throw new RuntimeException("Error generating texture name.");
        }


        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = true;    // No pre-scaling

        Bitmap bitmap = BitmapFactory.decodeResource(contextMeshLoader.getResources(), R.raw.pngface, options);
        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureHandle[0]);
        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR);
        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);


        GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0);

        bitmap.recycle();
    }

}

Thanks again.


Update

For the float, it was an error of copy/paste. For my problem of display I have find the solution. I just need to add in the onDrawFrame method.

GLES30.glEnable(GLES30.GL_DEPTH_TEST);

Now, My meshe and his texture is correctly displayed.

Thanks for your help.

  • Are you sure all your triangles are facing outwards? Try to turn off back face culling, and see if anything changes. – Reaper Feb 06 '19 at 16:51
  • Why do you parse the indices as floating point? Use `Integer.parseInt` rather than `Float.parseFloat`. Use ans integral buffer (`IntBuffer`) instead of a floating point buffer (`FloatBuffer`)? – Rabbid76 Feb 07 '19 at 21:52

1 Answers1

0

Your assumption is wrong. The same vertex coordinate can be associated to different texture coordinates.

It the following code you create a new texture coordinate array, that has as many items as the array of vertex coordinate. That would work only, if each vertex coordinate is associated to exactly 1 texture coordinate. In the file are 7536 texture coordinates and 7366 vertex cooodinates.

public void parseTexture()
{
    int size = facesVertexBuffer.remaining();
    System.out.println("size " + size);
    for(int i = 0; i < size;i++)
    {
        int faceVertex = facesVertexBuffer.get(i);
        int faceTexture = facesTextureBuffer.get(i);
        float a = textureBufferTemp.get((faceTexture)*2);
        float b = -textureBufferTemp.get(((faceTexture)*2)+1);
        textureBuffer.put((faceVertex*2),a);
        textureBuffer.put(((faceVertex)*2)+1,b);
   }
   textureBuffer.position(0);

   System.out.println("end parse texture");
}

If there are different indices for vertex coordinates and texture coordinates, then vertex positions have to be "duplicated". The vertex coordinate and its attributes (like texture coordinate) form a data reocord. You can imagine a 3D vertex coordinate and a 2D texture coordinate as a single 5D coordinate. See Rendering meshes with multiple indices.

The vertex attributes for each vertex position form a set of data. This means you have to create tuples of vertex coordinate, and texture coordiantes.

Let's assume that you have a .obj file like this:

v -1 -1 -1
v  1 -1 -1
v -1  1 -1
v  1  1 -1
v -1 -1  1
v  1 -1  1
v -1  1  1
v  1  1  1

vt 0 0
vt 0 1
vt 1 0
vt 1 1

vn -1  0  0
vn  0 -1  0
vn  0  0 -1
vn  1  0  0
vn  0  1  0
vn  0  0  1

f 3/1/1 1/2/1 5/4/1 7/3/1
f 1/1/2 2/2/2 3/4/2 6/3/2
f 3/1/3 4/2/3 2/4/3 1/3/3
f 2/1/4 4/2/4 8/4/4 6/3/4
f 4/1/5 3/2/5 7/4/5 8/3/5
f 5/1/6 6/2/6 8/4/6 7/3/6

From this you have to find all the combinations of vertex coordinate, texture texture coordinate and normal vector indices, which are used in the face specification:

 0 : 3/1/1
 1 : 1/2/1
 2 : 5/4/1
 3 : 7/3/1
 4 : 1/1/2
 5 : 2/2/2
 6 : 3/4/2
 7 : 6/3/2
 8 : ...

Then you have to create a vertex coordinate, texture coordinate and normal vector array corresponding to the array of combinations indices. The vertex coordinates and its attributes can either be combined in one array to data sets, or to three arrays with equal number of attributes:

 index   vx vy vz     u v     nx ny nz
 0 :     -1  1 -1     0 0     -1  0  0
 1 :     -1 -1 -1     0 1     -1  0  0
 2 :     -1 -1  1     1 1     -1  0  0
 3 :     -1  1  1     1 0     -1  0  0
 4 :     -1 -1 -1     0 0      0 -1  0
 5 :      1 -1 -1     0 1      0 -1  0
 6 :     -1  1 -1     1 1      0 -1  0
 7 :      1 -1  1     1 0      0 -1  0
 8 : ...

Further be aware, the the data type short, which is used for the indices in your application, has a range of [-32768, 32767]. This my be large enough for this model, but the number of indices of larger models will exceed this limit.


The easiest workaround, is, to create an array of triangle primitives. Completely skip the index buffer and use GLES30.glDrawArrays() to draw the mesh.

Red the vertex coordinates and texture coordinates in a temporary buffer.

ByteBuffer bufferVTemp = ByteBuffer.allocateDirect(verticesList.size() * 2 * 4);
bufferVTemp.order(ByteOrder.nativeOrder());
verticesBufferTemp = bufferVTemp.asFloatBuffer();

ByteBuffer bufferTTemp = ByteBuffer.allocateDirect(textureList.size() * 2 * 4);
bufferTTemp.order(ByteOrder.nativeOrder());
textureBufferTemp = bufferTTemp.asFloatBuffer();

for(String vertex: verticesList) {
    String coords[] = vertex.split(" "); // Split by space
    float x = Float.parseFloat(coords[1]);
    float y = Float.parseFloat(coords[2]);
    float z = Float.parseFloat(coords[3]);
    verticesBufferTemp.put(x);
    verticesBufferTemp.put(y);
    verticesBufferTemp.put(z);
}
verticesBufferTemp.position(0);

for (String texture: textureList)
{
    String textureIndices[] = texture.split("\\s+");
    float texture1 = Float.parseFloat(textureIndices[1]);
    float texture2 = Float.parseFloat(textureIndices[2]);
    textureBufferTemp.put(texture1);
    textureBufferTemp.put(texture2);
}
textureBufferTemp.position(0);

Then create an array of tringles

ByteBuffer bufferV = ByteBuffer.allocateDirect(facesVertexBuffer.size() * 3 * 4);
bufferV.order(ByteOrder.nativeOrder());
verticesBuffer = bufferV.asFloatBuffer();

ByteBuffer bufferT = ByteBuffer.allocateDirect(facesVertexBuffer.size() * 2 * 4);
bufferT.order(ByteOrder.nativeOrder());
textureBuffer = bufferT.asFloatBuffer();

int size = facesVertexBuffer.remaining();
System.out.println("size " + size);
for(int i = 0; i < size;i++)
{
    int faceVertex = facesVertexBuffer.get(i);
    int faceTexture = facesTextureBuffer.get(i);

    float x = verticesBufferTemp.get((faceVertex)*2);
    float y = verticesBufferTemp.get(((faceVertex)*2)+1);
    float z = verticesBufferTemp.get(((faceVertex)*2)+1);
    verticesBuffer.put( i*3,    x);
    verticesBuffer.put( i*3+1,  y);
    verticesBuffer.put( i*3+2), z);

    float u = textureBufferTemp.get((faceTexture)*2);
    float v = -textureBufferTemp.get(((faceTexture)*2)+1);
    textureBuffer.put( i*2,   u);
    textureBuffer.put( i*2+1, v);
}
verticesBuffer.position(0);
textureBuffer.position(0);

Draw the mesh by GLES30.glDrawArrays():

GLES30.glDrawElements(GLES30.GL_TRIANGLES, 0, facesVertexBuffer.size());
Rabbid76
  • 202,892
  • 27
  • 131
  • 174