Help with understanding collada

Started by Andrew_3ds, June 30, 2015, 04:31:20

Previous topic - Next topic

Andrew_3ds

I'm trying to start using DAE instead of OBJ files for OpenGL because of armature, and was wondering if anyone has a source that actually explains what everything in the collada file means. I know how to parse XML, I just don't know how to get the indices, vertices, normals, UV coords, and make it all work in order for OpenGL to understand it. I tried reading the Khronos ref card of collada but it didn't make any sense.

Kai

COLLADA is useful if you want to have keyframe skeleton animations with skinning.
For static meshes with arbitrary textures, vertices, indices and normals, the Wavefront OBJ/MTL format is sufficient and is also much easier to interpret.
This format also allows grouping of objects, so you can identify parts in your program and do basic affine transformations/animations on them in your program.
Could you explain your detailed requirements for a model format, because I also did not quite get what you mean by "armature." (at least what that translates to in my native language, it does not make sense to me. :)
If you really need to use COLLADA, there is of course the specs over at Khronos: https://www.khronos.org/files/collada_spec_1_5.pdf
Googling around brought also a GitHub project with some sample files: https://github.com/assimp/assimp/tree/master/test/models/Collada

Andrew_3ds

Armature is another name for the skeleton used for animation. I am currently using OBJ, but I was trying to find a format that also stored bones required for skeletal animation. Collada has support for bones, but it is very confusing to learn, while wavefront is much easier to understand. Is there a better format to use than collada?

quew8

