PDA

View Full Version : Strange problem when drawing wavefront OBJ



Ruggero Visitnin
07-20-2014, 12:09 PM
hi,
i'm new in the forum so i hope i'm not making mistakes posting this help request.

i wrote some code to load an OBJ file into my program but the result is very strange, here it is:

1371

and this is my code

main.cpp


#include <GL\glew.h>
#include <GL\freeglut.h>
#include <iostream>

#include "ShaderManager.h"
#include "ContentManager.h"
#include "DebugManager.h"

#pragma comment (lib, "glew32.lib")

const int WIDTH = 800;
const int HEIGHT = 600;
const char TITLE[] = "Spark Engine";

GLuint program;
ShaderManager shader;
DebugManager debug;
ContentManager content;

void init()
{

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-2.0, 2.0, -2.0, 2.0, -2.0, 2.0);
debug.openGlInfoLog();
program = shader.loadFromStrings(); //load vertex and fragment shader from strings
debug.modelInfoLog("../cube.obj"); // print information about model that you want to load
content.load("../cube.obj");
}


void display()
{
// background color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1, 0, 0, 1);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

content.draw();
//glUseProgram(program);
glDrawArrays(GL_TRIANGLES, 0, 3);

//glRotatef(1.0, 0.0, 360.0, 0.0);

glutSwapBuffers();
}


void reshape(GLsizei width, GLsizei height)
{
}

void close()
{
//exit(1);
}

int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
glutInitContextVersion(4, 0);
glutInitContextFlags(GLUT_CORE_PROFILE | GLUT_DEBUG);
glutInitContextProfile(GLUT_FORWARD_COMPATIBLE);

glutInitWindowSize(WIDTH, HEIGHT);
glutCreateWindow(TITLE);

GLenum status = glewInit();

if (status != GLEW_OK)
{
std::cerr << "Glew Initialization failed: " << status << std::endl;
exit(1);
}

init();
glutDisplayFunc(display);
glutIdleFunc(display);
glutReshapeFunc(reshape);
glutCloseFunc(close);
glutMainLoop();

return 0;
}

contentManager.h

#ifndef CONTENTMANAGER_H
#define CONTENTMANAGER_H

#include <string>
#include <vector>

typedef struct Model
{
int vertices;
int texels;
int normals;
int faces;

}Model;

typedef struct
{
float x;
float y;
float z;

}Vertex;

typedef struct
{
float x;
float y;
float z;

}Normal;

typedef struct
{
int vx;
int vy;
int vz;

int vnx;
int vny;
int vnz;

}Face;

class ContentManager
{
public:
ContentManager();
~ContentManager();

Model m_load(std::string file_path);

bool load(char *file_path);
void draw();


private:
std::vector <Vertex> vertices;
std::vector <Normal> normals;
std::vector <Face> faces;
};


#endif // CONTENTMANAGER_H

ContentManager.cpp

#include <GL\freeglut.h>
#include <fstream>
#include <iostream>
#include <string>

#include "ContentManager.h"

ContentManager::ContentManager()
{
}


ContentManager::~ContentManager()
{

}

Model ContentManager::m_load(std::string file_path)
{
Model model = { 0 };

std::ifstream file;
file.open(file_path);

if (!file.good())
{
std::cerr << "Error opening obj file" << std::endl;
exit(1);
}

while (!file.eof())
{
std::string line;
getline(file, line);

std::string type = line.substr(0, 2);

if (type.compare("v ") == 0)
model.vertices++;
else if (type.compare("vt") == 0)
model.texels++;
else if (type.compare("vn") == 0)
model.normals++;
else if (type.compare("f ") == 0)
model.faces++;
}

file.close();

return model;
}

