0

In my first Android OpenGL Application I want to render a simple cube with a texture to the screen. I load my object from an obj File and if I render it with a color it works great but with a texture I have some problems. I have debuged my code and looked at all arrays and found out that all is read fine from the file. Here is my obj file:

v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000

vt -1.918677 2.918677
vt -1.918677 -1.918677
vt 2.918677 -1.918677
vt 2.918677 2.918677

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

To draw the object on the screen I have these arrays (later as Buffer) and these OpenGL methods:

float vertices[] = {1f, -1f, -1f, 1f, ... , -1f, 1f, -1f };
short order[]    = { 1, 2, 3, 7, ... , 4, 0, 7 }; // f o/t | o - 1
float textures[] = { -1.918677, 2.918677, ... , 2.918677 2.918677 };

Matrix.multiplyMM(mvpMatrix, 0, vpMatrix, 0, modelMatrix, 0);
GLES20.glUniformMatrix4fv(shaderMatrix, 1, false, mvpMatrix, 0);

GLES20.glEnableVertexAttribArray(shaderPosition);
GLES20.glVertexAttribPointer(shaderPosition, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);

GLES20.glEnableVertexAttribArray(shaderTexCoordinate);
GLES20.glVertexAttribPointer(shaderTexCoordinate, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);

GLES20.glDrawElements(GLES20.GL_TRIANGLES, orderBuffer.capacity(), GLES20.GL_UNSIGNED_SHORT, orderBuffer);
GLES20.glDisableVertexAttribArray(shaderPosition);

But this does not work - the texture is not displayed correctly. I have the presumption the OpenGL does not know which triangle it has to draw with witch texture coordinate. So how can I tell this OpenGL? Do I have to create an array/buffer for the texture coordinates with the same size of the vertices so OpenGL use the first the vertices to render the triangle and the first three texturecoodinates for this triangle? Or how do I have to do it?

So in conclusion: Can I set something like the drawOrder funktion but for textureCoordinates?

Cilenco
  • 6,951
  • 17
  • 72
  • 152
  • See http://stackoverflow.com/a/23356738 and http://stackoverflow.com/a/23713424 for answers that describe how to handle this. – Reto Koradi Dec 31 '14 at 04:12
  • Please see my own answer here. I think I have implemented the pseudocode and the texture looks great now but now the cube itself is not draw perfectly. Is there something wrong with the vertices? – Cilenco Dec 31 '14 at 11:34
  • That code looks reasonable at first sight. I would use a simple example like the cube, and step through it in a debugger. Should be fairly obvious to spot where things go wrong. – Reto Koradi Dec 31 '14 at 19:29
  • Thank you it works now. My problem was that I havn't enabled the depth buffer... Stupid mistake but now it works great :) – Cilenco Jan 01 '15 at 12:34

2 Answers2

1

OpenGL considers a single vertex to be the combination of every attribute that describes that vertex. So, in your case, a single unit composed of a position and a texture coordinate is a vertex. They're of dimension five.

A line like:

f 2/1 3/2 4/3

Describes a triangle with three vertices. The vertices have coordinates:

(1.000000 -1.000000 1.000000, -1.918677 2.918677)
(-1.000000 -1.000000 1.000000, -1.918677 -1.918677)
(-1.000000 -1.000000 -1.000000, 2.918677 -1.918677)

You probably want to load by:

  1. create an array containing every v;
  2. create an array containing every vt;
  3. create a hash map from (v, vt) to index;
  4. for each vertex referred to by an f, consult the hash map for an index:
    • if one exists, add that index to your order buffer;
    • otherwise look up the appropriate values for that v and vt and append them to your vertexBuffer and textureBuffer arrays, store the index of the position you just wrote to to both your hash map with key (v, vt) and to your order buffer.

