0

I am trying to load OBJ files to use them in OpenGL. Every object in the OBJ file has its own texture. In my code I split up every object of the file into a struct that is defended like this:

struct ObjModelComponent {
    std::vector<glm::vec3> vertices;
    std::vector<glm::vec2> uvs;
    std::vector<glm::vec3> normals;

    Texture tex;
    ArrayBuffer vertex, uv, normal;
    GLuint VAO;
};

The Texture class is a simple class to make loading and handling a texture easier.

In the class of the model I have a std::vector of my ObjModelComponent.

I'm rendering the Object like this:

void ObjModel::render() {
    for(int i = 0; i < components.size(); i++) {
        components[i].vertex.activate();
        components[i].uv.activate();
        components[i].normal.activate();

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, components[i].tex.getTextureID());
        shader->sendInt(0, components[i].tex.getTextureName());

        glBindVertexArray(components[i].VAO);
        shader->sendMat4(*data->projection, "projection");
        shader->sendMat4(data->viewMat, "view");
        shader->sendMat4(model, "model");

        glDrawArrays(GL_TRIANGLES, 0, int(components[i].vertices.size()));
        glBindVertexArray(0);
    }
}

The Texture class looks like this:

Texture::Texture(std::string fileName, TextureType type):
textureName("tex"), fileName(fileName), type(type) {
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);

    unsigned char *image = SOIL_load_image(fileName.c_str(), &texWidth, &texHeight, 0, SOIL_LOAD_RGBA);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    switch (type) {
        case TEXTURE_GENERATE_MIPMAP:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);

            glGenerateMipmap(GL_TEXTURE_2D);
            break;

        case TEXTURE_NO_MIP_MAP:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        default:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            break;
    }

    glBindTexture(GL_TEXTURE_2D, 0);

    SOIL_free_image_data(image);
}

Texture::Texture() {

}

Texture& Texture::operator=(const Texture &other) {
    this->fileName = other.fileName;
    this->textureName = other.textureName;
    this->type = other.type;

//    glDeleteTextures(1, &tex);

    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);

    unsigned char *image = SOIL_load_image(fileName.c_str(), &texWidth, &texHeight, 0, SOIL_LOAD_RGBA);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    switch (type) {
        case TEXTURE_GENERATE_MIPMAP:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);

            glGenerateMipmap(GL_TEXTURE_2D);
            break;

        case TEXTURE_NO_MIP_MAP:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        default:
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            break;
    }

    glBindTexture(GL_TEXTURE_2D, 0);

    SOIL_free_image_data(image);

    return *this;
}

Texture::~Texture() {
    glDeleteTextures(1, &tex);
}

GLuint Texture::getTextureID() {
    return tex;
}

void Texture::setTextureName(std::string name) {
    textureName = name;
}

std::string Texture::getTextureName() {
    return textureName;
}

glm::vec2 Texture::getTextureSize() {
    return glm::vec2(texWidth, texHeight);
}

This is how the components are generated:

    for(int i = 0; i < fileLines.size(); i++) {
            if(fileLines[i].substr(0, 2) == "o ") {
                if(!first) {
                    tmpComp.tex = tmpTex;
                    components.emplace_back(tmpComp);
                    componentIndex++;
                }

                first = false;

                tmpTex = Texture(hg::substr(path, 0, int(path.find_last_of("/"))) + "/" + hg::substr(fileLines[i], 2, int(fileLines[i].length())) + ".png");
            }
    }

This is how the VAOs and array buffers are generated:

for(int i = 0; i < components.size(); i++) {
        glGenVertexArrays(1, &components[i].VAO);
        glBindVertexArray(components[i].VAO);

        components[i].vertex.setData(components[i].vertices.data(), sizeof(glm::vec3) * components[i].vertices.size(), 0);
        components[i].uv.setData(components[i].uvs.data(), sizeof(glm::vec2) * components[i].uvs.size(), 1);
        components[i].normal.setData(components[i].normals.data(), sizeof(glm::vec3) * components[i].normals.size(), 2);

        components[i].vertex.activate();
        components[i].uv.activate();
        components[i].normal.activate();

        glBindVertexArray(0);
    }

You can find my complete code on GitHub: https://github.com/Kuechenzwiebel/SDL-OpenGL-Tests

The problem is, that the texture displayed on my object is that, that was used by the last object that was rendered.

Update: Removing the glDeleteTextures in the destructor of the Texture, yields in the result, that the texture displayed on all components is that that was used for the last component.

I really don't understand why this isn't working, I copied it from other primitives, that only use one texture. And there everything is working fine.

genpfault
  • 51,148
  • 11
  • 85
  • 139