bool ContentManager::load(char *file_path)
{
std::string line;
std::ifstream file(file_path);

if (file.fail())
{
std::cerr << "Could not open file " << file_path << " file is illegible or doesn' t exist." << std::endl;
return false;
}

int vertex_count = 0;
int normal_count = 0;
int face_count = 0;

float fill;

while (!file.eof())
{
std::getline(file, line);

if (line.c_str()[0] == 'v') {

if (line.c_str()[1] == ' ')
{
vertices.resize(vertex_count + 1);

sscanf(line.c_str(), "v %f %f %f", &vertices[vertex_count].x, &vertices[vertex_count].y, &vertices[vertex_count].z);
//std::cout << "vertex " << vertices[vertex_count].x << "," << vertices[vertex_count].y << "," << vertices[vertex_count].z << std::endl;
vertex_count += 1;
}
else if (line.c_str()[1] == 'n')
{
normals.resize(normal_count + 1);

sscanf(line.c_str(), "vn %f %f %f", &normals[normal_count].x, &normals[normal_count].y, &normals[normal_count].z);
//std::cout << "normals " << normals[normal_count].x << "," << normals[normal_count].y << "," << normals[normal_count].z << std::endl;
normal_count += 1;
}
}
else if (line.c_str()[0] == 'f')
{
faces.resize(face_count + 1);

sscanf(line.c_str(), "f %d//%d %d//%d %d//%d", &faces[face_count].vx, &faces[face_count].vnx, &faces[face_count].vy, &faces[face_count].vny, &faces[face_count].vz, &faces[face_count].vnz);
//std::cout << "faces " << faces[face_count].vx << "//" << faces[face_count].vnx << "," << faces[face_count].vy << "//" << faces[face_count].vny << "," << faces[face_count].vz << "//" << faces[face_count].vnz << std::endl;
face_count += 1;
}
}

return true;
}

void ContentManager::draw()
{
for (int i = 0; i < faces.size(); i++)
{
glBegin(GL_TRIANGLE_FAN);

glNormal3f(normals[faces[i].vnx - 1].x, normals[faces[i].vnx - 1].y, normals[faces[i].vnx - 1].z);
glVertex3f(vertices[faces[i].vx - 1].x, vertices[faces[i].vx - 1].y, vertices[faces[i].vx - 1].z);

glNormal3f(normals[faces[i].vny - 1].x, normals[faces[i].vny - 1].y, normals[faces[i].vny - 1].z);
glVertex3f(vertices[faces[i].vy - 1].x, vertices[faces[i].vy - 1].y, vertices[faces[i].vy - 1].z);

glNormal3f(normals[faces[i].vnz - 1].x, normals[faces[i].vnz - 1].y, normals[faces[i].vnz - 1].z);
glVertex3f(vertices[faces[i].vz - 1].x, vertices[faces[i].vz - 1].y, vertices[faces[i].vz - 1].z);

glEnd();
}
}

in your opinion why have i this strange result?

MtRoad
07-21-2014, 06:19 PM
It looks like you clear the depth buffer, but remember that depth testing is disabled by default. I'd try changing your code before your draw to:

// background color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1, 0, 0, 1);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST); //<<<<<

In your future programs try to not change state every time you draw if you don't need to. Those glEnable commands would be better put in your init() function since all your code here is using lighting and depth testing.

On a different note, why are you drawing using GL_TRIANGLE_FAN? Most models are created using triangles, quads or triangle strips. If you are using models exported from blender, they are most likely quads or triangles. From the way your .OBJ model loader reads faces, you are only readings faces with 3 vertices (which would be triangles), so you should use GL_TRIANGLES instead. This is likely another source of the problem. Be careful with your model loader because .OBJ also supports quads.

Ruggero Visitnin
07-22-2014, 12:33 AM
It looks like you clear the depth buffer, but remember that depth testing is disabled by default. I'd try changing your code before your draw to:

// background color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(1, 0, 0, 1);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST); //<<<<<

In your future programs try to not change state every time you draw if you don't need to. Those glEnable commands would be better put in your init() function since all your code here is using lighting and depth testing.

On a different note, why are you drawing using GL_TRIANGLE_FAN? Most models are created using triangles, quads or triangle strips. If you are using models exported from blender, they are most likely quads or triangles. From the way your .OBJ model loader reads faces, you are only readings faces with 3 vertices (which would be triangles), so you should use GL_TRIANGLES instead. This is likely another source of the problem. Be careful with your model loader because .OBJ also supports quads.


i'm sorry, i was drawing TRIANGLES_FAN because i was experimenting and i forgot to give you the correct code, for the others notes i made everything as you say so now work perfectly and i put
in the init() function all the functions that don't need to be in the draw() function, thanks a lot

MtRoad
07-22-2014, 05:08 AM
i'm sorry, i was drawing TRIANGLES_FAN because i was experimenting and i forgot to give you the correct code, for the others notes i made everything as you say so now work perfectly and i put
in the init() function all the functions that don't need to be in the draw() function, thanks a lot

