-1

I can't seem to render an OBJ model using glDrawElements in C++.

Here is my code:

#include <iostream>
#include <stdio.h>
#include <vector>
#include <fstream>
#include "Object.h"

#define BUFFER_OFFSET(i) ((char *)NULL + (i))


class Model : public object
{
private:
    struct  Vertex
    {
        CBfloat v1, v2, v3;
        CBfloat vn1, vn2, vn3;
        CBfloat vt1, vt2;

        Vertex(vec3 _position, vec3 _normal, vec2 _texCoord)
        {
            v1 = _position[0];
            v2 = _position[1];
            v3 = _position[2];
            vn1 = _normal[0];
            vn2 = _normal[1];
            vn3 = _normal[2];
            vt1 = _texCoord[0];
            vt2 = _texCoord[1];
        }
    };

    struct  Face
    {
        CBuint v[3];
        CBuint vt[3];
        CBuint vn[3];

        CBuint mat_id;

        Face(CBuint v1, CBuint vt1, CBuint vn1, CBuint v2, CBuint vt2, CBuint vn2, CBuint v3, CBuint vt3, CBuint vn3) : mat_id(-1)
        {
            v[0] = v1;
            vt[0] = vt1;
            vn[0] = vn1;
            v[1] = v2;
            vt[1] = vt2;
            vn[1] = vn2;
            v[2] = v3;
            vt[2] = vt3;
            vn[2] = vn3;
        }
    };

    struct  Mesh
    {
        CBuint vbo;
        CBuint ebo;

        Mesh() {}
        Mesh(std::vector <Vertex>& v, std::vector <CBuint>& i)
        {
            // Generate
            glGenBuffers(1, &vbo);
            glGenBuffers(1, &ebo);

            // Bind
            glBindBuffer(GL_ARRAY_BUFFER, vbo);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

            // Buffer
            glBufferData(GL_ARRAY_BUFFER, v.size() * sizeof(Vertex), &v[0], GL_STATIC_DRAW);
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(CBuint) * 3, &i[0], GL_STATIC_DRAW);

            // Set attribs
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(0));
            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(sizeof(v) * 3));
            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(sizeof(v) * 5));

            // Enable attribs
            glEnableVertexAttribArray(0);
            glEnableVertexAttribArray(1);
            glEnableVertexAttribArray(2);

            // Clean up
            glBindVertexArray(0);
        }
        ~Mesh()
        {
            glDeleteBuffers(1, &vbo);
            glDeleteBuffers(1, &ebo);
        }
    };

    // Local variables
    CBuint                      _vao;
    bool                        _materialised;
    std::string                 _obj_url;
    std::string                 _mtl_url;
    std::vector <std::string>   _material_element;
    std::vector <Vertex>        _vertices;
    std::vector <CBuint>        _indices;
    std::vector <Mesh*>         _meshes;
    std::vector <Material*>     _materials;

