PDA

View Full Version : how to draw multiple different objects?



Ruggero Visitnin
08-16-2014, 06:21 AM
hello,
i was thinking about how to load multiple objects in modern OpenGL, but i'm stuck, i tried various things but seems none of them works, so i'm here again to ask you to help me:

this is the code that loads obj files:



#include <fstream>
#include <iostream>
#include <math.h>

#include "ContentManager.h"



GLvoid ContentManager::loadOBJ(const GLchar *file_path)
{
std::string line;
std::ifstream file(file_path);

if (file.fail())
std::cerr << "Cannot open file " << file_path << " file is illegible or doesn' t exist." << std::endl;

else
{
has_uv = false;
has_normal = false;

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

std::vector <GLfloat> temp_normal;
std::vector <GLuint> temp_normal_index;

GLfloat fill;

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

if (line.c_str()[0] == 'v')
{
if (line.c_str()[1] == ' ')
{
vertex.resize(vertex_count + 3);
sscanf(line.c_str(), "v %f %f %f", &vertex[vertex_count], &vertex[vertex_count + 1], &vertex[vertex_count + 2]);

vertex_count += 3;
}
else if (line.c_str()[1] == 'n')
{
has_normal = true;
temp_normal.resize(normal_count + 3);
sscanf(line.c_str(), "vn %f %f %f", &temp_normal[normal_count], &temp_normal[normal_count + 1], &temp_normal[normal_count + 2]);


normal_count += 3;
//for now scan only vertices
}
else if (line.c_str()[1] == 't')
{
has_uv = true;
//for now scan only vertices
}
}
else if (line.c_str()[0] == 'f')
{
if (has_normal)
{
if (has_uv)
{
// scan all three value of vertex_index: vertex_vertex_index/uv_vertex_index/normal_vertex_index (x3)

vertex_index.resize(face_count + 3);
temp_normal_index.resize(face_count + 3);

sscanf(line.c_str(), "f %d/%d/%d %d/%d/%d %d/%d/%d", &vertex_index[face_count], &fill, &temp_normal_index[face_count], &vertex_index[face_count + 1], &fill, &temp_normal_index[face_count + 1], &vertex_index[face_count + 2], &fill, &temp_normal_index[face_count + 2]);

face_count += 3;
}

else if (!has_uv)
{
// scan only two value of vertex_index: vertex_vertex_index//normal_vertex_index (x3)

vertex_index.resize(face_count + 3);
temp_normal_index.resize(face_count + 3);

sscanf(line.c_str(), "f %d//%d %d//%d %d//%d", &vertex_index[face_count], &temp_normal_index[face_count], &vertex_index[face_count + 1], &temp_normal_index[face_count + 1], &vertex_index[face_count + 2], &temp_normal_index[face_count + 2]);

face_count += 3;
}
}

else if (!has_normal)
{
if (has_uv)
{
//scan only two value of vertex_index: vertex_vertex_index / uv_vertex_index (x3)

vertex_index.resize(face_count + 3);
sscanf(line.c_str(), "f %d/%d %d/%d %d/%d", &vertex_index[face_count], &fill, &vertex_index[face_count + 1], &fill, &vertex_index[face_count + 2], &fill);

face_count += 3;
}

else if (!has_uv)
{
//scan only one value of vertex_index: vertex_vertex_index (x3)

vertex_index.resize(face_count + 3);
sscanf(line.c_str(), "f %d %d %d", &vertex_index[face_count], &vertex_index[face_count + 1], &vertex_index[face_count + 2]);

face_count += 3;
}
}
}
}

if (has_normal)
{
normal.resize(vertex_count);
std::cout << face_count / 3 << std::endl;

for (GLint i = 0; i < vertex_index.size(); i++)
{
vertex_index[i] -= 1;
temp_normal_index[i] -= 1;
//std::cout << vertex_index[i] << std::endl;
}

temp_buffer_data.resize((vertex_index.size() + temp_normal_index.size()) * 3);
GLint j = 0;

for (GLint i = 0; i < buffer_data.size(); i += 6)
{
buffer_data[vertex_index[j] * 6] = vertex[vertex_index[j] * 3];
buffer_data[vertex_index[j] * 6 + 1] = vertex[vertex_index[j] * 3 + 1];
buffer_data[vertex_index[j] * 6 + 2] = vertex[vertex_index[j] * 3 + 2];

buffer_data[vertex_index[j] * 6 + 3] = temp_normal[temp_normal_index[j] * 3];
buffer_data[vertex_index[j] * 6 + 4] = temp_normal[temp_normal_index[j] * 3 + 1];
buffer_data[vertex_index[j] * 6 + 5] = temp_normal[temp_normal_index[j] * 3 + 2];


//std::cout << vertex_index[j] << " position: " << buffer_data[vertex_index[j] * 6] << " " << buffer_data[vertex_index[j] * 6 + 1] << " " << buffer_data[vertex_index[j] * 6 + 2] << " normal: " << buffer_data[vertex_index[j] * 6 + 3] << " " << buffer_data[vertex_index[j] * 6 + 4] << " " << buffer_data[vertex_index[j] * 6 + 5] << std::endl;

j++;
}
}

/*if (!has_normal)
{
for (GLint i = 0; i < vertex_index.size(); i++)
vertex_index[i] -= 1;

calculateNormals();

buffer_data.resize((vertex_index.size() + normal.size()));
GLint j = 0;

for (GLint i = 0; i < buffer_data.size(); i += 6)
{
buffer_data[vertex_index[j] * 6] = vertex[vertex_index[j] * 3];
buffer_data[vertex_index[j] * 6 + 1] = vertex[vertex_index[j] * 3 + 1];
buffer_data[vertex_index[j] * 6 + 2] = vertex[vertex_index[j] * 3 + 2];

buffer_data[vertex_index[j] * 6 + 3] = normal[j * 3];
buffer_data[vertex_index[j] * 6 + 4] = normal[j * 3 + 1];
buffer_data[vertex_index[j] * 6 + 5] = normal[j * 3 + 2];


//std::cout << vertex_index[j] << " position: " << buffer_data[vertex_index[j] * 6] << " " << buffer_data[vertex_index[j] * 6 + 1] << " " << buffer_data[vertex_index[j] * 6 + 2] << " normal: " << buffer_data[vertex_index[j] * 6 + 3] << " " << buffer_data[vertex_index[j] * 6 + 4] << " " << buffer_data[vertex_index[j] * 6 + 5] << std::endl;

j++;
}
}*/
}
}