You're using a lot of older fixed-function style OpenGL. Here are some tutorials http://www.opengl-tutorial.org/ to help you out. Also, instead of writing your own loader you may want to try a free loader such as FBX (http://www.autodesk.com/products/fbx/overview) by AutoDesk which supports animation (which obj doesnt).
.

Carmine
07-22-2014, 09:32 AM
in your opinion why have i this strange result? I can make some debugging suggestions if you are still interested in taking the same approach. Make a copy of the obj file you are trying to render, and edit it with a text editor. Cut out all of the face (polygon) definitions except for the first 5 or so. Try to render it in wireframe. If that doesn't make things clear, use OpenGL to display the vertex numbers referred to in the 'f' lines. Good luck.

MtRoad
07-22-2014, 01:33 PM
If I'm needing to write a model loader, I'll always check my model data by importing it in Blender to ensure it's valid, made of triangles (or strips), normals point the right way, and the textures look correct. This step is especially important to me if you are exporting something from a 3D modeler to your preferred format to ensure you exported the data correctly. I've felt really stupid a few times when I forgot to triangulate meshes from blender before export.

I use a shader similar to the following to ensure the models normals look correct.


//vertex shader
attribute vec4 position;
attribute vec3 normal;

varying lowp vec4 colorVarying;

uniform mat4 modelViewProjectionMatrix;
uniform mat3 normalMatrix;

void main() {
// normals can be from (-1,-1,-1) to (1,1,1) so bound to (0,0,0) to (1,1,1).
vec3 boundedNormal = 0.5 * ( normal + vec3( 1 ) );
colorVarying = vec4( boundedNormal, 1 );
gl_Position = modelViewProjectionMatrix * position;
}

// fragment shader:
varying lowp vec4 colorVarying;

void main() {
gl_FragColor = colorVarying;
}

Ruggero Visitnin
08-03-2014, 06:09 AM
hi, it's me again
i started working on modern opengl and i was stopped by the OBJ loader and, again, i can't really understand why this doesn't work.
i know that the following codes aren't well written but before do things well i want to understand how to make this stuff works and if it doesn't work i want to understand why.

thanks for your time.

this is my hedaer for OBJ loader file

#ifndef CONTENTMANAGER_H
#define CONTENTMANAGER_H

#include <GL\glew.h>
#include <GL\freeglut.h>
#include <string>
#include <vector>


class ContentManager
{
public:
ContentManager(); // constructor
~ContentManager(); // destructor

GLboolean load(const char *file_path);
GLvoid draw();


private:
typedef struct
{
GLfloat x;
GLfloat y;
GLfloat z;

}Vertex;

typedef struct
{
GLfloat x;
GLfloat y;
GLfloat z;

}Normal;

typedef struct
{
GLfloat x;
GLfloat y;
}Uv;

typedef struct
{
GLint vx;
GLint vy;
GLint vz;

GLint vnx;
GLint vny;
GLint vnz;

GLint uvx;
GLint uvy;
GLint uvz;

}Face;

private:
std::vector <Vertex> vertices;
std::vector <Normal> normals;
std::vector <Uv> uvs;
std::vector <Face> faces;

std::vector <GLfloat> data;

GLboolean is_uv;

GLuint vao_id;
GLuint vbo_id;
};


#endif // CONTENTMANAGER_H

and this is my code for OBJ loader cpp file

#include <fstream>
#include <iostream>

#include "ContentManager.h"

ContentManager::ContentManager()
{
}

ContentManager::~ContentManager()
{
}

