Dislaimer : this is a learning project, I'm trying to learn both c++ and 3d rendering with opengl :)
I've setup a project with BGFX and GLFW, managed to initalize both and render basic shapes to the screen. I now wanted to load arbitraty Wavefront .OBJ files (exported from blender), so I created a basic .OBJ loader, that allows me to create a "Mesh" for every group in the .OBJ, and adds them to a "Model" (so 1 .OBJ file = 1 Model = 1+ Meshes) :
.OBJ Loader :
static std::vector<Group> loadOBJ(const std::string& file_path)
{
std::stringstream ss;
std::ifstream in_file(file_path);
std::string line = "";
std::string prefix = "";
if (!in_file.is_open()) {
global.logger->getCoreLogger()->error("Could not open file at : {0}", file_path);
throw std::runtime_error("Error when trying to open a file.");
}
glm::vec3 temp_vec3;
glm::vec2 temp_vec2;
GLint temp_glint = 0;
std::string temp_str;
//Vertex positions
std::vector<glm::fvec3> vertex_positions;
std::vector<glm::fvec2> vertex_texCoords;
std::vector<glm::fvec3> vertex_normals;
std::vector<Group> groups;
Group* currentGroup = 0;
while (std::getline(in_file, line)) {
ss.clear();
ss.str(line);
ss >> prefix;
if (prefix == "#")
{
}
else if (prefix == "mtllib")
{
ss >> temp_str;
loadMaterials(getMtlDirectoryPath(temp_str.c_str()));
}
else if (prefix == "g")
{
Group g;
groups.push_back(g);
currentGroup = &groups[groups.size() - 1];
}
else if (prefix == "o")
{
}
else if (prefix == "s")
{
}
else if (prefix == "usemtl" && currentGroup)
{
ss >> temp_str;
currentGroup->materialName = temp_str;
}
//Vertex position
else if (prefix == "v" && currentGroup)
{
ss >> temp_vec3.x >> temp_vec3.y >> temp_vec3.z;
vertex_positions.push_back(temp_vec3);
}
else if (prefix == "vt" && currentGroup)
{
ss >> temp_vec2.x >> temp_vec2.y;
vertex_texCoords.push_back(temp_vec2);
}
else if (prefix == "vn" && currentGroup)
{
ss >> temp_vec3.x >> temp_vec3.y >> temp_vec3.z;
vertex_normals.push_back(temp_vec3);
}
else if (prefix == "f" && currentGroup)
{
int counter = 0;
while(ss >> temp_glint)
{
//Pushing indices into arrays
if (counter == 0)
currentGroup->vertex_positions_indices.push_back(temp_glint);
else if (counter == 1)
currentGroup->vertex_texCoords_indices.push_back(temp_glint);
else if (counter == 2)
currentGroup->vertex_normals_indices.push_back(temp_glint);
//Handling characters
if (ss.peek() == '/')
{
++counter;
ss.ignore(1, '/');
}
else if (ss.peek() == ' ')
{
++counter;
ss.ignore(1, ' ');
}
//Reset the counter every 3 indices
if (counter > 2) counter = 0;
}
}
else
{
}
}
for (auto &group : groups)
{
//Build final vertex array
group.vertices.resize(group.vertex_positions_indices.size(), Vertex());
for (std::vector<Vertex>::size_type i = 0; i != group.vertices.size(); i++)
{
group.vertices[i].position = vertex_positions[group.vertex_positions_indices[i] - 1];
group.vertices[i].texCoord = vertex_texCoords[group.vertex_texCoords_indices[i] - 1];
group.vertices[i].normal = vertex_normals[group.vertex_normals_indices[i] - 1];
}
global.logger->getCoreLogger()->info("This mesh has {0} vertices ({1} triangles).", group.vertices.size(), group.vertices.size() / 3);
}
global.logger->getCoreLogger()->info("Created model with {0} mesh(es).", groups.size());
return groups;
}
Model initialization:
void Model::loadModel(const char* model_name) {
std::vector<Group> temp_groups = loadOBJ(getObjectDirectoryPath(model_name));
for(Group g : temp_groups) {
Mesh mesh;
mesh.initialize(g.vertices);
if(!g.materialName.empty())
{
mesh.setMaterial(g.materialName);
}
m_meshes.push_back(mesh);
}
m_is_initialized = true;
}
Mesh class:
void Mesh::initialize(std::vector<Vertex> v) {
m_vertices = v;
const bgfx::VertexLayout vertexLayout = Vertex::getVertexLayout();
m_vertex_buffer_handle = bgfx::createVertexBuffer(bgfx::makeRef(m_vertices.data(), sizeof(Vertex) * m_vertices.size()), vertexLayout);
m_is_initialized = true;
}
bgfx::VertexBufferHandle Mesh::getVertexBuffer() {
return m_vertex_buffer_handle;
}
Vertex class:
struct Vertex
{
glm::vec3 position;
glm::vec2 texCoord;
glm::vec3 normal;
static bgfx::VertexLayout getVertexLayout()
{
bgfx::VertexLayout vertex_layout;
vertex_layout.begin()
.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
.add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float)
.add(bgfx::Attrib::Normal, 3, bgfx::AttribType::Float)
.end();
return vertex_layout;
}
};
Rendering a Model:
void Renderer::renderMesh(Mesh* mesh) {
if (mesh->isInitialized()) {
bgfx::setVertexBuffer(0, mesh->getVertexBuffer());
bgfx::submit(0, m_program);
} else {
//error handling
}
}
void Renderer::renderModel(Model* model) {
if (model->isInitialized()) {
for (Mesh mesh : model->getMeshes()) {
renderMesh(&mesh);
}
} else {
//error handling
}
}
Now to the problem:
It seems that when I load a model with multiple meshes, like this "grass" :
Only some are rendered to the screen, even though they are correctly sent to the GPU as we can see in BGFX debug info :
I can also see that there are (random?) big shapes that are surely not part of the original model, like on the right side of the screenshot.
When trying to render a more "complex" model, more problems start to appear and I'm not sure how to handle them either :
The two ball-like shape are correct, one is missing, and the triangle mess under them is supposed to be a trunk ...
And zooming / rotating around the model with basic camera control, missing shapes don't seem to be anywhere else
Some thoughts :
- I'm not using indicies, vertices are duplicated in the vertexArray for each face (vertex also contains normals and texture coords so I thought this was the way to go)
- Shaders are from this BGFX template project, only slightly modified to suit my Vertex structure
Edit :
- Additional thought : The randomness makes it wierd, it's not always the same part of the model that doesn't render, sometimes there is only the 1st mesh, sometimes more as shown in the screenshot above
I'm kinda lost here so any help will be greatly appreciated, and I'll gladly add other snippets of code parts if you want more details :)