how to draw multiple different objects?

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

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

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

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

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