0

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" : Grass model, viewed in Blender

Only some are rendered to the screen, even though they are correctly sent to the GPU as we can see in BGFX debug info : Engine rendering grass screenshot 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 : Engine rendering of a low poly tree 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 :)

JulienElM
  • 1
  • 1
  • multiple meshes aren't really a thing in obj files. Its just one giant vertex array, and a set of indices to reference them. If you are splitting the meshes, have you correctly offset all of the vertex indices? (e.g. -1 for model0, -1 - model0.numVertices, for mode1, etc?) – robthebloke Oct 30 '22 at 01:53
  • @robthebloke I understood that this was the way to go since each mesh may have a different material, but i'm open to suggestions :) Not sure I understood the second part of the comment, vertex/tex/normals coords are shared between all the meshes, are you referring to indicies after the face (f) prefix ? – JulienElM Oct 30 '22 at 08:45
  • Yup. If mesh0 has 100 vertices, the second mesh indices will start at 101. – robthebloke Oct 30 '22 at 22:26
  • @robthebloke Looking back at my code, I believe I got this part correctly (I added the .OBJ parser on the post if you want to take a look). Could it be that some info is lost/corrupted when I transfer it to the gpu ? Or it is expecting it in a different format maybe ? – JulienElM Oct 31 '22 at 23:14
  • I've also uploaded the project on a [github repo](https://github.com/JulienElM/EDN_ENGINE/tree/master) – JulienElM Oct 31 '22 at 23:51
  • There are no obj files in the repo to confirm, but your 'f' handling in your obj loader seems suss. It doesn't handle quads: "f 1/1/1 2/2/2 3/3/3 4/4/4", or cases where no indices exist: "f 1//1 2//2 3//3" or "f 1 2 3" or "f 1/1/ 2/2/ 3/3/ 4/4/". – robthebloke Nov 01 '22 at 05:10
  • Oh yeah, forgot that they are excluded sorry, Ive added the files for the grass example above ! Quads => For now everything is triangulated so I didn't handle the case, and as for the 'f' cases, I was aware that those cases would have to be handled, but right now even with .obj files that don't have these kind of special/tricky faces it does't properly display them. – JulienElM Nov 01 '22 at 13:47

0 Answers0