3
\$\begingroup\$

I was using IBOs to render meshes (for example a cube) from wave-front files (.obj), without texture coordinates or normals, in OpenGL.

Following this, I attempted to implemented texturing. The mesh was distorted because the indices are not referencing the correct vertices. There is a one-to-one relation between texture coordinates and vertices. Therefore, by implementing texture coordinates, extra vertices have been added to the vertex array/buffer (vertices with the same position but with different texture coordinates). Therefore, existing vertices have been offset and no longer correspond to the indices array/buffer.

enter image description here

Related rendering Code:

private int vbo;
private int ibo;
private int size;

public Mesh() {
    vbo = glGenBuffers();
    ibo = glGenBuffers();
    size = 0;
}

public void addVertices(Vertex[] vertices, int[] indices) {
    size = indices.length;

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, Util.createFlippedBuffer(vertices),
            GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
            Util.createFlippedBuffer(indices), GL_STATIC_DRAW);
}

public void draw() {
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, false, Vertex.SIZE * 4, 0);
    glVertexAttribPointer(1, 2, GL_FLOAT, false, Vertex.SIZE * 4, 12);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glDrawElements(GL_TRIANGLES, size, GL_UNSIGNED_INT, 0);

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
}

I had to switch to a vertex buffer object (VBO) only method of rendering to properly display the mesh with its texture. However, now will not gain any benefits of IBOs when render other, more complex meshes.

enter image description here

Related rendering Code:

private int vbo;
private int size;

public Mesh() {
    vbo = glGenBuffers();
    size = 0;
}

public void addVertices(Vertex[] vertices, int[] indices) {
    size = vertices.length;

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, Util.createFlippedBuffer(vertices),
            GL_STATIC_DRAW);
}

public void draw() {
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glVertexAttribPointer(0, 3, GL_FLOAT, false, Vertex.SIZE * 4, 0);
    glVertexAttribPointer(1, 2, GL_FLOAT, false, Vertex.SIZE * 4, 12);

    glDrawArrays(GL_TRIANGLES, 0, size);

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
}

Code for loading meshes from wave-front files:

