Public Group

Texture mapping an indexed cube

Recommended Posts

I'm trying to implement .obj file loading in my engine using tinyobjloader library, however I'm running into a texturing issue. The texture on the sides of the cube gets stretched out. After doing some digging I came across a post on stackoverflow with a similar issue and the answers were that because the cube is indexed and only has 8 vertices, some of the uv values are the same (since vertices are shared). The recommended solution was to use 4 vertices per face. It also says that if you need normal of the face (which I do, for lighting etc) that I'd have to have unique vertices anyways.

So in other words, does that mean I can't use indexed geometry if i plan to light/texture it?

Share on other sites

You can use indexed geometry. It's just that the definition of a vertex is not "a unique point in space", but is "a unique tuple of attributes". If any of the attributes is different (position, normal, UV), then that's a different vertex.

So when loading an obj file, you first need to de-index the data (because they use one index per attribute, not one index per vertex), and then re-index the data using the above definition of a vertex.

When drawing an indexed cube, each face has 4 vertices, used by two triangles, stored as 6 indices.
When drawing a non-indexed cube, each face has 6 vertices, which is wasteful.

Share on other sites
36 minutes ago, Hodgman said:

You can use indexed geometry. It's just that the definition of a vertex is not "a unique﻿ point in space",﻿ but is "a unique tuple of attributes". If any of the attributes is different (position, normal, UV), then that's a different vertex.﻿﻿

﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿ So when loading an obj file, you first﻿ need to de-index the data﻿ (because they use﻿ one index per ﻿attribute, not one index per v﻿ertex), and﻿﻿ ﻿then re-index t﻿he data using the above﻿ definition of a vert﻿ex.﻿﻿

﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿﻿ When drawing an indexed cube, each face has 4 vertices, used by two triangles, stored as 6 indices.
When drawing a non-indexed cube, each﻿ face has 6 vertices, which is wasteful.

But doesn't that means I still need 36 vertices to represent every possible "tuple" of attributes? Or am I just misunderstanding what you mean when you say "de-index"? The way I understand it now is that I have to generate a unique vertex from the index table of the obj file, or do you mean something different? By the way this is the index table given to me by the library...

Share on other sites
On 8/9/2019 at 11:20 AM, VanillaSnake21 said:

But doesn't that means I still need 36 vertices to represent every possible﻿ "tuple" of attributes﻿?﻿﻿ Or am I just misunderstanding what you mean when you say "de-index"? The way I understand it now is that I have to generate a unique vertex from the index table of the obj file, or do you mean something different? By the way this is the index table given to me by the library...

If you de-index them (get rid of the indices from the file and create unique tuples of values), then yes, you end up with 36 vertices...
But, in your array, item[0] and item[3] are identical. Within those 36 vertices, there's actually only 24 unique ones (4 for each face of the cube).

Instead of de-indexing (turning the OBJ indices into a vertex attribute structre) and then reindexing them (finding the unique vertex structures), you can do this in one step with something like:

//The index data from your OBJ file:
struct ObjIndices { int vertex_index, normal_index, texcoord_index; };

//we're going to find the unique set of ObjIndices and map them to a NEW singular index value
map<ObjIndices, int> reindexer;

vector<int> newIndexBuffer; // this will be the new index buffer data that we send to the GPU

//little routine checks if this ObjIndices struct has been seen before, and if not, assigns it a new index value
int GetNewIndex(objIndices)
if not reindexer.contains(objIndices) then
reindexer[objIndices] = reindexer.size()
return reindexer[objIndices]

for each indices as o
newIndexBuffer.push_back( GetNewIndex(o) )

//After this, newIndexBuffer should contain 36 entries (6 faces * 2 triangles * 3 corners per tri)
//  and reindexer should contain just 24 entries (6 faces * 4 unique corners)

//now you can read the unique values from the 'reindexer' map, and use them to build your vertex buffer that will be sent to the GPU
struct Vertex { float3 vertex; float3 normal; float2 texcoord; }
vector<Vertex> newVertexBuffer
newVertexBuffer.resize( reindexer.size() )
for each reindexer as objIndices, index
newVertexBuffer[index] = Vertex{
obj.vertex_values[objIndices.vertex_index],
obj.normal_values[objIndices.normal_index],
obj.texcoord_values[objIndices.texcoord_index],
}

Share on other sites
Posted (edited)
6 hours ago, Hodgman said:

If you de-index them (get rid of the indices from the file and create unique tuples of values), then yes, you end up with 36 vertices...
But, in your array, item[﻿0] and item[3] are identical. Within those 36 vertices, there's actually only 24 unique ones (4 for each face of the cube).

﻿﻿﻿﻿ Instead of de-indexing (turning the OBJ indices into﻿﻿ ﻿a vertex attribute structre) and then reindexing them (finding the unique vertex structures), you can do this in one step with something like:

It took me a while, but I think I managed to write something based on your pseudocode. I'm not sure if I followed it exactly but the code is working and the texture issue is fixed. Really appreciate it!

By the way this is the final code if you care to see, I'm not sure if I did more work than I had to, but I had to write a comparator function for the map and use a reverse map to get the final vertex list

class index_comparator
{
public:
bool operator()(const tinyobj::index_t& lhv, const tinyobj::index_t& rhv) const
{
return std::tie(lhv.vertex_index, lhv.normal_index, lhv.texcoord_index) < std::tie(rhv.vertex_index, rhv.normal_index, rhv.texcoord_index);
}
};