GLboolean ContentManager::load(const char *file_path)
{
std::string line;
std::ifstream file(file_path);

is_uv = false;

if (file.fail())
{
std::cerr << "Could not open file " << file_path << " file is illegible or doesn' t exist." << std::endl;
return false;
}

GLint vertex_count = 0;
GLint normal_count = 0;
GLint face_count = 0;
GLint uv_count = 0;

while (!file.eof())
{
std::getline(file, line);

if (line.c_str()[0] == 'v')
{
if (line.c_str()[1] == ' ')
{
vertices.resize(vertex_count + 1);

sscanf(line.c_str(), "v %f %f %f", &vertices[vertex_count].x, &vertices[vertex_count].y, &vertices[vertex_count].z);
//std::cout << line << std::endl;

vertex_count += 1;
}
else if (line.c_str()[1] == 'n')
{
normals.resize(normal_count + 1);

sscanf(line.c_str(), "vn %f %f %f", &normals[normal_count].x, &normals[normal_count].y, &normals[normal_count].z);
//std::cout << line << std::endl;

normal_count += 1;
}
else if (line.c_str()[1] == 't')
{
is_uv = true;

uvs.resize(uv_count + 1);

sscanf(line.c_str(), "vt %f %f", &uvs[uv_count].x, &uvs[uv_count].y);
//std::cout << line << std::endl;
uv_count += 1;
}
}
else if (line.c_str()[0] == 'f')
{
faces.resize(face_count + 1);

if (is_uv)
{
sscanf(line.c_str(), "f %d/%d/%d %d/%d/%d %d/%d/%d", &faces[face_count].vx, &faces[face_count].uvx, &faces[face_count].vnx, &faces[face_count].vy, &faces[face_count].uvy, &faces[face_count].vny, &faces[face_count].vz, &faces[face_count].uvz, &faces[face_count].vnz);
//std::cout << line << std::endl;

face_count += 1;

}
else if (!is_uv)
{
sscanf(line.c_str(), "f %d//%d %d//%d %d//%d", &faces[face_count].vx, &faces[face_count].vnx, &faces[face_count].vy, &faces[face_count].vny, &faces[face_count].vz, &faces[face_count].vnz);
//std::cout << line << std::endl;

face_count += 1;
}
}
}

data.resize(faces.size() * 9);
std::cout << data.size() << std::endl;

GLint j = 0;
for (GLint i = 0; i < data.size(); i++)
{
// first vertex
data[i] = vertices[faces[j].vx - 1].x; std::cout << data[i] << " "; i++;
data[i] = vertices[faces[j].vx - 1].y; std::cout << data[i] << " "; i++;
data[i] = vertices[faces[j].vx - 1].z; std::cout << data[i] << " // "; i++;

// second vertex
data[i] = vertices[faces[j].vy - 1].x; std::cout << data[i] << " "; i++;
data[i] = vertices[faces[j].vy - 1].y; std::cout << data[i] << " "; i++;
data[i] = vertices[faces[j].vy - 1].z; std::cout << data[i] << " // "; i++;

// third vertex
data[i] = vertices[faces[j].vz - 1].x; std::cout << data[i] << " "; i++;
data[i] = vertices[faces[j].vz - 1].y; std::cout << data[i] << " "; i++;
data[i] = vertices[faces[j].vz - 1].z; std::cout << data[i] << std::endl;

j++;
}

glGenVertexArrays(1, &vao_id);

glBindVertexArray(vao_id);

glGenBuffers(1, &vbo_id);

glBindBuffer(GL_ARRAY_BUFFER, vbo_id);

glBufferData(GL_ARRAY_BUFFER, sizeof(data), &data, GL_STATIC_DRAW);

return true;
}

GLvoid ContentManager::draw()
{
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);

glDrawArrays(GL_TRIANGLES, 0, data.size() / 3);

glDisableVertexAttribArray(0);
}

MtRoad
08-04-2014, 11:48 AM
Don't post to the same thread for additional questions, use a different thread.


