Search code examples
c++opengltextures

Can't load multiple texture on OpenGL


I'm trying to load multiple textures in openGL. To validate this I want to load 2 textures and mix them with the following fragment shader:

#version 330 core

out vec4 color;
in vec2 v_TexCoord;

uniform sampler2D u_Texture0;
uniform sampler2D u_Texture1;

void main()
{
    color = mix(texture(u_Texture0, v_TexCoord), texture(u_Texture1, v_TexCoord), 0.5);
}

I'have abstract couple of OpenGL's functionality into classes like Shader, Texture UniformXX etc..

Here's an attempt to load the 2 textures into the sampler units of the fragment:

        Shader shader;
        shader.Attach(GL_VERTEX_SHADER, "res/shaders/vs1.shader");
        shader.Attach(GL_FRAGMENT_SHADER, "res/shaders/fs1.shader");
        shader.Link();
        shader.Bind();

        Texture texture0("res/textures/container.jpg", GL_RGB, GL_RGB);
        texture0.Bind(0);

        Uniform1i textureUnit0Uniform("u_Texture0");
        textureUnit0Uniform.SetValues({ 0 });
        shader.SetUniform(textureUnit0Uniform);

        Texture texture1("res/textures/awesomeface.png", GL_RGBA, GL_RGBA);
        texture1.Bind(1);

        Uniform1i textureUnit1Uniform("u_Texture1");
        textureUnit1Uniform.SetValues({ 1 });
        shader.SetUniform(textureUnit1Uniform);

Here's what the Texture implementation looks like:

#include "Texture.h"
#include "Renderer.h"
#include "stb_image/stb_image.h"

Texture::Texture(const std::string& path, unsigned int destinationFormat, unsigned int sourceFormat)
    : m_Path(path)
{
    stbi_set_flip_vertically_on_load(1);
    m_Buffer = stbi_load(path.c_str(), &m_Width, &m_Height, &m_BPP, 0);

    GLCALL(glGenTextures(1, &m_RendererID));
    GLCALL(glBindTexture(GL_TEXTURE_2D, m_RendererID));
    GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
    GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
    GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));
    GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, destinationFormat, m_Width, m_Height, 0, sourceFormat, GL_UNSIGNED_BYTE, m_Buffer));
    glGenerateMipmap(GL_TEXTURE_2D);
    GLCALL(glBindTexture(GL_TEXTURE_2D, 0));

    if (m_Buffer)
        stbi_image_free(m_Buffer);
}

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

void Texture::Bind(unsigned int unit) const
{
    GLCALL(glActiveTexture(GL_TEXTURE0 + unit));
    GLCALL(glBindTexture(GL_TEXTURE_2D, m_RendererID));
}

void Texture::Unbind() const
{
    GLCALL(glBindTexture(GL_TEXTURE_2D, 0));
}

Now instead of actually getting an even mix of color from both textures I only get the second texture appearing and blending with the background: enter image description here

I've pinpointed the problem to the constructor of the Texture implementation, if I comment out the initialization of the second texture such as that its constructor is never being called then I can get the first texture to show up.

Can anyone suggest what I'm doing wrong?


Solution

  • Took me a while to spot, but at the point where you call the constructor of the second texture, your active texture unit is still 0, so the constructor happily repoints your texture unit and you are left with two texture units bound to the same texture.

    The solution should be simple enough: do not interleave texture creation and texture unit assignment, by creating the textures first and only then binding them explicitly.

    Better yet, look into using direct state access to avoid all this binding.

    To highlight the problem for future viewers of this question, this is the problematic sequence of calls:

    // constructor of texture 1
    glGenTextures(1, &container)
    glBindTexture(GL_TEXTURE_2D, container) // Texture Unit 0 is now bound to container
    
    // explicit texture0.Bind call
    glActiveTexture(GL_TEXTURE0) // noop
    glBindTexture(GL_TEXTURE_2D, container) // Texture Unit 0 is now bound to container
    
    // constructor of texture 2
    glGenTextures(1, &awesomeface)
    glBindTexture(GL_TEXTURE_2D, awesomeface) // Texture Unit 0 is now bound to awesomeface instead of container.
    
    // explicit texture1.Bind call
    glActiveTexture(GL_TEXTURE1)
    glBindTexture(GL_TEXTURE_2D, awesomeface) // Texture Unit 0 and 1 are now bound to awesomeface.