std::map<tinyobj::index_t, int, index_comparator> uniqueVertexMap;

//go through each index and find unique entries
for (tinyobj::index_t i : shapes[s].mesh.indices)
uniqueVertexMap.insert(std::pair<tinyobj::index_t, int>(i, uniqueVertexMap.size()));

//allocate space for the vertices
obj.vertexList = new Vertex::PosNormTex[uniqueVertexMap.size()];
obj.numVertices = uniqueVertexMap.size();

//copy the vertices in the same order as they are in the map
for ( auto& keyval : uniqueVertexMap)
{
tinyobj::real_t vx = attrib.vertices[3 * keyval.first.vertex_index + 0];
tinyobj::real_t vy = attrib.vertices[3 * keyval.first.vertex_index + 1];
tinyobj::real_t vz = attrib.vertices[3 * keyval.first.vertex_index + 2];

tinyobj::real_t nx = attrib.normals[3 * keyval.first.normal_index + 0];
tinyobj::real_t ny = attrib.normals[3 * keyval.first.normal_index + 1];
tinyobj::real_t nz = attrib.normals[3 * keyval.first.normal_index + 2];

tinyobj::real_t tx = attrib.texcoords[2 * keyval.first.texcoord_index + 0];
tinyobj::real_t ty = attrib.texcoords[2 * keyval.first.texcoord_index + 1];

// Optional: vertex colors
// tinyobj::real_t red = attrib.colors[3*idx.vertex_index+0];
// tinyobj::real_t green = attrib.colors[3*idx.vertex_index+1];
// tinyobj::real_t blue = attrib.colors[3*idx.vertex_index+2];

//
// per-face material
//shapes[s].mesh.material_ids[f];

Vertex::PosNormTex vert;

vert.pos.x = vx;
vert.pos.y = vy;
vert.pos.z = vz;

vert.norm.x = nx;
vert.norm.y = ny;
vert.norm.z = nz;

vert.uv.x = tx;
vert.uv.y = ty;

obj.vertexList[keyval.second] = vert;
}

//now re-index the old index list
for (tinyobj::index_t i : shapes[s].mesh.indices)
obj.indexList.push_back(uniqueVertexMap[i]);



Edited by VanillaSnake21

Share on other sites
3 hours ago, VanillaSnake21 said:

It took me a while, but I think I managed to write something based on your pseudocode. I'm not sure if I followed it exactly but the code is working and the texture issue is fixed. Really appreciate it!

By the way this is the final code if you care to see, I'm not sure if I did more work than I had to, but I had to write a comparator function for the map and use a reverse map to get the final vertex list


class index_comparator
{
public:
bool operator()(const tinyobj::index_t& lhv, const tinyobj::index_t& rhv) const
{
return std::tie(lhv.vertex_index, lhv.normal_index, lhv.texcoord_index) < std::tie(rhv.vertex_index, rhv.normal_index, rhv.texcoord_index);
}
};

std::map<tinyobj::index_t, int, index_comparator> uniqueVertexMap;

//go through each index and find unique entries
for (tinyobj::index_t i : shapes[s].mesh.indices)
uniqueVertexMap.insert(std::pair<tinyobj::index_t, int>(i, uniqueVertexMap.size()));

//allocate space for the vertices
obj.vertexList = new Vertex::PosNormTex[uniqueVertexMap.size()];
obj.numVertices = uniqueVertexMap.size();

//copy the vertices in the same order as they are in the map
for ( auto& keyval : uniqueVertexMap)
{
tinyobj::real_t vx = attrib.vertices[3 * keyval.first.vertex_index + 0];
tinyobj::real_t vy = attrib.vertices[3 * keyval.first.vertex_index + 1];
tinyobj::real_t vz = attrib.vertices[3 * keyval.first.vertex_index + 2];

tinyobj::real_t nx = attrib.normals[3 * keyval.first.normal_index + 0];
tinyobj::real_t ny = attrib.normals[3 * keyval.first.normal_index + 1];
tinyobj::real_t nz = attrib.normals[3 * keyval.first.normal_index + 2];

tinyobj::real_t tx = attrib.texcoords[2 * keyval.first.texcoord_index + 0];
tinyobj::real_t ty = attrib.texcoords[2 * keyval.first.texcoord_index + 1];

// Optional: vertex colors
// tinyobj::real_t red = attrib.colors[3*idx.vertex_index+0];
// tinyobj::real_t green = attrib.colors[3*idx.vertex_index+1];
// tinyobj::real_t blue = attrib.colors[3*idx.vertex_index+2];

//
// per-face material
//shapes[s].mesh.material_ids[f];

Vertex::PosNormTex vert;

vert.pos.x = vx;
vert.pos.y = vy;
vert.pos.z = vz;

vert.norm.x = nx;
vert.norm.y = ny;
vert.norm.z = nz;

vert.uv.x = tx;
vert.uv.y = ty;

obj.vertexList[keyval.second] = vert;
}

//now re-index the old index list
for (tinyobj::index_t i : shapes[s].mesh.indices)
obj.indexList.push_back(uniqueVertexMap[i]);



Thanks for this

• Game Developer Survey

We are looking for qualified game developers to participate in a 10-minute online survey. Qualified participants will be offered a \$15 incentive for your time and insights. Click here to start!

• 12
• 14
• 10
• 14
• 24