1
\$\begingroup\$

I'm currently doing simple 3D drawing in OpenGL but I have hit a roadblock I just can't seem to get around. The problem is happening with trying to load .obj files. I've written my own .obj reader.

I am using SDL2 and GLEW. I use SOIL for loading textures.

When loading in a texture on a model, the texture on "front" and "back" are always correct, like this: Correct

But on the other sides, it's completely warped, like this: Incorrect

Now I think it has something to do with the texture coordinates, but I have no clue what. I've tried multiple .obj files, even simple cubes. All experience the same weird glitch.

Model loader

ModelData* ObjFormatLoader::loadModel(std::string t_filename)
{
    std::string fileAdress = "..//Assets//Models//" + t_filename + ".obj";

    Logger::GetInstance()->logAction("loading .obj file " + t_filename);
    if(!std::experimental::filesystem::exists(fileAdress))
    {
        Logger::GetInstance()->logError("No such file exists.", __LINE__, __FILE__);
        return nullptr;
    }

    std::ifstream model(fileAdress);

    std::vector<glm::fvec3> vertices = std::vector<glm::fvec3>();
    std::vector<glm::fvec2> textures = std::vector<glm::fvec2>();
    std::vector<glm::fvec3> normals = std::vector<glm::fvec3>();

    std::vector<GLuint> indices;

    std::vector<GLfloat> verticesArray = std::vector<GLfloat>();
    std::vector<GLfloat> normalsArray = std::vector<GLfloat>();
    std::vector<GLfloat> textureArray = std::vector<GLfloat>();

    std::string line;
    while (getline(model, line))
    {
        std::vector<std::string> currentLine = splitStringDel(line, ' ');
        if (line.find("v ") == 0)
        {
            vertices.push_back(glm::fvec3(atof(currentLine.at(1).c_str()), atof(currentLine.at(2).c_str()), atof(currentLine.at(3).c_str())));
        }
        else if (line.find("vt ") == 0)
        {
            textures.push_back(glm::fvec2(atof(currentLine.at(1).c_str()), atof(currentLine.at(2).c_str())));
        }
        else if (line.find("vn ") == 0)
        {
            normals.push_back(glm::fvec3(atof(currentLine.at(1).c_str()), atof(currentLine.at(2).c_str()), atof(currentLine.at(3).c_str())));
        }
        else if (line.find("f ") == 0)
        {
            textureArray.resize(vertices.size() * 2);
            normalsArray.resize(vertices.size() * 3);
            break;
        }
    }

    model.clear();
    model.seekg(0, std::ios::beg);
    while (getline(model, line))
    {
        if (line.find("f ") == 0)
        {
            std::vector<std::string> currentLine = splitStringDel(line, ' ');
            std::vector<std::string> vertex1 = splitStringDel(currentLine.at(1), '/');
            std::vector<std::string> vertex2 = splitStringDel(currentLine.at(2), '/');
            std::vector<std::string> vertex3 = splitStringDel(currentLine.at(3), '/');

            processVertex(vertex1, indices, textures, normals, textureArray, normalsArray);
            processVertex(vertex2, indices, textures, normals, textureArray, normalsArray);
            processVertex(vertex3, indices, textures, normals, textureArray, normalsArray);
        }
    }

    model.close();

    verticesArray.resize(vertices.size() * 3);

    int vertexPointer = 0;
    for (glm::fvec3 Vertex : vertices)
    {
        verticesArray[vertexPointer++] = Vertex.x;
        verticesArray[vertexPointer++] = Vertex.y;
        verticesArray[vertexPointer++] = Vertex.z;
    }

    return ModelManager::GetInstance()->loadModelToVao(verticesArray, indices, textureArray);
}

void ObjFormatLoader::processVertex(const std::vector<std::string> &t_vertex_data, std::vector<GLuint> &t_indices_data, const std::vector<glm::vec2> &t_texture_data, const std::vector<glm::vec3> &t_normals_data, std::vector<GLfloat> &t_texture_array, std::vector<GLfloat> &t_normals_data_array)
{
    int currentVertex = atoi(t_vertex_data.at(0).c_str()) - 1;
    t_indices_data.push_back(currentVertex);

    glm::vec2 currentTex = t_texture_data.at(atoi(t_vertex_data.at(1).c_str()) - 1);
    t_texture_array[currentVertex * 2] = currentTex.x;
    t_texture_array[currentVertex * 2 + 1] = 1.0f - currentTex.y;

    glm::vec3 currentNormal = t_normals_data.at(atoi(t_vertex_data.at(2).c_str()) - 1);
    t_normals_data_array[currentVertex * 3] = currentNormal.x;
    t_normals_data_array[currentVertex * 3 + 1] = currentNormal.y;
    t_normals_data_array[currentVertex * 3 + 2] = currentNormal.z;
}