ArrayList<Vertex> vertices = new ArrayList<Vertex>();
    ArrayList<Integer> indices = new ArrayList<Integer>();
    ArrayList<Integer> textureIndices = new ArrayList<Integer>();
    ArrayList<Vector2f> textureCoordinates = new ArrayList<Vector2f>();
    BufferedReader meshReader = null;

    try {
        meshReader = new BufferedReader(new FileReader("./res/models/"
                + fileName));

        String line;

        while ((line = meshReader.readLine()) != null) {
            String[] tokens = line.split(" ");
            tokens = Util.removeEmptyStrings(tokens);

            if (tokens.length == 0 || tokens[0].equals("#"))
                continue;
            else if (tokens[0].equals("v"))
                vertices.add(new Vertex(new Vector3f(Float
                        .valueOf(tokens[1]), Float.valueOf(tokens[2]),
                        Float.valueOf(tokens[3]))));
            else if (tokens[0].equals("vt"))
                textureCoordinates.add(new Vector2f(Float
                        .valueOf(tokens[1]), Float.valueOf(tokens[2])));
            else if (tokens[0].equals("f")) {
                for (int t = 1; t < 4; t++) {
                    String[] splitToken = tokens[t].split("/");

                    indices.add(Integer.parseInt(splitToken[0]) - 1);
                    textureIndices.add(Integer.parseInt(splitToken[1]) - 1);
                }
            }
        }

        meshReader.close();

        ArrayList<Vertex> processedVertices = new ArrayList<Vertex>();

        for(int i = 0; i < indices.size(); i++) {
            processedVertices.add(new Vertex(vertices.get(indices.get(i)).getPos(), textureCoordinates.get(textureIndices.get(i))));
        }

        // Then 'indices' and 'processedVertices' are sent to the GPU.

How can I correctly implement IBOs with texture coordinates, so that an IBO is 'aligned' to a VBO after adding extra vertices (vertices with the same position but with different texture coordinates)?

\$\endgroup\$
3
  • \$\begingroup\$ To me it seems that more than just the texture coordinates are wrong when you use indexing. the shape of the box is wrong. Could you show us the code? (Also you should in the end probably use indexing WITH VBOs.) \$\endgroup\$
    – akaltar
    Jun 14, 2015 at 11:11
  • \$\begingroup\$ @Parkash I think you are messing up when you are preprocessing vertices. Try to send just vetices and indicdes.(If this is indeed the problem, I will post this as an answer so you can accept it) Could I also see your rendering code? \$\endgroup\$
    – akaltar
    Jun 14, 2015 at 14:11
  • \$\begingroup\$ I have posted the rendering code. Sending just the vertices ('unprocessed') and indices produces the correct shade. However, this does not answer the question. How will the shader know how to texture this mesh without texture coordinates. \$\endgroup\$
    – pm79080
    Jun 14, 2015 at 14:48

3 Answers 3

2
\$\begingroup\$

The problem is that obj files contain vertex data in parts:pos,tex,normal and indices in them point to each of these parts individually. In OpenGL you need to combine those parts into one data object - vertex. And have your indices point to vertices, not their parts.

My code (scala, but should be ok)

val vertexListB = new RList[vec3]()//output positions
val texListB = new RList[vec2]()//output texCoords
val normalListB = new RList[vec3]()//output normals
val indexListB = new RList[Int]()//output indices

val vertices = new RList[VertexFull]()//output vertex structure list, consists of pos, tex, normal

for(i <- 0 until vertexIndicesTriangleList.size()){
  val vI = vertexIndicesTriangleList(i) //vertexIndicesTriangleList - all scanned vertex positions from obj file
  val tI = texCoordsIndicesTriangleList(i) //texCoordsIndicesTriangleList - all scanned vertex texture coords from obj file
  val nI = normalIndicesTriangleList(i)//normalIndicesTriangleList - all scanned vertex normals from obj file
  //we are looping through all vertex positions

  val v = vertexList(vI) //some pos
  val t = textureList(tI)//some texCoord that corresponds to that pos
  val n = normalList(nI)//some normal that corresponds to that pos

  val hv = vertexListB.getIndex(v,false)
  val ht = texListB.getIndex(t, false)
  val hn = normalListB.getIndex(n, false)

  if(ht != -1 && hv == ht && hn == ht){ //check for duplicate vertex data, its duplicated if and only if all of 3 components:pos, tex and normal are equal
    indexListB << hv //if ht != -1 then our list does contain that vertex already
  }
  else{
    vertexListB << v //if doesnt contain/not duplicated then put data into the lists
    texListB << t
    normalListB << n
    indexListB << vertexListB.size() - 1//index points to the last put vertex, vertexListB, texListB, normalListB are equal sized
  }

}

for(i <- 0 until vertexListB.size()){
  vertices << new VertexFull(vertexListB(i), texListB(i), normalListB(i))
}
\$\endgroup\$
3
\$\begingroup\$

Basically, there is Vertex data, which is stored in one or more VBOs. Typical (3D) vertex has like: vec3 pos, vec2 uv, mat3 tbn (or vec3 normal) - maybe some other modifiers, possibly several different uv coords or something like that. Anyways, no matter if you store those to 1 or multiple VBOs, IBO will index all the buffers with same index.

So, each pos/uv/tbn combination is unique vertex: three vertices may have same pos, but if one has different uv (and thus tbn) and one has different tbn/normal, they are then three different vertices. That means, that if you use e.g. three VBOs (one for pos, one for uv, one for normal/tbn), they all need to be exactly the same size.

So, to make your IBO to work, you create an array with all the unique vertex attributes, and index them. I found it easier to use interleaved VBO. Here is my implementation of wavefront OBJ loader (in D; missing some things like normal smoothing):

https://github.com/mkoskim/games/blob/master/engine/blob/wavefront.d

In my own project, I'm slowly moving towards ASSIMP suggested in this thread, too. But still, IMO it is worthwhile to examine your own loaders/generators to get deeper view how GPUs (or at least OpenGL shaders) work with vertex data.

\$\endgroup\$
6
  • \$\begingroup\$ So by 'create an array with all the unique vertex attributes' do you mean, I should create an array with each element consisting of a unique pos/uv/tbn combination? If so, how do I index them? \$\endgroup\$
    – pm79080
    Jun 26, 2015 at 10:45
  • \$\begingroup\$ Yes, I mean exactly that - all combinations of all vertex data (pos, uv, tbn) make unique combinations. You make those, and index them with same index from IBO. \$\endgroup\$
    – MaKo
    Jun 26, 2015 at 12:23
  • \$\begingroup\$ I mean: You have same pos, but different normals - you need to split vertex data to several different vertex rows. You have same pos, same uv, but you have UV seam at that point, meaning that it results different tbn - you need to split that point to several vertices. \$\endgroup\$
    – MaKo
    Jun 26, 2015 at 12:26
  • \$\begingroup\$ ...Vertex data can be shared when all the attributes you have assigned to it (pos, uv, normal/tbn, color, ...) are exactly the same. \$\endgroup\$
    – MaKo
    Jun 26, 2015 at 12:32
  • \$\begingroup\$ As shown in my first screen shot, I have attempted to do this (creating a vertex for every combination of attributes and using the same indices) and this has created an incorrect result. \$\endgroup\$
    – pm79080
    Jun 29, 2015 at 16:56
0
\$\begingroup\$

When using index buffers, you are indexing into all of the buffers at the same time. That means that if you have a point which can have multiple texture coordinates(If there is a seam at that point) or normals(for example if faceted) then you need to duplicate the given points vertex coordinates with all possible combinations.


If you want to have indexing with texture coordinates and normals, then you have 2 options:

  • Export the .obj so that it has all the duplicates already(a.k.a. baking)(I believe you can set this up in 3ds max GW obj exporter by disabling "optimize")
  • Do this duplicating of vertices in your own code.

I would recommend either leaving this system as-is and worry about performance later or use Assimp, which not only does this all for you, it supports more formats with consistency, and will save you some headaches later with its tangent generation and other useful preprocessing.

Some pseudocode for aligning as asked for:

float* verticess = new float[NumFaces * 9]; // vertices : input, verticess ouptut to VBO
float* normalss = new float[NumFaces * 9];  // normals: input, normalss outut to VBO
float* texcoordss = new float[NumFaces * 6];
for(int i = 0; i < NumFaces; i++)
{
    //For 3 vertices with 3(xyz coordinates) va - A vertex index, vb - B vertex ....
    verticess[i * 9] = vertices[faces[i].va-1].v[0]; // vertices[i].v is array of 3(xyz)
    verticess[i*9+1] = vertices[faces[i].va-1].v[1];
    verticess[i*9+2] = vertices[faces[i].va-1].v[2];

    verticess[i*9+3] = vertices[faces[i].vb-1].v[0];
    verticess[i*9+4] = vertices[faces[i].vb-1].v[1];
    verticess[i*9+5] = vertices[faces[i].vb-1].v[2];

    verticess[i*9+6] = vertices[faces[i].vc-1].v[0];
    verticess[i*9+7] = vertices[faces[i].vc-1].v[1];
    verticess[i*9+8] = vertices[faces[i].vc-1].v[2];

    //For normalss 3 normals with 3(xyz coordinates) na - A normal index...

    normalss[i * 9] = normals[faces[i].na-1].v[0];
    normalss[i*9+1] = normals[faces[i].na-1].v[1];
    normalss[i*9+2] = normals[faces[i].na-1].v[2];

    normalss[i*9+3] = vecnormals[faces[i].nb-1].v[0];
    normalss[i*9+4] = vecnormals[faces[i].nb-1].v[1];
    normalss[i*9+5] = vecnormals[faces[i].nb-1].v[2];

    normalss[i*9+6] = vecnormals[faces[i].nc-1].v[0];
    normalss[i*9+7] = vecnormals[faces[i].nc-1].v[1];
    normalss[i*9+8] = vecnormals[faces[i].nc-1].v[2];

    //for texcoordss 3 coords with 2(st coordinates) ta - A tex index ....

    texcoordss[i * 6] = texcoords[faces[i].ta-1].v[0];
    texcoordss[i*6+1] = texcoords[faces[i].ta-1].v[1];

    texcoordss[i*6+2] = texcoords[faces[i].tb-1].v[0];
    texcoordss[i*6+3] = texcoords[faces[i].tb-1].v[1];

    texcoordss[i*6+4] = texcoords[faces[i].tc-1].v[0];
    texcoordss[i*6+5] = texcoords[faces[i].tc-1].v[1];
}
\$\endgroup\$
6
  • \$\begingroup\$ Could you provide an example (pseudocode) of how to duplicate vertices, so that the indices and vertices arrays/buffers are correctly aligned? \$\endgroup\$
    – pm79080
    Jun 15, 2015 at 13:00
  • \$\begingroup\$ @Prakash Added, while it might be a verbose its what I used some time ago, so it works.(Sorry for the awful naming) \$\endgroup\$
    – akaltar
    Jun 15, 2015 at 15:28
  • \$\begingroup\$ I see that you have shown how to duplicate vertices but I do not understand how this code relates to aligning an IBO to match the correct elements in a VBO, after duplicate vertices have been added to the VBO. \$\endgroup\$
    – pm79080
    Jun 15, 2015 at 16:03
  • \$\begingroup\$ @Prakash After you do this, you don't need to use the index buffer for rendering, its "baked" into the VBO. You just created the VBO from the index data. You could use more intelligent code to create something that still uses the index buffer just cleverly matches the other buffers to it, but you'd need to modify the index buffer too if you want seams in normals or texture coordinates. \$\endgroup\$
    – akaltar
    Jun 15, 2015 at 16:10
  • \$\begingroup\$ I have already shown above (in the source code for loading the mesh from file) that I have implemented the 'baking' method with the 'processedVertices' array. I would like to use IBOs so I am looking for an example of intelligent code to modify the index buffer. :) \$\endgroup\$
    – pm79080
    Jun 15, 2015 at 17:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .