Basic Usage of .MTL in OpenGL - c++

I am currently parsing the .mtl file associated to the .obj file I have. I can properly render the model, but how can I use the .mtl file? Where am I supposed to send the values of it? How do I use it? Currently, can't find anything that uses .mtl file in OpenGL. They just show how they parse it.
EDIT :
This is how do it in OpenGL. I have also created my own OBJ parser. Notice that the code is shorten for just an idea for you how I am doing it.
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, cc * sizeof(GLfloat), v, GL_STATIC_DRAW);
GLuint tcbuffer;
glGenBuffers(1, &tcbuffer);
glBindBuffer(GL_ARRAY_BUFFER, tcbuffer);
glBufferData(GL_ARRAY_BUFFER, tcc * sizeof(GLfloat), vt, GL_STATIC_DRAW);
GLuint ncbuffer;
glGenBuffers(1, &ncbuffer);
glBindBuffer(GL_ARRAY_BUFFER, ncbuffer);
glBufferData(GL_ARRAY_BUFFER, ncc * sizeof(GLfloat), vn, GL_STATIC_DRAW);
glEnableVertexAttribArray(m_vert);
glBindBuffer(GL_ARRAY_BUFFER, vbufferid);
glVertexAttribPointer(m_vert, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(m_texcoord);
glBindBuffer(GL_ARRAY_BUFFER, tcbufferid);
glVertexAttribPointer(m_texcoord, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(m_color);
glBindBuffer(GL_ARRAY_BUFFER, cbufferid);
glVertexAttribPointer(m_color, 4, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(m_vertexnormal);
glBindBuffer(GL_ARRAY_BUFFER, ncbufferid);
glVertexAttribPointer(m_vertexnormal, 3, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, vc);

The MTL file accompanying the .OBJ file is a description of the materials applied to the .OBJ mesh.
For each material, it specifies the texture path and several color properties like diffuse and specular color.
OpenGL doesnt care about .obj, so it also doesnt care about .MTL, the way you use the data is up to you. There's a couple of ways to apply the data in the .MTL file to the opengl mesh.
The easiest way to do this is as follows:
Look at the OBJ file's USEMTL instructions and seperate the faces based on material, basically, whenever you see USEMTL X, all following faces are to be drawn with material X until you encounter a new USEMTL Y.
Then when you draw, for each material called MAT: bind the texture from the .MTL file for MAT, set the diffuse and specular parameters for MAT, and draw only those faces where the .OBJ said USEMTL MAT.
There's a few other ways to be a lot more efficient about it, let me know in the comments how you currently draw your mesh, since this has some influence on what the best option is.
The MTL file can contain some more advanced stuff but this should get you started.
For more info on the MTL file and what the values mean, see Wavefront .obj file.

Related

Rendering different models (with their own ELEMENT_ARRAY_BUFFER) without changing which VAO is used

I am trying to render multiple models sharing the same VAO and vertex format (following top answer on this post Render one VAO containing two VBOs), however I cannot get it to work with GL_ELEMENT_ARRAY_BUFFER nor can I find any resource/example to help me. Is it even possible to do that or does the element array buffer work in a way that is incompatible with glVertexAttribFormat/glBindVertexBuffer and sharing VAOs? or am I missing the ELEMENT_ARRAY_BUFFER equivalent of glBindVertexBuffer?
My VAO is first created this way:
glCreateVertexArrays(1, &sharedVao);
glBindVertexArray(sharedVao);
glEnableVertexAttribArray(0);
glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, 0);
glVertexAttribBinding(0, 0);
// (just 1 for the example but there is more)
glBindVertexArray(0);
Then my model buffers are created as follow:
glBindVertexArray(sharedVao); // tried with and without binding vao first, no success
glCreateBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(vertex), vertices.data(), GL_STATIC_DRAW);
// (just 1 for the example but there is more)
glCreateBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, triangles.size() * sizeof(triangle), triangles.data(), GL_STATIC_DRAW);
glBindVertexArray(0);
And finally I render as follow:
glBindVertexArray(sharedVao);
for (auto const& model : models)
{
glBindVertexBuffer(0, model.vbo, sizeof(vertex));
// (just 1 for the example but there is more)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model.ebo);
// also tried glVertexArrayElementBuffer(sharedVao, model.ebo);
glDrawElements(GL_TRIANGLES, model.triangleCount * 3, GL_UNSIGNED_INT, nullptr);
}
Note that it does work if I start rendering the same VAO with glDrawArray (so without element array buffer).
This C++ GLSL Multiple IBO in VAO may be of valuable use, but still not sure what it means for sharing VAO formats for multiple models... (also realized that calling it IBO gives me more results than EBO...).
EDIT: this question was originally closed as supposedly a duplicate of Rendering meshes with multiple indices but it is not. Unlike this other question I am not talking about having different indices for different data (ex: indices for positions, indices for normals, indices for texture coords, etc.) but to have different indices per draw calls, while still using the same VAO format (the same way it is done with VBO and glBindVertexBuffer in Render one VAO containing two VBOs).
Multiple draw calls with shared Vertex Array Object
The purpose of this method is to avoid the cost of changing VAO format (see glVertexAttribPointer and glVertexAttribFormat: What's the difference? or https://www.youtube.com/watch?v=-bCeNzgiJ8I&t=1860) by sharing VAO and only rebinding buffers for every draw.
A clear example that doesn't make use of Element Buffer Array can be seen here: Render one VAO containing two VBOs
Create shared VAO (could be only once per program):
GLuint sharedVao = 0;
glCreateVertexArray(1, &sharedVao);
glBindVertexArray(sharedVao);
glEnableVertexAttribArray(0);
glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, 0);
// binding each attribute from its own buffer so attrib_index == buffer_index
glVertexAttribBinding(0, 0);
glEnableVertexAttribArray(1);
glVertexAttribFormat(1, 2, GL_FLOAT, GL_FALSE, 0);
glVertexAttribBinding(1, 1);
Create mesh buffers (would be only once per mesh):
struct Mesh
{
GLuint m_ebo = 0;
std::array<GLuint, 2> m_vbos = 0;
GLuint m_triangleCount = 0;
};
// Binding shared VAO here is mandatory as operations accessing or modifying EBO's
// state are not guaranteed to succeed if it wasn't bound to a VAO.
// However, for every new model, binding the EBO will unbind the previous, and we
// will need to rebind EBO to shared VAO for every draw call.
glBindVertexArray(sharedVao);
glCreateBuffer(1, &mesh.m_ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.m_ebo);
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
triangles.size() * sizeof(Triangle),
triangles.data(),
GL_STATIC_DRAW);
glBindVertexArray(0);
mesh.m_triangleCount = triangles.size();
glCreateBuffers(2, mesh.m_vbos.data());
glBindBuffer(GL_ARRAY_BUFFER, mesh.m_vbos[0]);
glBufferData(
GL_ARRAY_BUFFER,
positions.size() * sizeof(glm::vec3),
positions.data(),
GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, mesh.m_vbos[1]);
glBufferData(
GL_ARRAY_BUFFER,
textureCoords.size() * sizeof(glm::vec2),
textureCoords.data(),
GL_STATIC_DRAW);
Render loop:
// Bind shared VAO only once
// If drawing with different set of vertex data bound, use glEnableVertexAttribArray
// or glDisableVertexAttribArray before draw calls
glBindVertexArray(sharedVao);
for (auto const& mesh : meshes)
{
glBindVertexBuffer(0, mesh.m_vbos[0], 0, sizeof(glm::vec3));
glBindVertexBuffer(1, mesh.m_vbos[1], 0, sizeof(glm::vec2));
// This is the key difference with existing example on sharing VAO:
// EBO must be rebound for every draw (unless 2 draws share the same primitive indices)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.m_ebo);
glDrawElements(GL_TRIANGLES, mesh.m_triangleCount * 3, GL_UNSIGNED_INT, nullptr);
}

LWJGL Indexed VBO, a lot of confusion

I cannot figure out how to use an Indexed VBO, IMHO there's a lack of information about it (for example the lwjgl site in which the indexed vbo page is missing ATM).
The structure i'm using in my vertex buffer is {pos.x, pos.y pos.z}, {tex.u, tex.v tex.W} and {norm.x, norm.y norm.z}, my index buffer structure is {posIndex, texIndex, normIndex}
I'm reading all this data from an .obj file, if tex or norm is missing i set it to{-1,-1,-1}.
Here's the code part in which i send data to the GPUs buffers:
this.VBOSize = Vertices.size();
FloatBuffer vbo = BufferUtils.createFloatBuffer(this.VBOSize);
for (int i = 0; i < this.VBOSize; i++) {
vbo.put(Vertices.get(i));
}
vbo.flip();
glBindBuffer(GL_ARRAY_BUFFER, VBOHandle);
glBufferData(GL_ARRAY_BUFFER, vbo, GL_STATIC_DRAW);
this.IBOSize = Indices.size();
IntBuffer ibo = BufferUtils.createIntBuffer(this.IBOSize);
for (int i = 0; i < this.IBOSize; i++) {
ibo.put(Indices.get(i));
}
ibo.flip();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBOHandle);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ibo, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
and here's how i [incorrectly] render it:
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, Object3D.getVBOHandle());
glVertexAttribPointer(0, 3, GL_FLOAT, true, 12, 0);//3 floats * 4 sizeof(float)
glVertexAttribPointer(1, 3, GL_FLOAT, true, 12, 13);
glVertexAttribPointer(2, 3, GL_FLOAT, true, 12, 25);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Object3D.getIBOHandle());
glDrawElements(GL_TRIANGLES, Object3D.getIBOSize(), GL_UNSIGNED_INT, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
THis is not how opengl works. In openGL, a vertex is a set of attributes like position, normal, color, textcoord, whatever. Indexed rendering just references vertices. You cannot have different indices for the various attributes, but just one index for the whole set. If you have the situation where two vertices share their position, but, not the texcoords, they are entirely _different_ vertices, as far as the GL is concerned. You cannot directly use the data from lightwave .obj files but have to preprocess the data to generate the vertex arrays OpenGL can work this.
There is the GL_AMD_interleaved_elements extension which somewhat implements the feature you want to use. It still uses 32-Bit indices, but allowes one to split them into 2 16-Bit or 4 8-Bit indices to use different indices for different attributes, but this extension is far from being in core GL, isn't widely supported and is still very limited.
Nowadays with the programmable pipeline, one could also do the index dereferencing manually in the shaders, basically (mis)using the vertex attributes and accessing the real attribute arrays via a texture buffer object, but that is quite advanced and the performance implications are not clear.

GLSL passing indiced normals to shader

I generated model (Suzie) in blender and exported it to .obj file with normals. During loading mode to my app i noticed that numbers of vertices and normals are diffrent (2012 and 1967).
I try to implement simple cell shading. The problem is in passing normals to shader. For storing vertex data i use vectors from glm.
std::vector<unsigned int> face_indices;
std::vector<unsigned int> normal_indices;
std::vector<glm::vec3> geometry;
std::vector<glm::vec3> normals;
Result i've got so far
Buffers Layout
glBindVertexArray(VAO);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, VertexVBOID);
glBufferData(GL_ARRAY_BUFFER, geometry.size() * sizeof(glm::vec3), &geometry[0], GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, NormalVBOID);
glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals[0], GL_DYNAMIC_DRAW);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VIndexVBOID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, face_indices.size() * sizeof(unsigned int), &face_indices[0], GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
Rendering fragment
glBindVertexArray(VAO);
glPolygonMode(GL_FRONT_AND_BACK, GL_QUADS);
glDrawElements(GL_QUADS, face_indices.size(), GL_UNSIGNED_INT, (void*)0);
glBindVertexArray(0);
The reason that had such wierd problem was that some normals were used more than once to preserve disk space so i had to rearrange them in a proper order. So the solution is pretty trival.
geometry.clear();
normals.clear();
geometry.resize(vv.size());
normals.resize(vv.size());
for (unsigned int i = 0; i < face_indices.size(); i++)
{
int vi = face_indices[i];
int ni = normal_indices[i];
glm::vec3 v = vv [vi];
glm::vec3 n = vn [ni];
geometry[vi] = v ;
normals[vi] = n ;
indices.push_back(vi);
}
You should also keep in mind that using the smooth modifier in Blender before export will in some cases help ensure that you have 1 normal per vertex (you may or may not need to also set per-vert normal view instead of face-normal view...can't rem so you'll have to test). This is because by default, blender uses per-face normals. The smooth modifier ("w" hotkey menu)
will switch it to per-vertex norms. Then when you export, you export verts and norms as usual, and the number should match. It doesn't always, but this has worked for me in the past.
This could possibly mean less unnecessary juggling of your data during import.

OpenGL VBO program gives blank screen

I've been trying to convert some of my code to modern OpenGL. I've gotten it to the point where I don't get any OpenGL errors, but nothing shows up when I try to draw an object. Here's my code (minus context creation, and error checking):
//Compile shaders and create/link program
//I very highly doubt the problem's here (all my tests say it worked fine),
//so I'm leaving this out for now, but I'll dig it out of my classes if
//there's no obvious problem with the VBO code.
//Create VAO, VBO
unsigned vaoId, vboId;
int positionAttributeLocation;
float vertices[] = {...vertex data here...};
unsigned indices[] = {...index data here...};
positionAttributeLocation = glGetAttribLocation(programId, "position");
glGenVertexArrays(1, &vaoId);
glGenBuffers(1, &vboId);
glBindVertexArray(vaoId);
glBindBuffer(GL_ARRAY_BUFFER, vboId);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(positionAttributeLocation, 3, GL_FLOAT, GL_FALSE, 0, null);
glEnableVertexAttribArray(positionAttributeLocation);
//Create index buffer
unsigned indexId;
glGenBuffers(1, &indexId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glUseProgram(programId);
glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(unsigned int), GL_UNSIGNED_INT, null);
Not quite SSCCE, but I think that's all the code that could possibly be causing an issue and it's pretty much self-contained.
Try glUseProgram() before your glGetAttribLocation()/glEnableVertexAttribArray() calls.
I figured it out. With some of my refactoring, I forgot to set my width and height variables properly, creating a 0 by 0 viewport. Oops...
Your problem more than likely lies with your cg program and modelview space.
Add cgGLSetStateMatrixParameter(modelViewMatrix, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY); to your program just before gldrawarrays, and in your cg file add OUT.HPos = mul(ModelViewProj, IN.position);.
Also add modelViewMatrix as a cgparameter in you initcg section.
I worked this out from the basic opengl samples in the cgtoolkit, and my render function is very similar to yours and now works after having the same problem.

