How to render objects more efficiently?

Hello, I have a scene that consists of 1 player object, 1 platform, 1 enemy, and 1 background. Currently, this is how my render function looks like:

   void Sprite::Render()
    {
    	glUseProgram(m_Program);
    	glActiveTexture(GL_TEXTURE0);
    
    	// Background 
    	glBindVertexArray(m_BackgroundVAO);
    	glBindTexture(GL_TEXTURE_2D, m_Textures[1]);
    	glUniform1i(glGetUniformLocation(m_Program, "Texture1"), 0);
    
    	m_ProjectionMatrix = m_Camera.ViewToWorldMatrix();
    	m_TransformationMatrix = m_ProjectionMatrix;
    	m_TransformationMatrixLoc = glGetUniformLocation(m_Program, "TransformationMatrix");
    	glUniformMatrix4fv(m_TransformationMatrixLoc, 1, GL_FALSE, &m_TransformationMatrix[0][0]);
    
    	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
    	
    	// Enemy One
    	glBindVertexArray(m_EnemyOneVAO);
    	glBindTexture(GL_TEXTURE_2D, m_Textures[2]);
    	glUniform1i(glGetUniformLocation(m_Program, "Texture3"), 0);
    
    	m_ProjectionMatrix = glm::translate(glm::mat4(), glm::vec3(EnemyOneXCoord, EnemyOneYCoord, 0.0f)) * m_Camera.ViewToWorldMatrix();
    	m_TransformationMatrix = m_ProjectionMatrix;
    	m_TransformationMatrixLoc = glGetUniformLocation(m_Program, "TransformationMatrix");
    	glUniformMatrix4fv(m_TransformationMatrixLoc, 1, GL_FALSE, &m_TransformationMatrix[0][0]);
    
    	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
    
    	// Platform One
    	glBindVertexArray(m_PlatformVAO);
    	glBindTexture(GL_TEXTURE_2D, m_Textures[3]);
    	glUniform1i(glGetUniformLocation(m_Program, "Texture4"), 0);
    
    	m_ProjectionMatrix = glm::translate(glm::mat4(), glm::vec3(0.0f, -0.5f, 0.0f)) * m_Camera.ViewToWorldMatrix();
    	m_TransformationMatrix = m_ProjectionMatrix;
    	m_TransformationMatrixLoc = glGetUniformLocation(m_Program, "TransformationMatrix");
    	glUniformMatrix4fv(m_TransformationMatrixLoc, 1, GL_FALSE, &m_TransformationMatrix[0][0]);
    
    	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
    
    	// Player
    	glBindVertexArray(m_PlayerVAO);
    	glBindTexture(GL_TEXTURE_2D, m_Textures[0]);
    	glUniform1i(glGetUniformLocation(m_Program, "Texture2"), 0);
    
    	m_ProjectionMatrix = glm::translate(glm::mat4(), glm::vec3(PlayerX, PlayerY, 0.0f)) * m_Camera.ViewToWorldMatrix();
    	m_TransformationMatrix = m_ProjectionMatrix;
    	m_TransformationMatrixLoc = glGetUniformLocation(m_Program, "TransformationMatrix");
    	glUniformMatrix4fv(m_TransformationMatrixLoc, 1, GL_FALSE, &m_TransformationMatrix[0][0]);
    
    	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
    
    	glBindTexture(GL_TEXTURE_2D, 0);
    	glBindVertexArray(0);
    }

As you can see, it is unnecessarily complicated. And if I want to draw, say, two or more enemies, or 4 or more platforms, then it’d get ridiculously large! My question is this, how can I make this smaller? I prefer to be able to do something like this in my main.cpp: Sprite.Render(Player); Sprite.Render(EnemyOne); etc…

Looking forward to reading your tips, thank you!

Not really. Four draw calls for the entire scene isn’t a lot.

The code shouldn’t get any larger; you’d just have more vertices in the arrays for enemies and platforms (you’d have one array for all of the enemies, not one per enemy; likewise for platforms).

[QUOTE=TheFearlessHobbit;1286760]
My question is this, how can I make this smaller? I prefer to be able to do something like this in my main.cpp: Sprite.Render(Player); Sprite.Render(EnemyOne); etc… [/QUOTE]
That isn’t going to be efficient. You want to draw as much as is practicable in each draw call. So all entities of a specific type would be drawn with one draw call.

Needless to say, you shouldn’t use a separate texture for each sprite; pack them into an atlas to minimise the amount of texture swapping.

You may wish to use instancing (glDrawElementsInstanced etc) to reduce the size of attribute arrays (not so much for the sake of saving memory, but to minimise the amount of data which needs to be transferred each frame). But that requires OpenGL 3.2 (instancing was added in 3.1, but to be useful it needs attribute divisors, which were added in 3.2).

You should organize your VAOs and buffers in a way that pays attention to how the data they contain will be handled. You seem to be organizing your data around what role they play in your imaginary 3D world, but this is not something computers can have any advantage from like humans do. Think about what actually makes up your objects and how this data will be handled. Ill assume your geometry is all stored in the usual vertex format (position, normal, uv-coord) which will be indexed and then you might have a bunch of textures you want to apply in your shaders.

Because all your objects are basically the same thing (a bunch of vertices and a bunch of indices) you could just store all you vertices and indices for every object in just one single VBO and element buffer. To draw an object, you would just have to supply glDrawElements with a count of indices and their offset in the element buffer ( you do this by passing the sizeof(indexType)indexOffset as a void into glDrawElements as the last parameter).
This will already greatly reduce you state changes in your render function, because you dont have to switch your VAOs (and VBOs and EBOs) for every object you want to draw, but just once. It also makes your API fairly easy to use, because one Mesh can be represented only by an index offset and count (into your element/index buffer).