GLvoid ContentManager::calculateNormals()
{
GLint normal_count = 0;
for (GLint i = 0; i < vertex_index.size(); i += 3)
{
normal.resize(vertex_index.size());

math::Vector3f temp_normal;

// v1 = p2 - p1 - > v1 = (p2x, p2y, p2z) - (p1x, p1y, p1z)
// v2 = p3 - p1 - > v2 = (p3x, p3y, p3z) - (p1x, p1y, p1z)
// - > v1 = (p2x - p1x, p2y - p1y, p2z - p1z)
// - > v2 = (p3x - p1x, p3y - p1y, p3z - p1z)

// Nx = ((v1y * v2z) - (v1z * v2y))
// Ny = ((v1z * v2x) - (v1x * v2z))
// Nz = ((v1x * v2y) - (v1y * v2x))
// - > Nx = ((p2y - p1y) * (p3z - p1z)) - ((p2z - p1z) * (p3y - p1y))
// - > Ny = ((p2z - p1z) * (p3x - p1x)) - ((p2x - p1x) * (p2z - p1z))
// - > Nz = ((p2x - p1x) * (p3y - p1y)) - ((p2y - p1y) * (p3x - p1x))


math::Vector3f p1 = math::vec3f(vertex[vertex_index[i] * 3], vertex[vertex_index[i] * 3 + 1], vertex[vertex_index[i] * 3 + 2]);
//std::cout << p1.x << " " << p1.y << " " << p1.z << std::endl;

math::Vector3f p2 = math::vec3f(vertex[vertex_index[i + 1] * 3], vertex[vertex_index[i + 1] * 3 + 1], vertex[vertex_index[i + 1] * 3 + 2]);
//std::cout << p2.x << " " << p2.y << " " << p2.z << std::endl;

math::Vector3f p3 = math::vec3f(vertex[vertex_index[i + 2] * 3], vertex[vertex_index[i + 2] * 3 + 1], vertex[vertex_index[i + 2] * 3 + 2]);
//std::cout << p3.x << " " << p3.y << " " << p3.z << std::endl;

math::Vector3f v1 = (p2 - p1);
math::Vector3f v2 = (p3 - p1);

temp_normal = math::normalize(math::cross(v1, v2));

normal[i + 0] = temp_normal.x;
normal[i + 1] = temp_normal.y;
normal[i + 2] = temp_normal.z;

//std::cout << normal[vertex_index[i + 0]] << " " << normal[vertex_index[i + 1]] << " " << normal[vertex_index[i + 2]] << std::endl;

normal_count += 1;
}
}

GLvoid ContentManager::initMesh(GLenum polygon_mode)
{
glGenVertexArrays(1, &vao[0]);
glGenBuffers(1, &vbo[0]);

glGenBuffers(1, &ebo[0]);

glBindVertexArray(vao[0]);

glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, buffer_data.size() * sizeof(GLfloat), &buffer_data[0], GL_DYNAMIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), 0);

glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, vertex_index.size() * sizeof(GLuint), &vertex_index[0], GL_STATIC_DRAW);

glBindVertexArray(NULL);

glPolygonMode(GL_FRONT_AND_BACK, polygon_mode);
}

GLvoid ContentManager::drawMesh(GLenum drawing_mode)
{

glBindVertexArray(vao[0]);

glDrawElements(drawing_mode, vertex_index.size(), GL_UNSIGNED_INT, 0);

glBindVertexArray(NULL);


}

GLvoid ContentManager::deleteMesh()
{
glDeleteBuffers(1, &ebo[0]);

glDeleteBuffers(1, &vbo[0]);

glDeleteVertexArrays(1, &vao[0]);
}



so, for what i need for now this works well on a single mesh, but how to adapt the code to load multiple objects, i read that i can use multiple VAOs or a single big VAO (hope this is correct), so my question is if someone of you can you help me to find my way through this

MtRoad
08-16-2014, 07:58 AM
I'd extract all the data-setting elements for the geometry into a struct or class and call it something like "Model" or "GeometryPacket". You need a VAO per different VBO and different set of program attribute bindings you use.

You can do something like this (http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter03.html) and use VAOs instead of storing the vertices and indices themselves in the "GeometryPacket".

Here's what I use in my little renderer. It provides the VAO for a program, the VBO (I update some geometry while running, so I need the buffer and would rather not glGet it) and primitive type and number of vertices for drawing. I don't use element arrays right now since its a super-simple toy renderer.


typedef struct {
GLuint vao;
GLuint vbo;
GLenum primitiveType;
GLsizei numVertices;
} ryDrawCall_t;


I build the VAO/VBOs upon data loading:


ryDrawCall_t ryDrawCallBuild( GLsizei dataLength,
void * data,
id< ryVertexFormat > format,
GLenum primType,
GLsizei numVertices,
ryProgram * program )
{
ryDrawCall_t call;

call.primitiveType = primType;
call.numVertices = numVertices;

// Create and bind VAOs for the draw call for the specific program.
glGenVertexArraysOES( 1, &call.vao );
glGenBuffers( 1, &call.vbo );
crPrintGLError();

glBindVertexArrayOES( call.vao );
glBindBuffer( GL_ARRAY_BUFFER, call.vbo );
glBufferData( GL_ARRAY_BUFFER, dataLength, data, GL_STATIC_DRAW );
crPrintGLError();

// Bind vertex attrib pointers. (( this here is custom magic I use to find attributes
// from the format which matches the program... really, it's just a bunch of
// logic to perform the correct glEnableVertexAttribArray and glVertexAttribPointer calls
[ program enableVertexAttributes ];
[ program applyFormat:format ];

// Prevent someone else from messing with our VAO state.
glBindVertexArrayOES( 0 );
crPrintGLError();
return call;
}


In my renderer I keep a list of draw calls and when the client loads a model, I give them the index of that model in my list of draw calls.


ryDrawCall_t drawCalls[ RY_MAX_DRAW_CALLS ];

Ruggero Visitnin
08-16-2014, 08:55 AM
i'm sorry but i can't understand what is your suggestion, my idea is to fill a single VBO with all the vertices of the objects i want to draw and the EBO with the indices of all the objects then call a glDrawElements for each objects that i want draw by specifying an offset, i know is not one of the best method but i need to learn first easy things then pass to some more complicated stuff. Your method seems quite complicated to me, but hey, probably i can't really understand what your renderer does

MtRoad
08-16-2014, 09:23 AM
so, for what i need for now this works well on a single mesh, but how to adapt the code to load multiple objects, i read that i can use multiple VAOs or a single big VAO (hope this is correct), so my question is if someone of you can you help me to find my way through this

You can use a single big VAO, but this makes you need to rebind each VBO and then setup all the vertex attrib pointers, over and over again. I had been doing that until someone here on the forums pointed me in the direction of not doing that.

A VAO keeps all the data you need to supply vertices into a single OpenGL object. Then instead of doing this for every object in your scene:


glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, buffer_data.size() * sizeof(GLfloat), &buffer_data[0], GL_DYNAMIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), 0);

glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, vertex_index.size() * sizeof(GLuint), &vertex_index[0], GL_STATIC_DRAW);


You can do this:


glBindVertexArray(vao[0]);
glDrawElements...


As an example I had rewritten a simpler version of your "ContentManager" using my concept in the airport from your previous thread, so here it is. It looks like all your ContentManager did was load a model, so I changed the name to SimpleModel. Then, I can create instances of SimpleModel, each with a different mesh.