public:
    Model(Shader* shader) : _materialised(false) {}
    Model(Shader* shader, const char* obj_file) : _materialised(false)
    {
        // Load OBJ file
        if (loadOBJ(obj_file) != 1)
            std::cout << "Error: Failed to load OBJ!" << std::endl;

        // Load MTL file
        if (_materialised)
        {
            if (loadMTL(shader, _mtl_url.c_str()) != 1)
                std::cout << "Error: Failed to load MTL file!" << std::endl;
        }

        // Apply material if none were applied
        if (!_materialised)
        {
            _materials.push_back(new Material(shader, "default_m",false, false, 1.0f, vec3(1.0f, 1.0f, 1.0f), vec3(0.25f, 0.25f, 0.25f), 1.0f, 8.0f, 1.0f));
            _materialised = true;
        }

        // Initialise matrix
        initialiseMatrix(shader);
    }

    ~Model()
    {
        if (!_indices.empty())
            clear();

        glDeleteVertexArrays(1, &_vao);
    }

    inline CBvoid clear()
    {
        _vertices.clear();
        _indices.clear();
        _materials.clear();
        _meshes.clear();
    }

    inline CBint loadOBJ(const char* file)
    {
        this->_obj_url = file;

        // Check for valid file
        std::ifstream _in(file);
        if (!_in)
        {
            std::cout << "Error: OBJ file is invalid!" << std::endl;
            return -1;
        }
        else if (!_in.is_open())
        {
            std::cout << "Error: Cannot open OBJ file!" << std::endl;
            return -1;
        }

        // Get data
        char data[256];
        std::vector <std::string> line;
        while (_in.getline(data, 256))
            line.push_back(data);

        // Assign vertex data
        std::vector <vec3>  _v;
        std::vector <vec3>  _vn;
        std::vector <vec2>  _vt;
        std::vector <Face>  _f;
        for (unsigned int i = 0; i < line.size(); i++)
        {
            switch ((line[i])[0])
            {
            case '#':
                continue;
            case '\0':
                continue;
            case 'm':
                char mtl_url[128];
                sscanf_s((&line[i])[0].c_str(), "mtllib %s", mtl_url, sizeof(mtl_url));
                _mtl_url = mtl_url;
                _materialised = true;
                break;
            case 'v':
                float x, y, z;
                if ((line[i])[1] == 'n')
                {
                    sscanf_s((&line[i])[0].c_str(), "vn %f %f %f", &x, &y, &z);
                    _vn.push_back(vec3(x, y, z));
                }
                if ((line[i])[1] == 't')
                {
                    sscanf_s((&line[i])[0].c_str(), "vt %f %f", &x, &y);
                    _vt.push_back(vec2(x, y));
                }
                else if ((line[i])[1] == ' ')
                {
                    sscanf_s((&line[i])[0].c_str(), "v %f %f %f", &x, &y, &z);
                    _v.push_back(vec3(x, y, z));
                }
                break;
            case 'u':
                char material_element[128];
                sscanf_s((&line[i])[0].c_str(), "usemtl %s", &material_element, sizeof(material_element));

                // Assign new material to element
                if (_material_element.size() > 1)
                    _f[_f.size() - 1].mat_id = _material_element.size();

                _material_element.push_back(material_element);
                break;
            case 'f':
                CBuint v_i[3];
                CBuint vn_i[3];
                CBuint vt_i[3];

                sscanf_s((&line[i])[0].c_str(), "f %d/%d/%d %d/%d/%d %d/%d/%d", &v_i[0], &vt_i[0], &vn_i[0], &v_i[1], &vt_i[1], &vn_i[1], &v_i[2], &vt_i[2], &vn_i[2]);

                // Faces
                _f.push_back(Face(v_i[0], vt_i[0], vn_i[0], v_i[1], vt_i[1], vn_i[1], v_i[2], vt_i[2], vn_i[2]));

                // Indices
                _indices.push_back(v_i[0] - 1);
                _indices.push_back(vt_i[0] - 1);
                _indices.push_back(vn_i[0] - 1);

                _indices.push_back(v_i[1] - 1);
                _indices.push_back(vt_i[1] - 1);
                _indices.push_back(vn_i[1] - 1);

                _indices.push_back(v_i[2] - 1);
                _indices.push_back(vt_i[2] - 1);
                _indices.push_back(vn_i[2] - 1);

                break;
            }
        }


        // Optimise vertices
        for (CBuint i = 0; i < _v.size(); i++)
            _vertices.push_back(Vertex(_v[i], vec3(0.0f, 0.0f, 0.0f), vec2(1.0f, 1.0f)));

        // Optimise buffers
        CBuint next_element = 0;
        glGenVertexArrays(1, &_vao);
        glBindVertexArray(_vao);
        if (_materials.size() > 1)
        {
            for (CBuint i = 0; i < _f.size(); i++)
            {
                if (_f[i].mat_id == next_element)
                {
                    _meshes.push_back(new Mesh(_vertices, _indices));
                    next_element++;
                }
            }
        }
        else
            _meshes.push_back(new Mesh(_vertices, _indices));
        glBindVertexArray(0);


        // Output
        std::cout << "v: " << _v.size() << std::endl;
        std::cout << "vn: " << _vn.size() << std::endl;
        std::cout << "vt: " << _vt.size() << std::endl;
        std::cout << "f: " << _f.size() << std::endl;
        std::cout << "vertices: " << _vertices.size() << std::endl;
        std::cout << "indices: " << _indices.size() << std::endl;
        std::cout << "meshes: " << _meshes.size() << std::endl;
        std::cout << "materials: " << _materials.size() << std::endl;

        // Close file and return
        _in.close();
        return 1;
    }

    inline CBint loadMTL(Shader* shader, const char* file)
    {
        // ----------------- MTL pipeline -----------------
        // ----------------------------------------------------
        // Ns - specular exponent multiplied by the texture value
        // d  - dissolve multiplied by the texture value
        // Ka - ambient colour multiplied by the texture value
        // Kd - diffuse colour multiplied by the texture value
        // Ks - specular colour multiplied by the texture value
        // Ke - emissive colour multiplied by the texture value
        // map_* - texture map multiplied by the texture value
        // bump - normal map multiplied by the texture value


        // Check file
        std::ifstream in_file(file);
        if (!in_file)
        {
            std::cout << "Error: MTL file is invalid!" << std::endl;
            return -1;
        }
        else if (!in_file.is_open())
        {
            std::cout << "Error: MTL file failed to open!" << std::endl;
            return -1;
        }

        // Push_back materials from elements in obj
        for (unsigned int i = 0; i < _material_element.size(); i++)
            _materials.push_back(new Material(shader, _material_element[i], false, false, 1.0f, vec3(1.0f, 1.0f, 1.0f), vec3(0.5f, 0.5f, 0.5f), 0.75f, 8.0f, 1.0f));

        // Get data
        char data[256];
        std::vector <std::string> line;
        while (in_file.getline(data, 256))
            line.push_back(data);

        // Assign data
        CBint  current_material_element = -1;
        for (unsigned int i = 0; i < line.size(); i++)
        {
            switch ((line[i])[0])
            {
            case '#':
                continue;
            case '\0':
                continue;
            case 'n':
                current_material_element++;
                break;
            case '\t':
                if ((line[i])[1] == 'N' && (line[i])[2] == 's')
                {
                    float _Ns = 0.0f;
                    sscanf_s((&line[i])[0].c_str(), "\tNs %f", &_Ns);
                    _materials[current_material_element]->_specular_damper = _Ns;
                }
                else if ((line[i])[1] == 'd')
                {
                    float _d = 0.0f;
                    sscanf_s((&line[i])[0].c_str(), "\td %f", &_d);
                    _materials[current_material_element]->_opacity = _d;
                }
                else if ((line[i])[1] == 'K' && (line[i])[2] == 'd')
                {
                    vec3 _Kd = vec3(0.0f);
                    sscanf_s((&line[i])[0].c_str(), "\tKd %f %f %f", &_Kd.data[0], &_Kd.data[1], &_Kd.data[2]);
                    _materials[current_material_element]->_diffuse = _Kd;
                }
                else if ((line[i])[1] == 'K' && (line[i])[2] == 's')
                {
                    vec3 _Ks = vec3(0.0f);
                    sscanf_s((&line[i])[0].c_str(), "\tKs %f %f %f", &_Ks.data[0], &_Ks.data[1], &_Ks.data[2]);
                    _materials[current_material_element]->_specular = _Ks;
                }
                break;
            }
        }

        return 1;
    }

    virtual void update(double delta)
    {
        tickMatrix();

        // TEMP!
        _rotation = vec4(20.0f, 0.0f, 1.0f, 0.0f);
    }

    virtual void render()
    {
        glUniformMatrix4fv(_u_model, 1, GL_FALSE, _model);

        _materials[0]->bind();

        glBindVertexArray(_vao);
        glDrawElements(GL_TRIANGLES, _indices.size(), GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
    }
};

