Jump to content
  • Advertisement
babaliaris

OpenGL I can't understand how to render a mesh with more than one textures.

Recommended Posts

Posted (edited)

I'm currently learning how to import models. I created some code that works well following this tutorial which uses only one sampler2D in the fragment shader and the model loads just fine with all the textures. The thing is what happens when a mesh has more than one textures? The tutorial says to define inside the fragment shader N diffuse and specular samplers with the format texture_diffuseN,  texture_specularN and set them via code, where N is 1,2,3, .. , max_samplers. I understand that but how do you use them inside the shader?

In the tutorial the shader is:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture_diffuse1;

void main()
{    
    FragColor = texture(texture_diffuse1, TexCoords);
}

 

which works perfectly for the test model that the tutorial is giving us. Now lets say you have the general shader:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture_diffuse1;
uniform sampler2D texture_diffuse2;
uniform sampler2D texture_diffuse3;
uniform sampler2D texture_diffuse4;
uniform sampler2D texture_diffuse5;
uniform sampler2D texture_diffuse6;
uniform sampler2D texture_diffuse7;

uniform sampler2D texture_specular1;
uniform sampler2D texture_specular2;
uniform sampler2D texture_specular3;
uniform sampler2D texture_specular4;
uniform sampler2D texture_specular5;
uniform sampler2D texture_specular6;
uniform sampler2D texture_specular7;

void main()
{    
	//How am i going to decide here which diffuse texture to output?
    FragColor = texture(texture_diffuse1, TexCoords);
}

 

Can you explain me this with a cube example? Lets say i have a cube which is a mesh and i want to apply a different texture for each face (6 total).

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture_diffuse1;
uniform sampler2D texture_diffuse2;
uniform sampler2D texture_diffuse3;
uniform sampler2D texture_diffuse4;
uniform sampler2D texture_diffuse5;
uniform sampler2D texture_diffuse6;

void main()
{    
  //How am i going to output the correct texture for each face?
    FragColor = texture(texture_diffuse1, TexCoords);
}

 

I know that the text coordinates will apply the texture at the correct face, but how do i now which sampler to use every time the fragments shader is called?

I hope you understand why I'm frustrated.

Thank you :) 

Edited by babaliaris

Share this post


Link to post
Share on other sites
Advertisement

You usually don't try to texture multiple surfaces at once during a draw call. Generally the reason there are multiple texture inputs and samplers to a shader is so you can combine different types of maps(diffuse, specular, normal, ao, etc). More specifically it's usually explained as one material per draw call, a material mostly consisting of the shaders to use and the textures to use as inputs to the shaders, if you have a model that requires using multiple materials to render then you are stuck doing multiple draw calls.

A good example is characters, high detail humanoids will often have a shader and uv map for the skin and most of the body, the hair is then rendered as separate geometry and often the eyes are as well for a multitude of reasons. Ironically the cube example is one of the more unusual ones, if you think of something like a barrel the barrel usually has what appears to be one texture wrapping the body, one for the top and one for the bottom. In reality that's usually one uv-mapped texture atlas. You could do the same for a box if you wanted you'd just have to combine 6 textures into one, that or just use one texture and have it drawn for each face.

Share this post


Link to post
Share on other sites

Seems youre reading some twisted tutorial.

The ildest implementation is as follows

Main concept is to store an array of textures, and store which face uses which texture (index) then you draw each face as separated one. You bind texture theb draw face, bind texture theb draw face.

 

Everyrhing varies on what kind of model you are dealing with, so actual rendering may be different, and so on switching between multiple textures can vary, not to mention that you could use one texture for that....

Share this post


Link to post
Share on other sites
Posted (edited)
23 minutes ago, Satharis said:

You usually don't try to texture multiple surfaces at once during a draw call. Generally the reason there are multiple texture inputs and samplers to a shader is so you can combine different types of maps(diffuse, specular, normal, ao, etc). More specifically it's usually explained as one material per draw call, a material mostly consisting of the shaders to use and the textures to use as inputs to the shaders, if you have a model that requires using multiple materials to render then you are stuck doing multiple draw calls.

A good example is characters, high detail humanoids will often have a shader and uv map for the skin and most of the body, the hair is then rendered as separate geometry and often the eyes are as well for a multitude of reasons. Ironically the cube example is one of the more unusual ones, if you think of something like a barrel the barrel usually has what appears to be one texture wrapping the body, one for the top and one for the bottom. In reality that's usually one uv-mapped texture atlas. You could do the same for a box if you wanted you'd just have to combine 6 textures into one, that or just use one texture and have it drawn for each face.

So in other words I'm splitting the 36 vertices to 2(tiangles) * 3 (vertices) = 6 vertices (1 face) and updating the vbo for each face every time and drawing (and setting the appropriate uniforms on the shader)?

Or is it better to create 6 different VBO'S for each face and one shader for all of them and update the shader's uniforms accordingly.

And what happens with meshes? Assimp give's you for each mesh all the vertices, is there a way to split them to faces? Is out there a good tutorial that can teach me exactly how to create such a system correctly to load any kind of models? 

Thank you.

Edited by babaliaris

Share this post


Link to post
Share on other sites

Conceptually you can think of each draw call as the minor unit of work for rendering. In the case of your cube the ideal would be as I said, use one VBO with all the vertices in it and draw or drawindexed and a texture atlas for all 6 faces.

In the alternate case of what you just stated (and weirdcat mentioned), each face is essentially treated as a separate object when it comes to rendering. So yes, you'd leave the shader as is, change the uniforms(since uniforms are essentially shader inputs) and do a draw call for each face. For the VBO you could just use one and specify an offset and only draw a number of the vertices, or you could use a VBO for each face. It doesn't really matter, though having one is of course generally better due to the overhead added for each one.

The benefit of a VBO usually is having all the vertex data in one bundle so the shader can gobble it all up at once, but in this case you're doing separate draw calls for each face anyway so it would be rather irrelevant from a performance point of view.

Share this post


Link to post
Share on other sites
Posted (edited)
13 minutes ago, Satharis said:

Conceptually you can think of each draw call as the minor unit of work for rendering. In the case of your cube the ideal would be as I said, use one VBO with all the vertices in it and draw or drawindexed and a texture atlas for all 6 faces.

In the alternate case of what you just stated (and weirdcat mentioned), each face is essentially treated as a separate object when it comes to rendering. So yes, you'd leave the shader as is, change the uniforms(since uniforms are essentially shader inputs) and do a draw call for each face. For the VBO you could just use one and specify an offset and only draw a number of the vertices, or you could use a VBO for each face. It doesn't really matter, though having one is of course generally better due to the overhead added for each one.

The benefit of a VBO usually is having all the vertex data in one bundle so the shader can gobble it all up at once, but in this case you're doing separate draw calls for each face anyway so it would be rather irrelevant from a performance point of view.

I can't understand. How can you have all the vertices (36) in one vbo and draw 6 faces with 6 draw calls? The VBO has all the vertices so it will draw the hole cube in one draw call. The only thing that comes in mind is this:

glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0)

The last parameter which i never used it and don't know what it does. Maybe is related to what you are trying to make me understand.  Can i somehow control for each draw call the vertices that the vertex shader is going to receive ? So with only one vbo and 6 draw calls and updating the last parameter of the draw call accordingly I can achieve that?

Edited by babaliaris

Share this post


Link to post
Share on other sites
Posted (edited)
GLCall(glDrawArrays(GL_TRIANGLES, 0, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 6, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 12, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 18, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 24, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 30, 6));

This works. So this is how I'm drawing each face???

Thanks for the help now understand but still i have to figure out how to use this with Assimp and draw each mesh and each face separately using indexing (ebo's).

Edited by babaliaris

Share this post


Link to post
Share on other sites
2 hours ago, babaliaris said:

GLCall(glDrawArrays(GL_TRIANGLES, 0, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 6, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 12, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 18, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 24, 6));
GLCall(glDrawArrays(GL_TRIANGLES, 30, 6));

This works. So this is how I'm drawing each face???

Thanks for the help now understand but still i have to figure out how to use this with Assimp and draw each mesh and each face separately using indexing (ebo's).

Haven't used Assimp so I couldn't really comment but usually a model loading library places all the relevant data into some structs/classes for you to work with. However, that's why there's a bit of a big leap from rendering things by hand to automatically, you'll have to rewrite your code to handle rendering just about any object with only the data for it being fed in from files. I would look at sample code using Assimp and see what you can figure out, but you have the basic tools to render anything just with the draw call commands and the others that affect the settings for the draw calls.

Share this post


Link to post
Share on other sites
Posted (edited)

 

assimp_structure.png

 

In assimp you traversing from the root node. Each child node has a mMeshes array that contains indexes. You use these indexes to get the appropriate meshes from scene->mMeshes. Then you can use this mesh object to get all the data required to draw a mesh and also an index to get all the materials from scene->mMaterials (Diffuse and Specular Maps). This is my code:

 

void Model::processNode(aiNode * node, const aiScene * scene)
{
	// process all the node's meshes (if any)
	for (unsigned int i = 0; i < node->mNumMeshes; i++)
	{
		aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
		meshes.push_back(processMesh(mesh, scene));
	}
	// then do the same for each of its children
	for (unsigned int i = 0; i < node->mNumChildren; i++)
	{
		processNode(node->mChildren[i], scene);
	}
}




Mesh Model::processMesh(aiMesh * mesh, const aiScene * scene)
{
  /*Get all the data for rendering a mehs*/
  
  
	std::vector<Vertex> vertices;
	std::vector<unsigned int> indices;
	std::vector<MeshTexture> textures;

	for (unsigned int i = 0; i < mesh->mNumVertices; i++)
	{
		Vertex vertex;
		
		vertex.Position.x = mesh->mVertices[i].x;
		vertex.Position.y = mesh->mVertices[i].y;
		vertex.Position.z = mesh->mVertices[i].z;


		vertex.Normal.x = mesh->mNormals[i].x;
		vertex.Normal.y = mesh->mNormals[i].y;
		vertex.Normal.z = mesh->mNormals[i].z;

		if (mesh->mTextureCoords[0]) // does the mesh contain texture coordinates?
		{
			vertex.TexCoords.x = mesh->mTextureCoords[0][i].x;
			vertex.TexCoords.y = mesh->mTextureCoords[0][i].y;
		}

		else
			vertex.TexCoords = glm::vec2(0.0f, 0.0f);


		vertices.push_back(vertex);
	}

	for (unsigned int i = 0; i < mesh->mNumFaces; i++)
	{
		aiFace face = mesh->mFaces[i];
		for (unsigned int j = 0; j < face.mNumIndices; j++)
			indices.push_back(face.mIndices[j]);
	}


	// process material
	if (mesh->mMaterialIndex >= 0)
	{
		aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];

		std::vector<MeshTexture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");

		textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());

		std::vector<MeshTexture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");

		textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
	}

	//Return the mesh.
	return Mesh(vertices, indices, textures);
}

 

 

And the mesh draw call:

void Mesh::Draw(Shader *shader)
{
	unsigned int diffuseNr = 1;
	unsigned int specularNr = 1;

	std::cout << textures.size() << std::endl;
	for (unsigned int i = 0; i < textures.size(); i++)
	{
		// activate proper texture unit before binding
		GLCall(glActiveTexture(GL_TEXTURE0 + i)); 

		// retrieve texture number (the N in diffuse_textureN)
		std::string number;
		std::string name = textures[i].type;
		if (name == "texture_diffuse")
			number = std::to_string(diffuseNr++);
		else if (name == "texture_specular")
			number = std::to_string(specularNr++);

		shader->SetUniform1f(("material." + name + number).c_str(), i);
		GLCall(glBindTexture(GL_TEXTURE_2D, textures[i].id));
	}
	GLCall(glActiveTexture(GL_TEXTURE0));

	// draw mesh
	GLCall(glBindVertexArray(VAO));
	GLCall(glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0));
}

 

As you can see the mesh draw call is working in a weird way, which the author of learnopengl say us to do:

oIrDMuJ.png

 

I want to change that mesh draw call and make it draw each face with the appropriate texture but i don't know how to do that. If you see in assimps diagram each mesh has a material index that let you retrieve two arrays from scene-mMaterials. One array for all the diffuse maps and one for the specular maps. Also from the mesh you can retrieve the number of faces. But how do you know how many vertices is a face consist of? Is this something standard? Also the materials you have from these two arrays, how do you know in which face you need to apply each material?

 

Face: A closed set of edges, in which a triangle face has three edges . So in triangular rendering, a face is actually a rectangular area from two triangles? So a face is been consist of 4 vertices? (Or 6 vertices if you draw it without an element buffer) .

 

If someone has a link or something that can point me in the right direction to learn that, it would be really helpful. 

Thank you.

Edited by babaliaris

Share this post


Link to post
Share on other sites

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!