#ifndef SIMPLE_MODEL_H
#define SIMPLE_MODEL_H

#include <gl.h>
#include <string>
#include <vector>

// Find a decent math library to use, which will do these.
struct mtvec2 {
GLfloat x, y;
};

struct mtvec3 {
GLfloat x, y, z;
};

// Really bad member names.
// Don't do this
//
// lol... "mtFace"
struct mtFace {
GLint v [ 3 ];
GLint n [ 3 ];
};


struct mtVertex {
mtvec3 position;
mtvec3 normal;
};



class mtSimpleModel {
mtSimpleModel( const char * filePath );
bool load( const char * filePath );
void interpretFaces ();

std::vector< mtvec3 > positions;
std::vector< mtvec3 > normals;
// std::vector< Uv > uvs;
std::vector< mtFace > faces;
mtVertex * data;
int numVertices;
};
#endif // SIMPLE_MODEL_H





#include "SimpleModel.h"

#include <fstream>
#include <iostream>


mtSimpleModel :: mtSimpleModel( const char * filePath ) {
load( filePath );
}


/**
* Builds interleaved position, normal data array since .obj is a curious
* format.
* \warning only handles triangle faces.
*/
void
mtSimpleModel :: interpretFaces( ) {
// Allocates enough space for all faces, assuming they are triangles.
// Point at the first vertex to write.
data = new mtVertex[ faces.size() * 3 ];
numVertices = faces.size() * 3;
mtVertex * nextVertex = data;

// Loop through all faces. Set the position and normal of each vertex
// from the face.
//
// Subtract 1 because .obj indices starting from 1, instead of 0 like
// everyone else.
for( auto it : faces ) {
for( int idx = 0; idx < 3; idx++ ) {
nextVertex->position = positions[ it.v[ idx ] - 1 ];
nextVertex->normal = normals [ it.n[ idx ] - 1 ];
nextVertex++;
}
}
}




bool
mtSimpleModel :: load( const char * filePath ) {
std::string line;
std::ifstream file( filePath );

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

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

if( line.c_str()[0] == 'v' ) {
if( line.c_str()[1] == ' ' ) {
mtvec3 v;
sscanf( line.c_str(), "v %f %f %f", &v.x, &v.y, &v.z );
positions.push_back( v );
}
else if( line.c_str()[1] == 'n' ) {
mtvec3 v;
sscanf( line.c_str(), "vn %f %f %f", &v.x, &v.y, &v.z );
normals.push_back( v );
}
else if ( line.c_str()[1] == 't' ) {
// vec2 v;
// sscanf( line.c_str(), "vt %f %f", &v.x, &v.y );
// uvs.push_back( v );
}
}
else if (line.c_str()[0] == 'f') {
mtFace f;
sscanf( line.c_str(), "f %d//%d %d//%d %d//%d", &f.v[0], &f.n[0], &f.v[1], &f.n[1], &f.v[2], &f.n[2] );
faces.push_back( f );
}
}
interpretFaces ();
return true;
}


void mtSimpleModel::draw() {
glDrawElements( //...
}


I could easily extend it to add a VAO into it (something like calling your initMesh() in the constructor) and then draw like this.


void loadResources () {
firstObject = new mtSimpleModel("sample.obj");
secondObject = new mtSimpleModel("cube.obj");
}

void glDraw() {
glUseProgram(program);
firstObject->draw();
secondObject->draw();
}

Ruggero Visitnin
08-16-2014, 10:11 AM
i made it before reading your post, for now i have a VAO and a VBO for each object but i can try to implement your solution, thanks a lot

now my code is like this, not the best optimization but works, i also wrote model characteristics into a struct then use it a dynamic vector of models:



GLvoid ContentManager::initMesh(GLenum polygon_mode)
{
for (GLint i = 0; i < model_counter; i++)
{
glGenVertexArrays(1, &vao[i]);

glGenBuffers(1, &vbo[0]);
glGenBuffers(1, &ebo[0]);

glBindVertexArray(vao[i]);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, model[i].vertex_data.size() * sizeof(GLfloat), &model[i].vertex_data[0], GL_DYNAMIC_DRAW);

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), 0);

glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, model[i].index_data.size() * sizeof(GLuint), &model[i].index_data[0], GL_STATIC_DRAW);

glPolygonMode(GL_FRONT_AND_BACK, polygon_mode);
}
}

GLvoid ContentManager::drawMesh(GLenum drawing_mode)
{
for (GLint i = 0; i < model_counter; i++)
{
glBindVertexArray(vao[i]);

glDrawElements(drawing_mode, model[i].index_data.size(), GL_UNSIGNED_INT, 0);

glBindVertexArray(NULL);
}
}