11
votes

I'm writing a a python export script from Blender 2.64. The idea is that I'm trying to export the mesh data in an OpenGL VBO-friendly way. So I'm exporting vertex attributes in array of structs layout. For instance, for a mesh with vertices, normals and 1 pair of texture coordinates, each vertexAttribute in the VBO would be 8 consecutive floats:

vvvnnntt 

So far so good. The problem is, when Blender does UV mapping, it can actually assign different uvs to a same vertex.

That is, say you have a cube: you have 8 vertices and you have, say 6 faces (quads in this case). I would have expected that a face/polygon with indices 0,1,2,3 implied:

vertex 0, normal 0, uvCoord0 
vertex 1, normal 1, uvCoord1 
vertex 2, normal 2, uvCoord2 
vertex 3, normal 3, uvCoord3 

And consequently, any mention of index 0 for instance, in any face, will always imply the vertex 0, normal 0, uvCoord0 tuple. Well, turns out in Blender, if I understand correctly one face could reference vertex 0 with uvCoord 0 while another might reference the same vertex 0 with a different uVCoord. So a face's loop_indices must actually be used to lookup both a vector and a uvCoord in an objets general data.vertices and data.uv_layers[].data containers.

This allows for applying uv maps per-face. So you could have a cube where each face has a different uv texture applied and even if two adjacent faces share a vertex, the vertex has a different uv coord depending on the face.

Still, my mesh shouldn't really have different uvs for a same vertex since I'm unwrapping into adjacent faces. Which means in my UV map, the unwrapped mesh is a set of adjacent faces (think for instance, a cross shape if it were a cube made up of 6 faces) and between two adjacent faces, their common vertices should mapp to the same point in the uv map.

So given the above, I thought this approach should work:

vertexAttributeList = []
    for vertex in mesh.data.vertices:
        vertexAttribute = list(vertex.co)
        vertexAttribute.extend(list(vertex.normal))
        vertexAttributeList.append(vertexAttribute)

    for triangle in mesh.data.polygons:
        for uv_layer in mesh.data.uv_layers:
            for i in triangle.loop_indices:
                lookupIndex = mesh.data.loops[i].vertex_index
                if len(vertexAttributeList[lookupIndex]) == 6:
                    uvCoord = uv_layer.data[i].uv
                    vertexAttributeList[lookupIndex].extend([uvCoord[0], 1 - uvCoord[1]])

As you can see, the implication in the above code is that I'll visit vertices more than once cause I'm iterating over a mesh's faces (triangles in this case) which share vertices. And every time I visit a vertex, if it hasn't had its UV coords assigned yet, then I assign them by looking them up using the triangle's loop_indices. After all, my assumption was I do have, at the end of the day, unique uv coords per vertex.

The above code gives he following layout, for example (I'm showing a mesh's first 6 vertex attributes):

-1.000000 -1.000000 -1.000000 -0.707083 -0.707083  0.000000  0.076381  0.948520
-1.000000  1.000000 -1.000000 -0.707083  0.707083  0.000000  0.454183  0.948519
 1.000000  1.000000 -1.000000  0.707083  0.707083  0.000000  0.325162  0.948519
 1.000000 -1.000000 -1.000000  0.707083 -0.707083  0.000000  0.205674  0.948519
-1.000000 -1.000000  1.000000 -0.577349 -0.577349  0.577349  0.581634  0.795012
-1.000000  1.000000  1.000000 -0.577349  0.577349  0.577349  0.454183  0.795012
...

But when I use this info plus a mesh's faces, which I layout like so:

4 5 1
5 6 2
6 7 3
7 4 0
...

to render my model in my program (an engine of sorts), the uv mapping is clearly messed up. That is, the model renders fine in terms of vertices and normals but the uv texture is clearly not correctly mapped.

Any thoughts? I mean, either I'm exporting right and messing up the OpenGL render code in my app or I'm exporting the wrong mapping between vertices and uv coordinates. (or both, sure.. but I'm assuming for now I'm messing up the export script).

One last thing, if I change the above python code to append each new uv which is assigned to a vertex instead of appending only if no uv was assigned yet, for vertex 1 I get:

[-1.0, -1.0, -1.0, -0.7070833444595337, -0.7070833444595337, 0.0, 0.07638061791658401, 0.9485195726156235, 0.5816344618797302, 0.9485194832086563, 0.07638061791658401, 0.9485195726156235]

And mind you there's only one uv layer in this example. So clearly Blender did assign 2 uv coordinates to vertex 1.