user11914177
  • 885
  • 11
  • 33
  • 1
    Do you generate and use a separate object (name) value for each texture ([`glGenTextures`](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGenTextures.xhtml))? – Rabbid76 Sep 03 '19 at 12:10
  • @Rabbid76 yes, I do. Also I have several primitives, each using one texture, that are working good. – user11914177 Sep 03 '19 at 12:13
  • Have you check if the problem is in the loading? (storing in the wrong `components[]` position, duplicate texture name, etc.) – Nadir Sep 03 '19 at 14:54
  • Please add the relevant code of these classes in use. It is quite possible that you shot yourself into the foot by doing `glDeleteTextures()` in `Texture::~Texture()` or something similar. – derhass Sep 03 '19 at 15:13
  • @derhass I added the Texture class – user11914177 Sep 03 '19 at 17:56
  • @Nadir Probably not, I also used a array I created by my self and it had the same result. – user11914177 Sep 03 '19 at 17:57
  • Well, the code is still not complete enough to really diagnose the issue, but it supports my previous hypothesis that you might have something of the form `struct ObjModelComponent foo; foo.tex=Texture(filename,...);` which will result in `foo.tex` having the previous name of a GL texture object which already will have been deleted from the GL context when the destructor for the temporary object was called after the copy assignment. Note that you might indirectly reach such a situation by using `components.push_back()`. – derhass Sep 03 '19 at 18:10
  • Also note that due to lack of proper initialization, your texture destructor might delete random textures from the GL if it is destoyed before a texture is loaded. – derhass Sep 03 '19 at 18:11
  • @derhass What code do you need for a complete diagnose? What would be a proper initialization? I'm using emplace_back(), not push_back(). Also if I remove the glDeleteTextures and run the program, I see the texture of the last component on all components. – user11914177 Sep 03 '19 at 18:29
  • 1
    You need to supply a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – derhass Sep 03 '19 at 18:39
  • @derhass Yes obviously. But can you roughly say what classes or function you need? – user11914177 Sep 03 '19 at 18:57
  • That's the point of completeness: add all the source code which is necessary to reproduce the issue. With the code snippets given so far, I can not do that, and I do suspect that the issue actually lies in those parts you omitted. – derhass Sep 03 '19 at 19:22
  • @derhass Ok, so because my question already is quite large, I give you a link to my git repo. https://github.com/Kuechenzwiebel/SDL-OpenGL-Tests – user11914177 Sep 03 '19 at 19:27
  • 1
    But that is not how this site works. The _question itself_ should contain all the information. That's also why you should create a _minimal_ example that reproduces the issue (which is a good debugging strategy in any case). – derhass Sep 03 '19 at 19:29
  • @derhass Ok, so I tried my best, but please say what you need! I can't know what you need if you don't write it. – user11914177 Sep 03 '19 at 19:39
  • Well, the way you use `tmpTex` actually has the issue I spoke of before (just in a different way, when it is destructed once the function is left), but this will not make a noticable difference, since in your actual github code, you're not using `components[i].tex` at all for the rendering, but `textures[i]`, which makes no sense at all. So your code in the question is neither complete nor minimal, it also differs from your actual code in ways which might completely remove any reproducibility. – derhass Sep 03 '19 at 22:11
  • @derhass I used the textures array, to make sure the problem isn't a array of textures, I now use the textures in the component with the same result. It seems your right about your theory that the textures get deleted. If I remove the glDeleteTextures in the destructor, all components of my object get the texture that is used by the last component. Do you have any ideas on that? – user11914177 Sep 04 '19 at 06:46
  • 1
    Well, my guess is that your `components.emplace_back(tmpComp)` calls the implicit copy constructor for `ObjModelComponent`, which in turn calls the implicit copy constructor for `Texture`, which means that both `components[i].tex` and `tmComp` now refer the same GL texture object. Later when you assign something else to `tmpComp.tex`, `Texture::operator=()` will delete this very GL texture object, and create a new one (likely with the same name than the one just deleted, so all of the previous components refer to the very same texture object, which was last loaded). – derhass Sep 04 '19 at 13:23
  • Note that this is only a guess, and it is not clrear what really might happen, since you do not provide a minimal, complete, example. But what I can say is the way you wrap GL objects in C++ classes, and the way you are using these classes, seems horribly broken to me. – derhass Sep 04 '19 at 13:24
  • @derhass I'm sorry to not include the copy operator. I will add the copy constructor. But your right, I'm destroying the "old" texture and generate a new one. But this doesn't seem to be the main problem. Even when manually using different textures I still only see the last texture on all objects. Do you have an idea why that is happening. The strange thing is, that multiple instances of simple objects (for example cubes) can have different textures with no problem. – user11914177 Sep 04 '19 at 21:33
  • @derhass So even when writing the render function completely without a for loop or an array of textures, there is the same error. – user11914177 Sep 04 '19 at 21:40

1 Answers1

0

I found a workaround, instead of having separate ArrayBuffers and VAOs, I only use one of each and also store at which index a new object begins. There I change the texture that is used.

user11914177
  • 885
  • 11
  • 33