I would recommend COLLADA. It's open source, it's consistent, it supports a whole range of features (more than I've ever managed to support), it has good support among modelling programs and, as such extensive formats go, it is quite easy to parse. Really the only draw back is that it not very memory efficient. If you are making the next Battlefield or Civilization that would be an issue. As it is, don't worry about that.

As for helping you understand, the specification seriously is your best friend. But to explain some of the core topics, this tutorial http://www.wazim.com/Collada_Tutorial_1.htm is not bad. It isn't great either. I used it to write my first parser and by the time I had written that parser, I understood the format enough for the specification to be more than enough. I also understood the format enough to know I had to rewrite the parser because like I say the tutorial is not great. I keep telling myself I'll write my own soon but I have been for a while...

Andrew_3ds

Thanks this helped alot. I parsed it and got all of the data, but I still have a question regarding texture coordinate sorting. Obviously, OpenGL only has one index buffer, but this has 3 different index for positions, normals, and textures. Each vertex calls a certain position, normal, and texture coordinate from the array, and since there is only one index buffer in OpenGL you have to sort normals and texture arrays to match the corresponding index from the vertex array, so for example index 7 gets the right position, normal, and texture coordinate for that vertex. Because two vertexes can share positions and normals, their arrays don't have to be as large as texture coordinate arrays, and because of this the biggest position index that will be called by the index buffer will be smaller than the maximum number of texture coordinates in its array. How do I get texture coordinates working since vertices don't share UV coordinates, and the index won't be big enough to get all the texture coordinates?

This is what I'm using to sort the normals, but I can't do this for texture coords because two vertices can share a position but not a texture coordinate, which would result in the previous texture coordinate getting overwritten when the vertex gets called again.
positions = new Vec3[temp_positions.size()];
normals = new Vec3[temp_normals.size()];

for(Vertex v : indices) {
    positions[v.pPointer] = temp_positions.get(v.pPointer);
    normals[v.pPointer] = temp_normals.get(v.nPointer);
}

quew8

The way I do it, and I make no claim that this is the best way but it is at least pretty simple, is to make a new version of a vertex each time it is referenced and then minimize the resulting set down by merging exact copies.

So example. Say you had a few arrays like this:

Position XYZ: [0, 0, 0, 1, 1, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0] (written nicely) [[0, 0, 0], [1, 1, 0], [2, 0, 0], [2, 2, 0], [0, 2, 0]]
Normal XYZ: [1, 0, 0, 0, 1, 0, 0, 0, 1]            (written nicely) [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
Texture Coords ST: [0, 0, 0.5, 0.5, 1, 0, 0, 1, 1, 1]       (written nicely) [[0, 0], [0.5, 0.5], [1, 0], [1, 1], [0, 1]]

And then there was a mesh with
<p> addressing position, normal, tex coords [0, 0, 0, 1, 0, 1, 2, 0, 2, 2, 0, 2, 1, 0, 1, 3, 0, 3, 3, 1, 3, 1, 1, 1, 4, 1, 4]
                  (written nicely) [[0, 0, 0], [1, 0, 1], [2, 0, 2], [2, 0, 2], [1, 0, 1], [3, 0, 3], [3, 1, 3], [1, 1, 1], [4, 1, 4]]
<vcount> [3, 3, 3]

So in the first pass, the vertices are just constructed ignoring everything but the raw data. So you get this list of vertices:
Quote0: Vertex{pos:[0, 0, 0], normal:[1, 0, 0], texCoords:[0, 0]}
1: Vertex{pos:[1, 1, 0], normal:[1, 0, 0], texCoords:[0.5, 0.5]}
2: Vertex{pos:[2, 0, 0], normal:[1, 0, 0], texCoords:[1, 0]}
3: Vertex{pos:[2, 0, 0], normal:[1, 0, 0], texCoords:[1, 0]}
4: Vertex{pos:[1, 1, 0], normal:[1, 0, 0], texCoords:[0.5, 0.5]}
5: Vertex{pos:[2, 2, 0], normal:[1, 0, 0], texCoords:[1, 1]}
6: Vertex{pos:[2, 2, 0], normal:[0, 1, 0], texCoords:[1, 1]}
7: Vertex{pos:[1, 1, 0], normal:[0, 1, 0], texCoords:[0.5, 0.5]}
8: Vertex{pos:[0, 2, 0], normal:[0, 1, 0], texCoords:[0, 1]}

and the indices are just

Quote[0, 1, 2, 3, 4, 5, 6, 7, 8]

But then the minimizing algorithm notices that 1 and 4 are the same so it merges them:

Quote0: Vertex{pos:[0, 0, 0], normal:[1, 0, 0], texCoords:[0, 0]}
1: Vertex{pos:[1, 1, 0], normal:[1, 0, 0], texCoords:[0.5, 0.5]}
2: Vertex{pos:[2, 0, 0], normal:[1, 0, 0], texCoords:[1, 0]}
3: Vertex{pos:[2, 0, 0], normal:[1, 0, 0], texCoords:[1, 0]}
4: Vertex{pos:[2, 2, 0], normal:[1, 0, 0], texCoords:[1, 1]}
5: Vertex{pos:[2, 2, 0], normal:[0, 1, 0], texCoords:[1, 1]}
6: Vertex{pos:[1, 1, 0], normal:[0, 1, 0], texCoords:[0.5, 0.5]}
7: Vertex{pos:[0, 2, 0], normal:[0, 1, 0], texCoords:[0, 1]}

[0, 1, 2, 3, 1, 4, 5, 6, 7]

remembering that indices after 4 get decremented. And then 2 and 3 can be merged as well:

Quote0: Vertex{pos:[0, 0, 0], normal:[1, 0, 0], texCoords:[0, 0]}
1: Vertex{pos:[1, 1, 0], normal:[1, 0, 0], texCoords:[0.5, 0.5]}
2: Vertex{pos:[2, 0, 0], normal:[1, 0, 0], texCoords:[1, 0]}
3: Vertex{pos:[2, 2, 0], normal:[1, 0, 0], texCoords:[1, 1]}
4: Vertex{pos:[2, 2, 0], normal:[0, 1, 0], texCoords:[1, 1]}
5: Vertex{pos:[1, 1, 0], normal:[0, 1, 0], texCoords:[0.5, 0.5]}
6: Vertex{pos:[0, 2, 0], normal:[0, 1, 0], texCoords:[0, 1]}

[0, 1, 2, 2, 1, 3, 4, 5, 6]

And this is what would be put in the VBO / IBO. It is actually as small as you can make the data and still draw the mesh in a single draw (because otherwise you'd have to mess around with changing pointers)

So this post turned out longer than I meant it to be. Anyway hope it is helpful.