Later on, you will probably want to animate some meshes ( like the ones which represent your player or enemy), while others will never be animated(platform and background). here you could split your data up into one VAO for dynamic Meshes and one for the static ones. The VBO in the dynamicVAO will later be streamed to when needed (or you might have another hotVAO which will be updated every frame). As a rule of thumb, it is always best to put as much data into one continuous memory block as possible, especially if it is the same data and is used in similar ways. Every time you bind a new buffer or VAO, your CPU and/or GPU basically have to stop what they are working on, change their toolsets and get back to work. But ideally, you want them to equip one toolset once, process all the data that requires those tools, and then swap to a different toolset and work on another pile of data. I highly recommend learning about data-orientated programming, which is a programming paradigm that focusses on trying to comprehend how the computer “thinks”, what it “sees” and how to make it as easy as possible for it to work on your data. And CPUs are optimized for doing the same thing over and over again, so you ideally want to batch data together which is being used in the same ways (from your CPUs perspective) and have as little state changes/toolset swaps as possible.

Here is an example of my draw loop for static objects. The texture implementation might be a little clumsy and not very versatile, but anyways, i think the drawing of the geometry is fairly efficient.


        //before the frameloop, all static meshes have been loaded and been stored in the VBO of STATIC_VAO
        //and for every mesh, a MeshReference has been created, which contains indexoffset and -count, material index and textures

        glBindVertexArray(STATIC_VAO.ID); //set state once - this binds the VBO with all the static Vertices and the EBO with the indices for all meshes
        OpenGL::ShaderProgram::initiate(gBufferShaderName);//use shader program and update viewMatrix uniform
	glUniformMatrix4fv(glGetUniformLocation(ShaderProgram::getPointer()->ID, "viewMatrix"), 1, GL_FALSE, glm::value_ptr(Camera::viewMatrix));
	

	for (unsigned int mIndex : staticMeshRenderList) { //a list of indices into the allStaticMeshes array. 
                                                                             //i used lists like these to organize the different rendering methods. next to staticMeshRenderList there are
                                                                             //transparentMeshRenderList and instancedMeshRenderList, which all just contain indices into an array of MeshReferences
		MeshReference& mesh = allStaticMeshes[mIndex];
		if (mesh.entities.size() > 0) {  //if there are even any entities which use this mesh... continue
                                                          //the MeshReference keeps track of the entities which use it, not the entities! 
                                                          //this way we can draw all entities which use the same mesh and do not have to sort the entities by the meshes that they are using to get continuity
			        //bind all mesh specific stuff
                                //(you could probably add another loop here, which loops over all the different "versions" of your mesh to be able to use different textures and materials etc. with the same mesh geometry
				glUniform1ui(glGetUniformLocation(ShaderProgram::getPointer()->ID, "materialIndex"), mesh.materialIndex);
				glActiveTexture(GL_TEXTURE0);
				glBindTexture(GL_TEXTURE_2D, mesh.ambient);
				glActiveTexture(GL_TEXTURE1);
				glBindTexture(GL_TEXTURE_2D, mesh.diffuse);
				glActiveTexture(GL_TEXTURE2);
				glBindTexture(GL_TEXTURE_2D, mesh.specular);

				for (unsigned int entityIndex : mesh.entities) { //for every entity of this mesh
                                        //update transformationMatrix for this mesh (the matrices could probably also be passed to the shaders once as one big uniform buffer, which would probably be more efficient
					glUniformMatrix4fv(glGetUniformLocation(ShaderProgram::getPointer()->ID, "transformationMatrix"),
						1, GL_FALSE, glm::value_ptr(Component::positionArray[entityIndex].matrix));

					//RENDER TO GEOMETRY FRAMEBUFFER - i use deferred shading, which makes lighting the scene a lot more performance friendly later on,                                                                                 
                                        //because you basically only have to process the pixels which are actually seen on your screen
					glDrawElements(GL_TRIANGLES, mesh.indexCount, GL_UNSIGNED_INT, (void*)(sizeof(unsigned int) * mesh.indexOffset));

				}
		}
	}

void Sprite::Render() isnt a good idea, you are considering each object in your scene as independent thing (kind of “bottom-up”), my suggestion would be to consider the scene as a whole:

(kind of “top-down”)

void Graphics::Render(const Scene& scene)
{
// set camera matrices etc

glUseProgram(m_program1);
glBindVertexArray(m_vertexarray1);

for each (element in your scene)
{
upload "model-to-world" transformation matrix
glDrawArrays(... triangles, mesh offset, vertexcount...);
}

glBindVertexArray(0);
glUseProgram(0);

}

that “Graphics” class has ALL shader, ALL vertices, ALL other resources it needs to visualize what you want to see: the “scene”

the scene is everything in you virtual world, the player, the enemies, the terrain, the nearby wood, the sky, just everything

once you have that, you can easily simulate it (physics) as a whole, using another class or a member function of the Scene class, and updating the sound sources becomes also just 1 for loop over all object in the “scene” thing

example:

https://sites.google.com/site/john87connor/home/3-4-depth-test

its likely that some of your static models use the same shader + vertex layout, so you can put all of the vertices into 1 big vertex buffer:

|--------------model1---------|--------------model2--------------------|-----etc…
vertex0,vertex1, …, vertexN, vertexN+1,vertexN+2, …, vertexN+M, …

to draw model1, you call glDrawArrays(GL_TRIANGLES, 0, N)
to draw model2, you call glDrawArrays(GL_TRIANGLES, N, M)