DrStrange DrStrange - 2 months ago 14
C++ Question

Wavefront OBJ Model Not Rendering - OpenGL C++

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!

Answer

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));