I can render a simple triangle when hard-coding the vertices and indices, so it can't be a shader problem. I've thoroughly checked through the index and vertex optimisation part and can't see anything wrong with it, even after comparing with other examples.

Any ideas, corrections, solutions or whatnot?

Edit: The cube model renders but now I've come across a new problem - the indices seem to be scrambled with double cross-overs when rendering the triangles:

Click me for screenshot!

And here is the same cube.obj model but in lit mode:

Click to see in lit mode!

Vertex shader code:

#version 330 core

// Vertex attributes
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 texCoord;

// Node parses
out vec2 TexCoord;
out vec3 Normal;
out vec3 Fragpos;

// Uniforms
uniform mat4 model;
uniform mat4 projection;
uniform mat4 view;

// Vertex loop
void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0f);   
    Normal =  mat3(transpose(inverse(model))) * normal;
    Fragpos = vec3(model * vec4(position, 1.0f));
    TexCoord = texCoord;
}

Fragment shader code:

#version 330 core


// Node parses
out vec4 color;
in vec2 TexCoord;
in vec3 Normal;
in vec3 Fragpos;

// Camera data
uniform vec3    view_loc;
vec3            view_dir;

// Model data
vec3    normal;

// Phong data
vec3    _ambient;
vec3    _diffuse;
vec3    _specular;
vec3    _emissive;
vec3    _result;

// Material uniforms
uniform struct MATERIAL
{
    bool    masked;
    bool    additive;
    float   opacity;
    float   specular_damper;
    float   specular_intensity;
    float   emissive_intensity;
    vec3    diffuse;
    vec3    specular;   
} _mat;
uniform sampler2D texture[3];

// Global uniforms
uniform struct GLOBAL
{
    float   ambient_coefficient;
    vec3    ambient_colour;
} _global;

// Directional uniforms
uniform struct DIRECTIONAL
{
    float   intensity;
    vec3    position;
    vec3    colour;
} _dir;
vec3 d_view_dir;

// Directional light
vec3 directional()
{
    vec3 r;

    d_view_dir = normalize(-_dir.position);
    float src = max(dot(normal, d_view_dir),0.0);

    r = (src * _dir.colour) * _dir.intensity;

    return r;
}