Given that you're drawing a cube, it's likely you'll end up with 24 vertices — your vertexBuffer array will be 24*3 scalars long and your textureBuffer array will be 24*2 scalars long. Your transient hash map will end up with 24 items in it, mapping to the integers from 0 to 23.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • I do not realy understand waht you are doing in step 4. `shaderPosition` and `shaderTexCoordinate` aren't arrays - they are only the varaibles from the shaders. And if I create a HashMap how can I pass it to the OpenGL framework? – Cilenco Dec 30 '14 at 19:17
  • Sorry, bad copying and pasting. Should be fixed. Your arrays are, of course, `vertexBuffer` and `textureBuffer`. – Tommy Dec 30 '14 at 19:18
  • One last question: Can I calculate the size of the order array? – Cilenco Dec 30 '14 at 20:22
  • The `orderBuffer`? If you're always receiving and drawing as `GL_TRIANGLES` then the number of `f` statements will indicate. Otherwise no. Similarly the number of vertices is the number of unique combinations of a `v` and a `vt` within the `f` lists so there's no formula for that, though it'll be between 1 and `v.count * vt.count` (and usually much closer to the low end of the spectrum), – Tommy Dec 30 '14 at 21:25
  • (aside: also don't forget that `y` texture coordinates differ between OBJ and OpenGL samplers; (0,0) is the top left in OBJ but the bottom left in OpenGL. Easiest solution: store `1.0-y` rather than `y` itself when loading) – Tommy Dec 30 '14 at 21:27
  • The vertex array has only the size 60 and the texture array the size 40. Is that right? In f there are 20 different combinations of v/vt so I think it is right. The order array size is 36 so I think this is right too. But as before the texture looks terrible. If all this is right do I have to use other GL methods now to render the scene? – Cilenco Dec 30 '14 at 22:26
0

My code for loading the object from the obj file. At the end I convert the vertices, textures and drawings ArrayLists to an array and the to the Buffer.

EDIT: For all how are looking for an obj Loader. This code here works great. Please be sure to enable the depth buffer. That was my problem. Otherwise the triangles are drawn in front of the other and it does not look like a cube. Thanks to everyone how helped me with this code.

while((line = bufferedReader.readLine()) != null)
{
    lineCounter += 1; line.trim();
    if(line.length() <= 1) continue;

    StringTokenizer tok = new StringTokenizer(line, " ");
    String cmp = tok.nextToken();

    if(cmp.equals("v"))
    {   // Read out the vertices
        all_vertices.add(   Float.valueOf(tok.nextToken())   );
        all_vertices.add(   Float.valueOf(tok.nextToken())   );
        all_vertices.add(   Float.valueOf(tok.nextToken())   );
    }
    else if(cmp.equals("vt"))
    {   // Read out the texture
        all_textures.add(   Float.valueOf(tok.nextToken())   );
        all_textures.add(   Float.valueOf(tok.nextToken())   );
    }
    else if(cmp.equals("f") || cmp.equals("fo"))
    {
        while(tok.hasMoreTokens()) 
        {
            String indices = tok.nextToken().trim();
            String[] variables = indices.split("/");

            if(map.containsKey(indices)) drawings.add(map.get(indices));
            else
            {
                short vertexIndex = (short) (vertices.size() / 3);
                map.put(indices, vertexIndex); drawings.add(vertexIndex);
                Log.d("ME", indices + "|" + vertexIndex);

                int verNumber = Integer.valueOf(variables[0]) - 1;
                vertices.add(all_vertices.get(3 * verNumber + 0));
                vertices.add(all_vertices.get(3 * verNumber + 1));
                vertices.add(all_vertices.get(3 * verNumber + 2));

                int texNumber = Integer.valueOf(variables[1]) - 1;
                textures.add(all_textures.get(2 * texNumber + 0));
                textures.add(all_textures.get(2 * texNumber + 1));
            }
        }
    }
}

Here you have also a little piece of code which converts the ArrayLists to arrays which you can then put in the specific Buffer. It is very performant and lies in the complexity n. Have fun with it:

float vertices[] = new float[vert.size()];
short drawings[] = new short[draw.size()];
float textures[] = new float[texs.size()];

int maxVD  = Math.max(vert.size(), draw.size());
int maxVNT = Math.max(maxVD, texs.size());
Log.d("ME", "" + maxVNT);

for(int i=0; i < maxVNT; i++)
{
    int vi = Math.min(i, vertices.length - 1);
    vertices[vi] = vert.get(vi);

    int di = Math.min(i, drawings.length - 1);
    drawings[di] = draw.get(di);

    int ti = Math.min(i, textures.length - 1);
    textures[ti] = texs.get(ti);
}
Cilenco
  • 6,951
  • 17
  • 72
  • 152