OpenGL loading OBJ model, texture distortion

I decided to import Wavefront .OBJ format to a test-scene that I'm working on. I get the model (vertices) to be in the right place and it displays fine. When I then apply a texture a lot of things looks distorted. I checked my Maya scene (there it looks good), and the object has many more uv-coordinates than vertex positions (this is what makes the scene looks weird in OpenGL, is my guess).
How would I go about loading a scene like that. Do I need to duplicate vertices and how do I store it in the vertex-buffer object?
You are right that you have to duplicate the vertices.
In addition to that you have to sort them in draw order, meaning that you have to order the vertices with the same offsets as the texture coordinates and normals.
basically you'll need this kind of structure:
float *verts = {v1_x,v1_y,v1_z,v1_w,v2_x,v2_y,v2_z,v2_w,...};
float *normals = {n1_x,n1_y,n1_z,n2_x,n2_y,n2_z,...};
float *texcoords = {t1_u,t1_v,t1_w,t2_u,t2_v,t2_w,...};
This however would mean that you have at least 108bytes per Triangle.
3(vert,norm,tex)
*3(xyz/uvw)
*3(points in tri)
*4(bytes in a float))
-----------------------
= 108
You can significantly reduce that number by only duplicating the vertices that actually are duplicate (have identical texture coordinate,vertices and normals meaning: smoothed normals and no UV borders) and using an Index Buffer Object to set the draw order.
I faced the same problem recently in a small project and I just split the models along the hard-edges and UV-Shell borders therefore creating only the necessary duplicate Vertices. Then I used the glm.h and glm.cpp from Nate Robins and copied/sorted the normals and texture coordinates in the same order as the vertices.
Then setup the VBO and IBO:
//this is for Data that does not change dynamically
//GL_DYNAMIC_DRAW and others are available
GLuint mDrawMode = GL_STATIC_DRAW;
//////////////////////////////////////////////////////////
//Setup the VBO
//////////////////////////////////////////////////////////
GLuint mId;
glGenBuffers(1, &mId);
glBindBuffer(GL_ARRAY_BUFFER, mId);
glBufferData(GL_ARRAY_BUFFER,
mMaxNumberOfVertices * (mVertexBlockSize + mNormalBlockSize + mColorBlockSize + mTexCoordBlockSize),
0,
mDrawMode);
glBufferSubData(GL_ARRAY_BUFFER, mVertexOffset, numberOfVertsToStore * mVertexBlockSize, vertices);
glBufferSubData(GL_ARRAY_BUFFER, mNormalOffset, numberOfVertsToStore * mNormalBlockSize, normals);
glBufferSubData(GL_ARRAY_BUFFER, mColorOffset, numberOfVertsToStore * mColorBlockSize, colors);
glBufferSubData(GL_ARRAY_BUFFER, mTexCoordOffset, numberOfVertsToStore * mTexCoordBlockSize, texCoords);
//////////////////////////////////////////////////////////
//Setup the IBO
//////////////////////////////////////////////////////////
GLuint IBOId;
glGenBuffers(1, &IBOId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBOId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, mMaxNumberOfIndices * sizeof(GLuint), 0, mDrawMode);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, numberOfIndicesToStore * sizeof(GLuint), indices);
//////////////////////////////////////////////////////////
//This is how to draw the object
//////////////////////////////////////////////////////////
glBindBuffer(GL_ARRAY_BUFFER, mId);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBOId);
//Enables and Disables are only necessary each draw
//when they change between objects
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(mVertexComponents, GL_FLOAT, 0, (void*)mVertexOffset);
if(mNormalBlockSize){
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 0, (void*)mNormalOffset);
}
if(mColorBlockSize){
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(mColorComponents, GL_FLOAT, 0, (void*)mColorOffset);
}
if(mTexCoordBlockSize){
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(mTexCoordComponents, GL_FLOAT, 0, (void*)mTexCoordOffset);
}
glDrawRangeElements(primMode,
idFirstVertex,
idLastVertex,
idLastVertex - idFirstVertex + 1,
mAttachedIndexBuffer->getDataType(),
0);
if(mTexCoordBlockSize)
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
if(mColorBlockSize)
glDisableClientState(GL_COLOR_ARRAY);
if(mNormalBlockSize)
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);