std::vector<std::string> ObjFormatLoader::splitStringDel(std::string const& original, char separator)
{
    std::vector<std::string> results;
    std::string::const_iterator start = original.begin();
    std::string::const_iterator end = original.end();
    std::string::const_iterator next = std::find(start, end, separator);
    while (next != end) {
        results.push_back(std::string(start, next));
        start = next + 1;
        next = std::find(start, end, separator);
    }
    results.push_back(std::string(start, next));
    return results;
}

Drawing

void RenderManager::renderEntity(BaseEntity * t_entity, StandardShader* t_shader)
{
    auto model = t_entity->getModel();
    auto modelData = model->getModelData();
    auto textureData = model->getTextureData();

    glBindVertexArray(modelData->getVaoId());
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    auto transformationMatrix = MathHelper::createTransformationMatrix(
        t_entity->getPosition(), 
        t_entity->getRotation(), 
        t_entity->getScale()
    );
    t_shader->loadTransformationMatrix(transformationMatrix);
    t_shader->loadProjectionMatrix(MathHelper::createProjectionMatrix());
    t_shader->loadViewMatrix(MathHelper::createViewMatrix(WindowManager::GetInstance()->getCamera()));

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureData->getTextureId());
    glDrawElements(GL_TRIANGLES, modelData->getVertexCount(), GL_UNSIGNED_INT, BUFFER_OFFSET(0));
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glBindVertexArray(0);
}

Texture loading

TextureData* ModelManager::loadTexture(std::string t_filename)
{
    GLint texture;
    std::string FileName = "..\\Assets\\Textures\\" + t_filename;
    texture = SOIL_load_OGL_texture
    (
        FileName.c_str(),
        SOIL_LOAD_AUTO,
        SOIL_CREATE_NEW_ID,
        SOIL_FLAG_MIPMAPS | SOIL_FLAG_TEXTURE_REPEATS
    );

    m_texture_vector.push_back(texture);
    return new TextureData(texture);
}

Fragment shader

#version 400 core

in vec2 pass_textureCoords;

out vec4 out_Color;

uniform sampler2D tex0;

void main()
{
    out_Color = texture(tex0, pass_textureCoords);
}

Vertex shader

#version 400 core

in vec3 position;
in vec2 textureCoords;

out vec2 pass_textureCoords;

uniform mat4 projectionMatrix;
uniform mat4 transformationMatrix;
uniform mat4 viewMatrix;

void main()
{
  vec4 worldPosition = transformationMatrix * vec4(position, 1.0);
  gl_Position = projectionMatrix * viewMatrix * worldPosition;
  pass_textureCoords = textureCoords;
}

Cube.obj

# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 02.05.2019 21:59:07

#
# object Box002
#

v -2.5000 -2.5000 0.0000
v -2.5000 2.5000 0.0000
v 2.5000 2.5000 0.0000
v 2.5000 -2.5000 0.0000
v -2.5000 -2.5000 5.0000
v 2.5000 -2.5000 5.0000
v 2.5000 2.5000 5.0000
v -2.5000 2.5000 5.0000
# 8 vertices

vn 0.0000 0.0000 -1.0000
vn 0.0000 0.0000 1.0000
vn 0.0000 -1.0000 0.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 1.0000 0.0000
vn -1.0000 0.0000 0.0000
# 6 vertex normals