I think you need to use data.data instead of &data (http://en.cppreference.com/w/cpp/container/vector/data). Since data is an object, &data points to the start of the object, which is not necessarily where your data is stored.

As a side note, you don't need to keep your own counts. You can use vertices.size() to give you the number of elements in the vector.

I'm going to try something and post back to see my version of this will work for you.

Carmine
08-04-2014, 03:04 PM
hi, it's me again ... Hello. It's me again too. I still say you should draw the first 5 triangles in your obj file in wireframe to find your error. It looks like a topology/logic error, i.e. you're connecting the wrong vertices to make the triangles. Perhaps you are misinterpreting how the 'f' records in an OBJ file work? 'f 5 7 8', means to connect vertex 5 to vertex 7 to vertex 8 back to vertex 5 to make a triangle. 5, 7, 8 are indices into the vertex array you've read in. Keep in mind that Wavefront vertex indices start with '1', not '0'. Good luck.

Ruggero Visitnin
08-05-2014, 02:45 AM
Hello. It's me again too. I still say you should draw the first 5 triangles in your obj file in wireframe to find your error. It looks like a topology/logic error, i.e. you're connecting the wrong vertices to make the triangles. Perhaps you are misinterpreting how the 'f' records in an OBJ file work? 'f 5 7 8', means to connect vertex 5 to vertex 7 to vertex 8 back to vertex 5 to make a triangle. 5, 7, 8 are indices into the vertex array you've read in. Keep in mind that Wavefront vertex indices start with '1', not '0'. Good luck.

i know how obj format works, but now the problem is not that i obtain a strange mesh but that i don't have nothing on the screen now that i'm trying to do with opnegl 3.x and above.
i think the problem is the buffer code because the code worked well using display lists function.

hope it is clear now.

Ruggero Visitnin
08-05-2014, 02:47 AM
thanks a lot

carsten neumann
08-05-2014, 07:37 AM
glBufferData(GL_ARRAY_BUFFER, sizeof(data), &data, GL_STATIC_DRAW);


This uploads 3 * sizeof(void*), so 24 bytes on a typical 64 bit host, for a usual implementation of std::vector. You want 3 * sizeof(GLfloat) * data.size() and (as already mentioned) data.data() (if using C++11) or &data.front() (for older compilers/standard library implementations).

MtRoad
08-05-2014, 06:45 PM
This uploads 3 * sizeof(void*), so 24 bytes on a typical 64 bit host, for a usual implementation of std::vector. You want 3 * sizeof(GLfloat) * data.size() and (as already mentioned) data.data() (if using C++11) or &data.front() (for older compilers/standard library implementations).

Great catch with the data length parameter!!!

You might want to use a struct to store your data. It provides a convenient method so you don't need to remember how many float values you have per vertex and it lets you more easily modify arrays of data. For example:


struct vertex {
GLfloat position[3];
GLfloat normal[3];
GLfloat texCoord[2];
};

Later on, build your info into an array of vertex and then you can conveniently use:


std::vector< vertex > data = // ...
glBufferData(GL_ARRAY_BUFFER, data.size()*sizeof(vertex), data.data(), GL_STATIC_DRAW);

// sets vao vertex pointers
vertex * v = NULL;
glVertexAttribPointer( positionAttribLoc, 3, GL_FLOAT, GL_FALSE, sizeof(v), v->position);
glVertexAttribPointer( normalAttribLocation, 3, GL_FLOAT, GL_FALSE, sizeof(v), v->normal);
glVertexAttribPointer( uvAttribLocation, 2, GL_FLOAT, GL_FALSE, sizeof(v), v->uv);

Ruggero Visitnin
08-07-2014, 03:49 AM
thanks to all for replys,
after a few days of work i solve my problem and now i can import only the position and the indices but hey, it works.
I want to ask you only one thing about this argument before decleare it solved.
is it better to import the normal from the mesh if there are or is it better to calculate normals every time i load a model?

PS: sorry for my bad english.

MtRoad
08-08-2014, 07:42 AM
Let's first talk about loading vertices from an OBJ file. The wavefront format (.OBJ) does not associate a vertex with a specific normal--it specifies a vertex and normal for each vertex in the face. This means that a single vertex position can be associated with multiple normals over the entire model.

Think of the corners of a cube. A .OBJ file can specify each cube face using a set of 6 normals and 8 vertices, but each face can pick whatever normal is in the direction of the face for a specific vertex of a face. So, the same vertex position could be associated with three different normals, one for each of the three faces that share that vertex.

Lets now talk about calculating normals for faces. You can calculate a normal to the plane each triangle is in but there are disadvantages to this.

1. Computation time for the normals can significantly increase your load time, since calculating the normal will require computing a cross product which is an expensive operation (6 multiplications and three subtractions per normal) following by normalizing the normal (square root).

2. It can make your model look faceted, since in some cases you specifically don't want your normals perpendicular to each triangle. A simple example would be a sphere--you want every normal to point out from the center since geometrically that is what should be true. If you calculated normals for a sphere using each triangle, you sphere will shade in such a way that it looks faceted, a fact you probably want to hide since it makes it look really artificial (perhaps unless you are creating a disco ball).

3. Loading normals from models allows artists to be able to adjust normals in their own way, and they come essentially for free (minus read cost).