1
BTW, in the meantime I went for the "no GL_ELEMENT_ARRAY" approach and so that I end up with duplicated vertices but with different uvs and, while I now have a huge VBO where vertices show up as many times as triangles reference them, the uvs mapp just fine when I render.SaldaVonSchwartz

1 Answers

2
votes

I think there might be a way to interpolate or otherwise reconcile / blend UVs so as to end up with a single UV per vertex if everything is adjacent. But in the meantime, and given that no one suggested an alternative, I ended up duplicating vertices, with their different UVs, and dropping the attempt at exporting for GL_ELEMENT_ARRAY. The following code works if rendering with a single VBO (using glDrawArrays):

def exportMesh(filepath):

    # Only one mesh per scene
    objList = [object for object in bpy.context.scene.objects if object.type == 'MESH']

    if len(objList) == 0:
        return
    elif len(objList) > 1:
        return
    #raise exepction? dialog box?


    # Process the single mesh object:
    mesh = objList[0]

    # File name is same as the mesh's name in Blender
    meshFilePath = filepath[0 : filepath.rindex('/') + 1] + mesh.name + ".mesh"
    file = open(meshFilePath, 'w')

    WorldTransform = Matrix().Identity(4)
    WorldTransform *= Matrix.Rotation(radians(90), 4, "X")
    file.write('World Transform:\n')
    for rcol in WorldTransform_T.row:
        file.write('{:9f} {:9f} {:9f} {:9f}\n'.format(row[0], row[1], row[2], row[3]))
    file.write('\n')

    # Mesh (local) transform matrix
    file.write('Mesh Transform:\n')
    localTransform_T = mesh.matrix_local.copy()
    localTransform_T.transpose()
    for row in localTransform_T.row:
        file.write('{:9f} {:9f} {:9f} {:9f}\n'.format(row[0], row[1], row[2], row[3]))
    file.write('\n')

    vertexAttributeList = []
    for triangle in mesh.data.polygons:
        vertices = list(triangle.vertices)
        i = 0
        for vertex in vertices:
            vertexAttribute = list(mesh.data.vertices[vertex].co)

            if triangle.use_smooth:
                vertexAttribute.extend(list(mesh.data.vertices[vertex].normal))
            else:
                vertexAttribute.extend(list(triangle.normal))

            for uv_layer in mesh.data.uv_layers:
                uvCoord = uv_layer.data[triangle.loop_indices[i]].uv
                vertexAttribute.extend([uvCoord[0], 1 - uvCoord[1]])

            totalVertexWeight = 0
            jointWeights = [group.weight for group in mesh.data.vertices[vertex].groups]
            jointIndices = [group.group for group in mesh.data.vertices[vertex].groups]
            for weight in jointWeights:
                totalVertexWeight += weight

            vgNum = len(mesh.vertex_groups)
            jointWeightsAttribute = []
            jointIndicesAttribute = []
            for vgIndex in range(4):
                if vgIndex < len(jointIndices):
                    jointWeightsAttribute.append(jointWeights[vgIndex] / totalVertexWeight)
                    jointIndicesAttribute.append(jointIndices[vgIndex])
                else:
                    jointWeightsAttribute.append(0)
                    jointIndicesAttribute.append(0)

            vertexAttribute.extend(jointWeightsAttribute)
            vertexAttribute.extend(jointIndicesAttribute)

            vertexAttributeList.append(vertexAttribute)
            i += 1

    # VBO
    vNum = len(vertexAttributeList)
    tNum = len(mesh.data.uv_layers)
    file.write('VBO Length: {:d}\n'.format(vNum))
    for vertexAttribute in vertexAttributeList:
        file.write('{:9f} {:9f} {:9f} {:9f} {:9f} {:9f} {:9f} {:9f} {:9f} {:9f} {:9f} {:9f} {:d} {:d} {:d} {:d}\n'.format(vertexAttribute[0],
                                                                                                                          vertexAttribute[1],
                                                                                                                          vertexAttribute[2],
                                                                                                                          vertexAttribute[3],
                                                                                                                          vertexAttribute[4],
                                                                                                                          vertexAttribute[5],
                                                                                                                          vertexAttribute[6],
                                                                                                                          vertexAttribute[7],
                                                                                                                          vertexAttribute[8],
                                                                                                                          vertexAttribute[9],
                                                                                                                          vertexAttribute[10],
                                                                                                                          vertexAttribute[11],
                                                                                                                          vertexAttribute[12],
                                                                                                                          vertexAttribute[13],
                                                                                                                          vertexAttribute[14],
                                                                                                                          vertexAttribute[15]))
    file.write('\n')


    # Done writing mesh file
    file.close()