I've written a very basic .obj file loader (just loads the vertex coords and triangular face indices) and renderer. When I only load one model at a time, it works great. When I load a 2nd model (or more), each one draws itself and part of the others, and I've wracked my brain trying to figure out why this is.
Some relevant code snippets:
This is where I open the .obj file and read the vertex coords and triangle face indices from the file.
bool Model::loadFromFile(string fileName)
{
vector<GLfloat> vertices;
vector<GLuint> indices;
fstream file(fileName);
string line = "";
while (getline(file, line))
{
istringstream lineReader(line);
char c;
//read the first letter of the line
//The first letter designates the meaning of the rest of the line
lineReader >> c;
vec3 vertex; //if we need to read .x, .y, and .z from the file
GLuint index[3]; //if we need to read the 3 indices on a triangle
switch (c)
{
case '#':
//we do nothing with comment lines
break;
case 'v': //V means vertex coords
case 'V': //so Load it into a vec3
lineReader >> vertex.x >> vertex.y >> vertex.z;
vertices.push_back(vertex.x);
vertices.push_back(vertex.y);
vertices.push_back(vertex.z);
break;
case 'vt':
case 'VT':
//no tex coords yet either
break;
case 'f': //F means indices of a face
case 'F':
lineReader >> index[0] >> index[1] >> index[2];
//we subtract one from each index in the file because
//openGL indices are 0-based, but .obj has them as
//1-based
indices.push_back(index[0] - 1);
indices.push_back(index[1] - 1);
indices.push_back(index[2] - 1);
cout << "Indices: " << index[0] << ", " << index[1] << ", " << index[2] << endl;
break;
}
}
return this->load(vertices, indices);
}
At the end of the loadFromFile method, the we pass in the vector of indices and vertices to the model::load() method, which creates and binds the VAO, VBO, and IBO.
bool Model::load(vector<float>& vertices, vector<GLuint>& indices)
{
//create the VAO
glGenVertexArrays(1, &handle);
glBindVertexArray(getHandle());
//and enable vertexPositionAttribute
glEnableVertexAttribArray(pShader->getPositionAttribIndex());
//Populate the position portion of the
//VAO
GLuint vboID = 0;
glGenBuffers(1, &vboID);
glBindBuffer(GL_ARRAY_BUFFER, vboID);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * vertices.size(), &vertices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vboID);
glVertexAttribPointer(pShader->getPositionAttribIndex(), 3, GL_FLOAT, GL_FALSE, 0, nullptr);
//Populate the indices portion of the
//VAO
GLuint iboID = 0;
glGenBuffers(1, &iboID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices.size(), &indices[0], GL_STATIC_DRAW);
//bind nothing to avoid any mistakes or loading
//of random data to my VAO
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
this->numVertices = vertices.size() / 3;
this->numIndices = indices.size();
cout << "Num Indices: " << numIndices << endl;
return true;
}
Once the model is loaded, it's VAO id is stored in a class variable. The VAO is loaded in the model::bind() method.
void Model::bind()
{
glBindVertexArray(getHandle());
}
And drawn with a call to glDrawElements()
void Model::draw()
{
//and draw me, bitchez!
glDrawElements(GL_TRIANGLES, this->numIndices, GL_UNSIGNED_INT, nullptr);
}
The models are drawn inside the level::draw() method:
void Level::draw()
{
pShader->setViewMatrix(camera.getViewMatrix());
for (int modelIndex = 0; modelIndex < models.size(); modelIndex++)
{
models[modelIndex]->bind();
for (int drawableIndex = 0; drawableIndex < drawables.size(); drawableIndex++)
drawables[drawableIndex]->draw();
}
}
There are no OpenGL errors, and the file parsing churns out the right values when I print it to the console.
If anyone can point out how/why this is happening, I would greatly appreciate it.
Well, after a few more hours of digging through code, I noted inside the level::draw method:
for (int modelIndex = 0; modelIndex < models.size(); modelIndex++)
{
models[modelIndex]->bind();
for (int drawableIndex = 0; drawableIndex < drawables.size(); drawableIndex++)
drawables[drawableIndex]->draw();
}
I bind the VAO of every model for every entity I load. Quite literally I was drawing every loaded model for everything on screen. Sorry to waste screen space on the OpenGL section with this!
Related
Currently working on a personal implementation of the Boids Flocking simulation to test what I've learned about OpenGL, I've been looking to see how high I can get the # of bonds drawn upon the screen above 30 FPS.
Unfortunately I've hit a road-block with how at 2^16 and beyond boids (Where each boid is three float vertices each of size 3, where the z value is maintained to be 0) the graphical output begins to flicker with boids disappearing and reappearing upon the screen.
Interestingly this only seems to happens in the beginning stages of the app, with the flickering seemingly ending upon the boids forming their "flocks" and coincidentally clumping together visually.
I am also suspect of the number 2^16 as it matches with the GLshort bit count of 16, so potentially an overflow of the values?
Here is the area I thought was most pertinent to the issue
std::vector<float> vertices(flock.boids.size()*9, 0.f);
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0])*static_cast<uint>(vertices.size()), vertices.data(), GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
float FPS_sum = 0;
size_t frames = 0;
float CPS_sum = 0;
std::chrono::high_resolution_clock::time_point start;
// render loop
while(!glfwWindowShouldClose(window))
{
processInput(window);
//Begin CPS timer
start = std::chrono::high_resolution_clock::now();
//Computation Step
std::vector<point_bucket<Boid> > tree;
point_bucket<Boid> base(0, 0, 2, 2, flock.boids);
base_split(base, 16, tree, 20);
for(auto& elm: tree)
{
flock.Update(elm.bucket);
}
flock.Mirror();
CPS_sum+=1000.f/std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()-start).count();
// rendering commands here
glClearColor(0.2f, 0.3f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
//Begin FPS timer
start = std::chrono::high_resolution_clock::now();
glBindVertexArray(VAO);
updateVertices(flock.boids, window, vertices);
updateBuffer(VBO, 0, vertices.data(), sizeof(vertices[0])*static_cast<uint>(vertices.size()), GL_ARRAY_BUFFER);
size_t draw_running_total = vertices.size()/3;
GLint draw_offset = 0;
while(draw_running_total > 0)
{
glDrawArrays(GL_TRIANGLES, draw_offset, draw_running_total > draw_size? draw_size: draw_running_total);
draw_running_total -= draw_size;
draw_offset += draw_size;
}
// check and call events and swap the buffers
glfwSwapBuffers(window);
glfwPollEvents();
// PULL FPS FOR DRAW
FPS_sum+=1000.f/std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()-start).count();
frames++;
if(frames==100)
{
std::cout << "Average across 100 frames... \n" << FPS_sum/frames << " FPS\n"
<< CPS_sum/frames << " CPS\n";
FPS_sum = 0;
CPS_sum = 0;
frames = 0;
}
//END PULL FOR FPS
}
void updateBuffer(uint &id, uint offset, void *data, uint size, GLenum shaderType)
{
glBindBuffer(shaderType, id);
glBufferSubData(shaderType, offset, size, data);
}
Personally looking online myself I was able to find questions like...
https://stackoverflow.com/questions/24099139/c-opengl-flickering-issues
... which seem only to deal with extraneous buffer swapping, whereas I only swap buffers at the end of the draw loop.
A reddit post addressed a similar issue and found a potential solution in a FBO, but still couldn't discover the root cause of the problem
Any help on would be greatly appreciated!
The GitHub repo for this project is also available for those interested.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 months ago.
Improve this question
I am currently rendering about 10 backpack objects and a huge sponza model with OpenGL (which has about 400 meshes and 2 million triangles), and 7 point lights in my scene. The performance is ok (60fps) when the window is in 800x600 resolution, however when I make the window about the size of my screen (2560x1600), performance hugely drops to about 15-20 fps, especially when looking through the sponza model (I am using Assimp). The code structure is like the following (simplified):
Mesh.cpp:
class Mesh {
// class that contains all vertex info (pos, normal, UV)
void draw(const Shader& shader) const {
// bind the textures + activate
// bind the vao
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
}
};
Model.cpp (only job is to load the meshes):
class Model {
std::vector<Mesh> meshes;
void draw(const Shader& shader) const {
for (const auto& mesh: meshes) {
mesh.draw(shader);
}
}
};
The thing is, since sponza model has 400 meshes, this corresponds to 400 draw calls + 10 backpack draw calls which roughly equals to 410 draw calls per frame. I guess that making a draw call for each mesh might create this performance drop. However, I am unsure how to solve it. Maybe I can put all the vertex data into one VBO and make one draw call, but I'm not exactly sure how to tackle that. Also, there are 25 materials in the sponza model.
P.S. I am using learnopengl.com's implementation of Mesh and Model, and here are the complete classes:
Model.cpp
class Model {
public:
explicit Model(const std::string &path);
~Model();
void draw(const Shader &shader);
private:
void load_model(const std::string &path);
void process_node(aiNode *node, const aiScene *scene);
Mesh process_mesh(aiMesh *mesh, const aiScene *scene);
std::vector<Texture> load_material_textures(aiMaterial *mat, aiTextureType type,
Texture::TextureType tex_type);
std::vector<Mesh> meshes;
std::vector<Texture> textures_loaded;
std::string directory;
};
#include <string>
Model::Model(const std::string &path) {
load_model(path);
}
Model::~Model() {
for (const auto &mesh : meshes) {
mesh.destroy();
}
}
void Model::draw(const Shader &shader) {
for (const auto &mesh : meshes)
mesh.draw(shader);
}
void Model::load_model(const std::string &path) {
Assimp::Importer importer;
const aiScene *scene = importer.ReadFile(path.c_str(), aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenSmoothNormals | aiProcess_JoinIdenticalVertices);
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
std::cout << "ERROR::ASSIMP::" << importer.GetErrorString() << std::endl;
return;
}
directory = path.substr(0, path.find_last_of("/"));
std::cout << "load_model(" << path << ")" << std::endl;
process_node(scene->mRootNode, scene);
std::cout << meshes.size() << std::endl;
}
void Model::process_node(aiNode *node, const aiScene *scene) {
// process each mesh located at the current node
for (unsigned int i = 0; i < node->mNumMeshes; i++) {
// the node object only contains indices to index the actual objects in the scene.
// the scene contains all the data, node is just to keep stuff organized (like relations between nodes).
aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
meshes.push_back(process_mesh(mesh, scene));
}
// after we've processed all of the meshes (if any) we then recursively process each of the children nodes
for (unsigned int i = 0; i < node->mNumChildren; i++) {
process_node(node->mChildren[i], scene);
}
}
Mesh Model::process_mesh(aiMesh *mesh, const aiScene *scene) {
std::vector<Vertex> vertices;
std::vector<Texture> textures;
std::vector<ui32> indices;
// fill in vertex data form the model info
for (int i = 0; i < mesh->mNumVertices; i++) {
Vertex vertex;
glm::vec3 temp_vec;
// get the position data
temp_vec.x = mesh->mVertices[i].x;
temp_vec.y = mesh->mVertices[i].y;
temp_vec.z = mesh->mVertices[i].z;
vertex.position = temp_vec;
// get the normal data
temp_vec.x = mesh->mNormals[i].x;
temp_vec.y = mesh->mNormals[i].y;
temp_vec.z = mesh->mNormals[i].z;
vertex.normals = temp_vec;
// get the uv data
if (mesh->mTextureCoords[0]) {
glm::vec2 uv_data;
uv_data.x = mesh->mTextureCoords[0][i].x;
uv_data.y = mesh->mTextureCoords[0][i].y;
vertex.uv = uv_data;
} else
vertex.uv = glm::vec2(0.0f, 0.0f);
vertices.push_back(vertex);
}
for (int i = 0; i < mesh->mNumFaces; i++) {
aiFace face = mesh->mFaces[i];
for (int j = 0; j < face.mNumIndices; j++)
indices.push_back(face.mIndices[j]);
}
if (mesh->mMaterialIndex >= 0) {
aiMaterial *mat = scene->mMaterials[mesh->mMaterialIndex];
std::vector<Texture> diffuse_maps = load_material_textures(mat, aiTextureType_DIFFUSE, Texture::TextureType::DIFFUSE);
textures.insert(textures.end(), diffuse_maps.begin(), diffuse_maps.end());
std::vector<Texture> specular_maps = load_material_textures(mat, aiTextureType_SPECULAR, Texture::TextureType::SPECULAR);
textures.insert(textures.end(), specular_maps.begin(), specular_maps.end());
std::vector<Texture> emission_maps = load_material_textures(mat, aiTextureType_EMISSIVE, Texture::TextureType::EMISSION);
textures.insert(textures.end(), emission_maps.begin(), emission_maps.end());
}
return Mesh(vertices, indices, textures);
}
std::vector<Texture> Model::load_material_textures(aiMaterial *mat, aiTextureType type, Texture::TextureType tex_type) {
std::vector<Texture> textures;
for (int i = 0; i < mat->GetTextureCount(type); i++) {
aiString str;
mat->GetTexture(type, i, &str);
bool skip = false;
for (int j = 0; j < textures_loaded.size(); j++) {
if (std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0) {
textures.push_back(textures_loaded[j]);
skip = true;
break;
}
}
if (!skip) {
Texture tex = ResourceManager::load_ogl_texture_from_path(directory + "/" + str.C_Str(), tex_type);
tex.path = str.C_Str();
textures.push_back(tex);
textures_loaded.push_back(tex);
}
}
return textures;
}
Mesh.cpp:
struct Vertex {
glm::vec3 position;
glm::vec3 normals;
glm::vec2 uv;
};
class Mesh {
public:
std::vector<Vertex> vertices;
std::vector<ui32> indices;
std::vector<Texture> textures;
Mesh(const std::vector<Vertex> &vertices, const std::vector<ui32> &indices, const std::vector<Texture> &textures);
~Mesh();
void draw(const Shader &shader) const;
void destroy() const;
private:
ui32 vao, vbo, ebo;
void setup_mesh();
};
Mesh::Mesh(const std::vector<Vertex> &vertices, const std::vector<ui32> &indices, const std::vector<Texture> &textures) : vertices(vertices), indices(indices), textures(textures) {
setup_mesh();
}
Mesh::~Mesh() {}
void Mesh::setup_mesh() {
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ebo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int),
&indices[0], GL_STATIC_DRAW);
// vertex positions
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr);
// vertex normals
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) offsetof(Vertex, normals));
// vertex texture coords
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) offsetof(Vertex, uv));
glBindVertexArray(0);
}
void Mesh::draw(const Shader &shader) const {
ui32 no_diffuse = 1;
ui32 no_specular = 1;
ui32 no_emission = 1;
for (int i = 0; i < textures.size(); i++) {
glActiveTexture(GL_TEXTURE0 + i);
std::string n, base_name;
base_name = textures[i].type;
if (std::strcmp(base_name.c_str(), "texture_diffuse") == 0)
n = std::to_string(no_diffuse++);
else if (std::strcmp(base_name.c_str(), "texture_specular") == 0)
n = std::to_string(no_specular++);// transfer unsigned int to string
else if (std::strcmp(base_name.c_str(), "texture_emission") == 0)
n = std::to_string(no_emission++);// transfer unsigned int to string
shader.setInt("material." + base_name + n, i);
glBindTexture(GL_TEXTURE_2D, textures[i].id);
}
glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glActiveTexture(GL_TEXTURE0);
}
void Mesh::destroy() const {
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ebo);
for (const auto &texture : textures)
glDeleteTextures(1, &texture.id);
}
MORE INFO: The rendering is the simple vanilla forward rendering.
400 meshes and 2 million triangles
400 different meshes? If yes, then simplify your model, that's ridiculous. If no, then use instancing. The 400 draw calls is just as ridiculous.
however when I make the window about the size of my screen (2560x1600), performance hugely drops to about 15-20 fps
While the overall architecture of your program is near unsalvageable, that sentence indicates that at least part of the problem is that you're running into your graphics card's fill rate limit (at least ignoring the large amounts of time it's doing nothing because you're too busy looping through random objects, drawing them one by one).
I am doing some computationally heavy stuff on meshes with libiGL(c++) and am trying to move these computations over to the GPU. To start off, I am trying to get a basic piece of code running but the attribute values do not seem to get passed on to the shaders.
int m = V.rows();//vertices
P.resize(m);
for (int i = 0; i < m; i++)
{
P(i) = i / m;
}
std::map<std::string, GLuint> attrib;
attrib.insert({ "concentration", 0 });
igl::opengl::create_shader_program(
mesh_vertex_shader_string,
mesh_fragment_shader_string,
attrib,
v.data().meshgl.shader_mesh);
GLuint prog_id = v.data().meshgl.shader_mesh;
const int num_vert = m;
GLuint vao[37 * 37];//37*37 vertices
glGenVertexArrays(m, vao);
GLuint buffer;
for (int i = 0; i < m; i++)
{
glGenBuffers(1, &buffer);
glBindVertexArray(vao[i]);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(float), P.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, (void*)(i*sizeof(float)));
}
The red color of each vertex should be dependant on its ordering (from 0 red to 1 red) but instead the mesh is colored with no red at all.
I am basing myself off of this code :https://github.com/libigl/libigl/issues/657 and http://www.lighthouse3d.com/cg-topics/code-samples/opengl-3-3-glsl-1-5-sample/.
I modified the shader strings accordingly by adding the following lines:
Vertex Shader
in float concentration;
out float c;
c = concentration;
where the last line is in main();
Fragment Shader
in float c;
outColor.x = c;
where the last line is in main().
What am I doing wrong?
Edit: OpenGL version used is actually 3.2
I'm a having problem loading meshes using Assimp. Some of the faces are not displayed even after ambient lighting. I'm using the code provided in the learnopengl.com tutorials for loading the mesh. I've included my Mesh and Model source below and the screenshot as well. If anyone can help with issue, I would really be very grateful.
Mesh.h
#pragma once
// Std. Includes
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
using namespace std;
// GL Includes
#include <GL/glew.h> // Contains all the necessery OpenGL includes
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
struct Vertex {
// Position
glm::vec3 Position;
// Normal
glm::vec3 Normal;
// TexCoords
glm::vec2 TexCoords;
};
struct Texture {
GLuint id;
string type;
aiString path;
};
class Mesh {
public:
/* Mesh Data */
vector<Vertex> vertices;
vector<GLuint> indices;
vector<Texture> textures;
/* Functions */
// Constructor
Mesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures)
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures;
// Now that we have all the required data, set the vertex buffers and its attribute pointers.
this->setupMesh();
}
// Render the mesh
void Draw(Shader shader)
{
// Bind appropriate textures
GLuint diffuseNr = 1;
GLuint specularNr = 1;
GLuint reflectionNr = 1;
for (GLuint i = 0; i < this->textures.size(); i++)
{
glActiveTexture(GL_TEXTURE0 + i); // Active proper texture unit before binding
// Retrieve texture number (the N in diffuse_textureN)
stringstream ss;
string number;
string name = this->textures[i].type;
if (name == "texture_diffuse")
ss << diffuseNr++; // Transfer GLuint to stream
else if (name == "texture_specular")
ss << specularNr++; // Transfer GLuint to stream
else if (name == "texture_reflection") // We'll now also need to add the code to set and bind to reflection textures
ss << reflectionNr++;
number = ss.str();
// Now set the sampler to the correct texture unit
glUniform1i(glGetUniformLocation(shader.program, (name + number).c_str()), i);
// And finally bind the texture
glBindTexture(GL_TEXTURE_2D, this->textures[i].id);
}
glActiveTexture(GL_TEXTURE0); // Always good practice to set everything back to defaults once configured.
// Also set each mesh's shininess property to a default value (if you want you could extend this to another mesh property and possibly change this value)
//glUniform1f(glGetUniformLocation(shader.Program, "material.shininess"), 16.0f);
// Draw mesh
glBindVertexArray(this->VAO);
glDrawElements(GL_TRIANGLES, this->indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
private:
/* Render data */
GLuint VAO, VBO, EBO;
/* Functions */
// Initializes all the buffer objects/arrays
void setupMesh()
{
// Create buffers/arrays
glGenVertexArrays(1, &this->VAO);
glGenBuffers(1, &this->VBO);
glGenBuffers(1, &this->EBO);
glBindVertexArray(this->VAO);
// Load data into vertex buffers
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
// A great thing about structs is that their memory layout is sequential for all its items.
// The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which
// again translates to 3/2 floats which translates to a byte array.
glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &this->vertices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indices.size() * sizeof(GLuint), &this->indices[0], GL_STATIC_DRAW);
// Set the vertex attribute pointers
// Vertex Positions
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)0);
// Vertex Normals
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Normal));
// Vertex Texture Coords
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, TexCoords));
glBindVertexArray(0);
}
};
Model.h
#pragma once
// Std. Includes
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <map>
#include <vector>
using namespace std;
// GL Includes
#include <GL/glew.h> // Contains all the necessery OpenGL includes
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <SOIL.h>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include "Mesh.h"
GLint TextureFromFile(const char* path, string directory);
class Model
{
public:
/* Functions */
// Constructor, expects a filepath to a 3D model.
Model(GLchar* path)
{
this->loadModel(path);
}
// Draws the model, and thus all its meshes
void Draw(Shader shader)
{
for (GLuint i = 0; i < this->meshes.size(); i++)
this->meshes[i].Draw(shader);
}
private:
/* Model Data */
vector<Mesh> meshes;
string directory;
vector<Texture> textures_loaded; // Stores all the textures loaded so far, optimization to make sure textures aren't loaded more than once.
/* Functions */
// Loads a model with supported ASSIMP extensions from file and stores the resulting meshes in the meshes vector.
void loadModel(string path)
{
// Read file via ASSIMP
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
// Check for errors
if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero
{
cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl;
return;
}
// Retrieve the directory path of the filepath
this->directory = path.substr(0, path.find_last_of('/'));
// Process ASSIMP's root node recursively
this->processNode(scene->mRootNode, scene);
}
// Processes a node in a recursive fashion. Processes each individual mesh located at the node and repeats this process on its children nodes (if any).
void processNode(aiNode* node, const aiScene* scene)
{
// Process each mesh located at the current node
for (GLuint i = 0; i < node->mNumMeshes; i++)
{
// The node object only contains indices to index the actual objects in the scene.
// The scene contains all the data, node is just to keep stuff organized.
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
this->meshes.push_back(this->processMesh(mesh, scene));
}
// After we've processed all of the meshes (if any) we then recursively process each of the children nodes
for (GLuint i = 0; i < node->mNumChildren; i++)
{
// Child nodes are actually stored in the node, not in the scene (which makes sense since nodes only contain
// links and indices, nothing more, so why store that in the scene)
this->processNode(node->mChildren[i], scene);
}
}
Mesh processMesh(aiMesh* mesh, const aiScene* scene)
{
// Data to fill
vector<Vertex> vertices;
vector<GLuint> indices;
vector<Texture> textures;
// Walk through each of the mesh's vertices
for (GLuint i = 0; i < mesh->mNumVertices; i++)
{
Vertex vertex;
glm::vec3 vector; // We declare a placeholder vector since assimp uses its own vector class that doesn't directly convert to glm's vec3 class so we transfer the data to this placeholder glm::vec3 first.
// Positions
vector.x = mesh->mVertices[i].x;
vector.y = mesh->mVertices[i].y;
vector.z = mesh->mVertices[i].z;
vertex.Position = vector;
// Normals
vector.x = mesh->mNormals[i].x;
vector.y = mesh->mNormals[i].y;
vector.z = mesh->mNormals[i].z;
vertex.Normal = vector;
// Texture Coordinates
if (mesh->mTextureCoords[0]) // Does the mesh contain texture coordinates?
{
glm::vec2 vec;
// A vertex can contain up to 8 different texture coordinates. We thus make the assumption that we won't
// use models where a vertex can have multiple texture coordinates so we always take the first set (0).
vec.x = mesh->mTextureCoords[0][i].x;
vec.y = mesh->mTextureCoords[0][i].y;
vertex.TexCoords = vec;
}
else
vertex.TexCoords = glm::vec2(0.0f, 0.0f);
vertices.push_back(vertex);
}
// Now wak through each of the mesh's faces (a face is a mesh its triangle) and retrieve the corresponding vertex indices.
for (GLuint i = 0; i < mesh->mNumFaces; i++)
{
aiFace face = mesh->mFaces[i];
// Retrieve all indices of the face and store them in the indices vector
for (GLuint j = 0; j < face.mNumIndices; j++)
indices.push_back(face.mIndices[j]);
}
// Process materials
if (mesh->mMaterialIndex >= 0)
{
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
// We assume a convention for sampler names in the shaders. Each diffuse texture should be named
// as 'texture_diffuseN' where N is a sequential number ranging from 1 to MAX_SAMPLER_NUMBER.
// Same applies to other texture as the following list summarizes:
// Diffuse: texture_diffuseN
// Specular: texture_specularN
// Normal: texture_normalN
// 1. Diffuse maps
vector<Texture> diffuseMaps = this->loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
// 2. Specular maps
vector<Texture> specularMaps = this->loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
// 3. Reflection maps (Note that ASSIMP doesn't load reflection maps properly from wavefront objects, so we'll cheat a little by defining the reflection maps as ambient maps in the .obj file, which ASSIMP is able to load)
vector<Texture> reflectionMaps = this->loadMaterialTextures(material, aiTextureType_AMBIENT, "texture_reflection");
textures.insert(textures.end(), reflectionMaps.begin(), reflectionMaps.end());
}
// Return a mesh object created from the extracted mesh data
return Mesh(vertices, indices, textures);
}
// Checks all material textures of a given type and loads the textures if they're not loaded yet.
// The required info is returned as a Texture struct.
vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, string typeName)
{
vector<Texture> textures;
for (GLuint i = 0; i < mat->GetTextureCount(type); i++)
{
aiString str;
mat->GetTexture(type, i, &str);
// Check if texture was loaded before and if so, continue to next iteration: skip loading a new texture
GLboolean skip = false;
for (GLuint j = 0; j < textures_loaded.size(); j++)
{
if (textures_loaded[j].path == str)
{
textures.push_back(textures_loaded[j]);
skip = true; // A texture with the same filepath has already been loaded, continue to next one. (optimization)
break;
}
}
if (!skip)
{ // If texture hasn't been loaded already, load it
Texture texture;
texture.id = TextureFromFile(str.C_Str(), this->directory);
texture.type = typeName;
texture.path = str;
textures.push_back(texture);
this->textures_loaded.push_back(texture); // Store it as texture loaded for entire model, to ensure we won't unnecesery load duplicate textures.
}
}
return textures;
}
};
GLint TextureFromFile(const char* path, string directory)
{
//Generate texture ID and load texture data
string filename = string(path);
filename = directory + '/' + filename;
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(filename.c_str(), &width, &height, 0, SOIL_LOAD_RGB);
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
// Parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureID;
}
Screenshots
1st screenshot
2nd screenshot
as I can see here
glDrawElements(GL_TRIANGLES, this->indices.size(), GL_UNSIGNED_INT, 0);
you draw all vertexes as triangles. And that is how you load indices from assimp
for (GLuint i = 0; i < mesh->mNumFaces; i++)
{
aiFace face = mesh->mFaces[i];
for (GLuint j = 0; j < face.mNumIndices; j++)
indices.push_back(face.mIndices[j]);
}
There is a problem here- face doesn't have exacly 3 vertexes (even if you use aiProcess_Triangulate when loading) which brokes drawing with GL_TRIANGLES. With aiProcess_Triangulate there will be faces with <= 3 vertexes (triangles and lines for example). You may ignore not triangle faces and that should fix drawing. Here is a fixed indeces filling cycle:
for (GLuint i = 0; i < mesh->mNumFaces; i++)
{
aiFace face = mesh->mFaces[i];
if (face.mNumIndices < 3) {
continue;
}
assert(face.mNumIndices == 3);
for (GLuint j = 0; j < face.mNumIndices; j++)
indices.push_back(face.mIndices[j]);
}
I know this is old, but as I had the same problem, I thought this might help other people as well:
In my case my texture coordinates were not correct and the problem was in the lines
vertices.push_back(mesh->mTextureCoords[0][i].x);
vertices.push_back(mesh->mTextureCoords[0][i].y);
In this case, we iterate over all vertices of an aiScene.
This lead in my case to your bug where not all faces were displayed.
Adding vertices via faces, however, fixed the issue for me:
const aiScene *tree = _importer.ReadFile(path, aiProcess_CalcTangentSpace | aiProcess_Triangulate | aiProcess_JoinIdenticalVertices | aiProcess_SortByPType);
// iterate over all meshes in this scene
for (unsigned int m = 0; m < tree->mNumMeshes; ++m) {
const aiMesh *mesh = tree->mMeshes[m];
// iterate over all faces in this mesh
for (unsigned int j = 0; j < mesh->mNumFaces; ++j) {
auto const &face = mesh->mFaces[j];
//normally you want just triangles, so iterate over all 3 vertices of the face:
for (int k = 0; k < 3; ++k) {
// Now do the magic with 'face.mIndices[k]'
auto const &vertex = mesh->mVertices[face.mIndices[k]];
vertices.push_back(vertex.x);
vertices.push_back(vertex.y);
vertices.push_back(vertex.z);
// Same for the normals.
auto const &normal = mesh->mNormals[face.mIndices[k]];
vertices.push_back(normal.x);
vertices.push_back(normal.y);
vertices.push_back(normal.z);
// Color of material
// ...
// And FINALLY: The UV coordinates!
if(mesh->HasTextureCoords(0)) {
// The following line fixed the issue for me now:
auto const &uv = mesh->mTextureCoords[0][face.mIndices[k]];
vertices.push_back(uv.x);
vertices.push_back(uv.y);
}
}
}
}
I'm having a really weird issue with depth testing here.
I'm rendering a simple mesh in an OpenGL 3.3 core profile context on Windows, with depth testing enabled and glDepthFunc set to GL_LESS. On my machine (a laptop with a nVidia Geforce GTX 660M), everything is working as expected, the depth test is working, this is what it looks like:
Now, if I run the program on a different PC, a tower with a Radeon R9 280, it looks more like this:
Strange enough, the really weird thing is that when I call glEnable(GL_DEPTH_TEST) every frame before drawing, the result is correct on both machines.
As it's working when I do that, I figure the depth buffer is correctly created on both machines, it just seems that the depth test is somehow being disabled before rendering when I enable it only once at initialization.
Here's the minimum code that could somehow be part of the problem:
Code called at initialization, after a context is created and made current:
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
Code called every frame before the buffer swap:
glClearColor(0.4f, 0.6f, 0.8f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// mShaderProgram->getID() simply returns the handle of a simple shader program
glUseProgram(mShaderProgram->getID());
glm::vec3 myColor = glm::vec3(0.7f, 0.5f, 0.4f);
GLuint colorLocation = glGetUniformLocation(mShaderProgram->getID(), "uColor");
glUniform3fv(colorLocation, 1, glm::value_ptr(myColor));
glm::mat4 modelMatrix = glm::mat4(1.0f);
glm::mat4 viewMatrix = glm::lookAt(glm::vec3(0.0f, 3.0f, 5.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 projectionMatrix = glm::perspectiveFov(60.0f, (float)mWindow->getProperties().width, (float)mWindow->getProperties().height, 1.0f, 100.0f);
glm::mat4 inverseTransposeMVMatrix = glm::inverseTranspose(viewMatrix*modelMatrix);
GLuint mMatrixLocation = glGetUniformLocation(mShaderProgram->getID(), "uModelMatrix");
GLuint vMatrixLocation = glGetUniformLocation(mShaderProgram->getID(), "uViewMatrix");
GLuint pMatrixLocation = glGetUniformLocation(mShaderProgram->getID(), "uProjectionMatrix");
GLuint itmvMatrixLocation = glGetUniformLocation(mShaderProgram->getID(), "uInverseTransposeMVMatrix");
glUniformMatrix4fv(mMatrixLocation, 1, GL_FALSE, glm::value_ptr(modelMatrix));
glUniformMatrix4fv(vMatrixLocation, 1, GL_FALSE, glm::value_ptr(viewMatrix));
glUniformMatrix4fv(pMatrixLocation, 1, GL_FALSE, glm::value_ptr(projectionMatrix));
glUniformMatrix4fv(itmvMatrixLocation, 1, GL_FALSE, glm::value_ptr(inverseTransposeMVMatrix));
// Similiar to the shader program, mMesh.gl_vaoID is simply the handle of a vertex array object
glBindVertexArray(mMesh.gl_vaoID);
glDrawArrays(GL_TRIANGLES, 0, mMesh.faces.size()*3);
With the above code, I'll get the wrong output on the Radeon.
Note: I'm using GLFW3 for context creation and GLEW for the function pointers (and obviously GLM for the math).
The vertex array object contains three attribute array buffers, for positions, uv coordinates and normals. Each of these should be correctly configured and send to the shaders, as everything is working fine when enabling the depth test every frame.
I should also mention that the Radeon machine runs Windows 8 while the nVidia machine runs Windows 7.
Edit: By request, here's the code used to load the mesh and create the attribute data. I do not create any element buffer objects as I am not using element draw calls.
std::vector<glm::vec3> positionData;
std::vector<glm::vec2> uvData;
std::vector<glm::vec3> normalData;
std::vector<meshFaceIndex> faces;
std::ifstream fileStream(path);
if (!fileStream.is_open()){
std::cerr << "ERROR: Could not open file '" << path << "!\n";
return;
}
std::string lineBuffer;
while (std::getline(fileStream, lineBuffer)){
std::stringstream lineStream(lineBuffer);
std::string typeString;
lineStream >> typeString; // Get line token
if (typeString == TOKEN_VPOS){ // Position
glm::vec3 pos;
lineStream >> pos.x >> pos.y >> pos.z;
positionData.push_back(pos);
}
else{
if (typeString == TOKEN_VUV){ // UV coord
glm::vec2 UV;
lineStream >> UV.x >> UV.y;
uvData.push_back(UV);
}
else{
if (typeString == TOKEN_VNORMAL){ // Normal
glm::vec3 normal;
lineStream >> normal.x >> normal.y >> normal.z;
normalData.push_back(normal);
}
else{
if (typeString == TOKEN_FACE){ // Face
meshFaceIndex faceIndex;
char interrupt;
for (int i = 0; i < 3; ++i){
lineStream >> faceIndex.positionIndex[i] >> interrupt
>> faceIndex.uvIndex[i] >> interrupt
>> faceIndex.normalIndex[i];
}
faces.push_back(faceIndex);
}
}
}
}
}
fileStream.close();
std::vector<glm::vec3> packedPositions;
std::vector<glm::vec2> packedUVs;
std::vector<glm::vec3> packedNormals;
for (auto f : faces){
Face face; // Derp derp;
for (auto i = 0; i < 3; ++i){
if (!positionData.empty()){
face.vertices[i].position = positionData[f.positionIndex[i] - 1];
packedPositions.push_back(face.vertices[i].position);
}
else
face.vertices[i].position = glm::vec3(0.0f);
if (!uvData.empty()){
face.vertices[i].uv = uvData[f.uvIndex[i] - 1];
packedUVs.push_back(face.vertices[i].uv);
}
else
face.vertices[i].uv = glm::vec2(0.0f);
if (!normalData.empty()){
face.vertices[i].normal = normalData[f.normalIndex[i] - 1];
packedNormals.push_back(face.vertices[i].normal);
}
else
face.vertices[i].normal = glm::vec3(0.0f);
}
myMesh.faces.push_back(face);
}
glGenVertexArrays(1, &(myMesh.gl_vaoID));
glBindVertexArray(myMesh.gl_vaoID);
GLuint positionBuffer; // positions
glGenBuffers(1, &positionBuffer);
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3)*packedPositions.size(), &packedPositions[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
GLuint uvBuffer; // uvs
glGenBuffers(1, &uvBuffer);
glBindBuffer(GL_ARRAY_BUFFER, uvBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2)*packedUVs.size(), &packedUVs[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);
GLuint normalBuffer; // normals
glGenBuffers(1, &normalBuffer);
glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3)*packedNormals.size(), &packedNormals[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
The .obj loading routine is mostly adapted from this one:
http://www.limegarden.net/2010/03/02/wavefront-obj-mesh-loader/
This doesn't look like a depth testing issue to me, but more like misalignment in the vertex / index array data. Please show us the code in which you load the vertex buffer objects and the element buffer objects.
It is because of the function ChoosePixelFormat.
In my case the ChoosePixelFormat returns a pixelformat ID with value 8 which provides a depth buffer with 16 bits instead of the required 24 bits.
One simple fix was to set the ID manually to the value of 11 instead of 8 to get a suitable pixelformat for the application with 24 bits of depth-buffer.