// Final gather
vec4 finalGather()
{
    normal = normalize(Normal);
    view_dir = normalize(view_loc - Fragpos);

    // Ambient
    _ambient = _global.ambient_coefficient * _mat.diffuse * _dir.intensity;

    // Diffuse
    _diffuse = _mat.diffuse * directional();

    // Specular 
    vec3 reflectDir = reflect(-d_view_dir, normal);
    float power = pow(max(dot(view_dir, reflectDir), 0.0), _mat.specular_damper);
    _specular = (_mat.specular) * power * _dir.colour * _dir.intensity;

    // Emissive


    // Final result
    _result = (_ambient + _diffuse + _specular);

    //_diffuse;
    return  vec4(_result, _mat.opacity);
}

// Main loop
void main(void)
{
    color = vec4(finalGather());
}

Could it be a buffer offset problem? Thanks for the help guys!

DrStrange
  • 94
  • 1
  • 9
  • Did you forget about the vertex normals? That is not the current problem you have, but will probably be your next problem. – BitTickler Oct 02 '16 at 15:17
  • vertex normals? What do you mean exactly? – DrStrange Oct 02 '16 at 15:38
  • Bearing in mind I'm using values (0.0f, 0.0f, 0.0f) for now. Culling is disabled. – DrStrange Oct 02 '16 at 16:30
  • 1
    Can you simplify this code? Remove all the texture, material, etc. stuff that does not appear to be in play for your question. At least I hope you're not trying to use the code where you have multiple materials, because that doesn't look like it could possibly work. From quickly scanning through the code, there are a couple of obvious errors, but it would take a lot of time to find all possible problems in this much code. You will probably spot those errors yourself if you read through your code, and remove the parts you're not using yet. – Reto Koradi Oct 02 '16 at 16:48
  • I haven't yet optimised for multiple materials. Thank you, I've simplified the code ^ – DrStrange Oct 02 '16 at 17:08
  • The vectors, named ``VN`` in your ``.obj`` files, if I am not mistaken are vertex normals. Without them, the orientation of your face is rather random-ish, because the order of the vertex indices you give is not the same for all face orientations. Think of a sphere and a triangle on its surface and the triangle on the other side of the sphere. If the vertices are enumerated in the same sequence, one face will be correct, the other shows its "inside". – BitTickler Oct 02 '16 at 17:26
  • Ah yes, but again I have cull-facing disabled. Plus if you look in the second to last for loop inside the "loadOBJ" function, I'm push_backing the normals to (0.0f, 0.0f, 0.0f) anyway so it should still show up :/ – DrStrange Oct 02 '16 at 17:27
  • Ah yes - I must have missed those lines while skimming over the code.. - nevermind :) – BitTickler Oct 02 '16 at 17:30
  • No worries :) Thanks anyway! – DrStrange Oct 02 '16 at 17:31
  • What's with the down-vote? – DrStrange Oct 02 '16 at 19:10

1 Answers1

0

You posted a lot of code, so there could potentially be more errors than the ones pointed out below. Also, even once you load and render your object correctly, it can still not show up for various reasons, e.g. because the coordinates are outside the view volume.

From a quick look, I saw two errors in the code where you load the geometry into buffers, and set up the vertex attributes:

glBufferData(GL_ARRAY_BUFFER, v.size() * sizeof(Vertex), &v[0], GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(CBuint) * 3, &i[0], GL_STATIC_DRAW);

In the second call, you're only loading 3 indices into the index buffer. You have to calculate the size of all indices, based on the size of the corresponding vector, just like you did for the first call:

glBufferData(GL_ELEMENT_ARRAY_BUFFER, i.size() * sizeof(CBuint), &i[0], GL_STATIC_DRAW);

The offsets in the vertex setup are also incorrect:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(0));
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(sizeof(v) * 3));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(sizeof(v) * 5));

v is a vector, so sizeof(v) is the size of the vector object, which is irrelevant here. You need the offset of the fields within the vertex object:

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(sizeof(CBfloat) * 3));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), BUFFER_OFFSET(sizeof(CBfloat) * 6));
Reto Koradi
  • 53,228
  • 8
  • 93
  • 133
  • Excellent! That solved the rendering problem! :D Many thanks to you sir!! Any ideas why the triangles are crossing over each other though? It could possibly be to do with the shader code. I've just posted a screenshot above, thanks again! – DrStrange Oct 02 '16 at 22:01
  • See it's weird because sometimes the amount of normals or/ and texture coordinates can differ in relation to positions (v, vt & vn) in an .obj file, so how would I fit the exact amount in the vector list of vertices? – DrStrange Oct 02 '16 at 22:15
  • See http://stackoverflow.com/questions/23349080/opengl-index-buffers-difficulties about how to deal with normal and texture coordinates. – Reto Koradi Oct 03 '16 at 01:06