vt 0.6677 0.3323 0.0000
vt 0.6677 0.0005 0.0000
vt 0.9995 0.0005 0.0000
vt 0.9995 0.3323 0.0000
vt 0.3341 0.0005 0.0000
vt 0.6659 0.0005 0.0000
vt 0.6659 0.3323 0.0000
vt 0.3341 0.3323 0.0000
vt 0.0005 0.6677 0.0000
vt 0.3323 0.6677 0.0000
vt 0.3323 0.9995 0.0000
vt 0.0005 0.9995 0.0000
vt 0.3341 0.3422 0.0000
vt 0.6659 0.3422 0.0000
vt 0.6659 0.6740 0.0000
vt 0.3341 0.6740 0.0000
vt 0.0005 0.3341 0.0000
vt 0.3323 0.3341 0.0000
vt 0.3323 0.6659 0.0000
vt 0.0005 0.6659 0.0000
vt 0.0005 0.0005 0.0000
vt 0.3323 0.0005 0.0000
vt 0.3323 0.3323 0.0000
vt 0.0005 0.3323 0.0000
# 24 texture coords

o Box002
g Box002
s 2
f 1/1/1 2/2/1 3/3/1 
f 3/3/1 4/4/1 1/1/1 
s 4
f 5/5/2 6/6/2 7/7/2 
f 7/7/2 8/8/2 5/5/2 
s 8
f 1/9/3 4/10/3 6/11/3 
f 6/11/3 5/12/3 1/9/3 
s 16
f 4/13/4 3/14/4 7/15/4 
f 7/15/4 6/16/4 4/13/4 
s 32
f 3/17/5 2/18/5 8/19/5 
f 8/19/5 7/20/5 3/17/5 
s 64
f 2/21/6 1/22/6 5/23/6 
f 5/23/6 8/24/6 2/21/6 
# 12 faces

If any other code is needed, please let me know.

\$\endgroup\$
3
\$\begingroup\$

This looks like it could be an export issue.

Some 3d modelling programs allow multiple uv co-ordinates per vertex. So if this is the case, you can map the three faces adjacent to that vertex with up to 3 different mappings.

When you export to obj, you only get one uv co-ordinate per vertex. This is why some faces look wrong, because they are using a different uv than was intended.

The only solution that I know of is to split the faces before you export. So instead of a cube having 8 vertices, you would have 4 independent vertices per face, 24 in total. This allows completely independent uv mapping per face.

\$\endgroup\$
  • \$\begingroup\$ I have added an obj I used to test as an example. Maybe you can spot any irregularities? \$\endgroup\$ – GigaNova May 3 at 9:43
  • \$\begingroup\$ Yes, I can see that you have 8 vertices in your obj file. Since you have 24 texture co-ordinates, your model loader will either be duplicating vertices (looks like this is not happening) or overwriting texture co-ordinates on the vertices, which will lead to the warping problem. \$\endgroup\$ – Jay May 7 at 1:11
1
\$\begingroup\$

Each face has different texture coordinates for its 4 vertices. However, your OBJ loading code is picking one texture coordinate and using that every time the same vertex position is used.

Your cube.obj file has these faces in it (among many others):

f 1/1/1 2/2/1 3/3/1 
f 1/9/3 4/10/3 6/11/3

Let's just look at the first vertex in each face. The first vertex in the first face is position 1, texcoord 1, normal 1. The first vertex in the second face is position 1, texcoord 9, normal 3.

This is your code for adding a vertex to the model:

int currentVertex = atoi(t_vertex_data.at(0).c_str()) - 1;
t_indices_data.push_back(currentVertex);


glm::vec2 currentTex = t_texture_data.at(atoi(t_vertex_data.at(1).c_str()) - 1);
t_texture_array[currentVertex * 2] = currentTex.x;
t_texture_array[currentVertex * 2 + 1] = 1.0f - currentTex.y;

glm::vec3 currentNormal = t_normals_data.at(atoi(t_vertex_data.at(2).c_str()) - 1);
t_normals_data_array[currentVertex * 3] = currentNormal.x;
t_normals_data_array[currentVertex * 3 + 1] = currentNormal.y;
t_normals_data_array[currentVertex * 3 + 2] = currentNormal.z;

So you see the first vertex (1/1/1) and you add index 0 to the indices list, then you set the texcoord for index 0 to texcoord 0, and you set the normal for index 0 to normal 0.

Then when you see the other vertex (1/9/3): you add index 0 to the indices list, then you set the texcoord for index 0 to texcoord 8, and you set the normal for index 0 to normal 2. So the first vertex will also use this texcoord and this normal!

That's why your texture coordinates are messed up - because you're mixing up texture coordinates from different faces. (You also do this with normals, but I'm guessing you aren't using the normals anyway)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.