Search code examples
c++opengltexturessoil

SOIL2 don't load grayscale texture


I am new to OpenGL and am learning it at the moment.

Using SOIL2, I'm loading a texture and trying to render it on the screen. I load the texture like this:

GLuint texture;
texture = SOIL_load_OGL_texture(("resources/textures/" + fileName + ".png").c_str(),
   SOIL_LOAD_AUTO, //Problem
   SOIL_CREATE_NEW_ID,
   SOIL_FLAG_INVERT_Y | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_NTSC_SAFE_RGB);

glBindTexture(GL_TEXTURE_2D, texture);
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, NULL);

The problem is that when I try to load a texture with SOIL_LOAD_AUTO or SOIL_LOAD_LA, I get a black square. That being said, if I use SOIL_LOAD_RGBA, the grayscale texture is displayed.

Internet searches turn up nothing, and I'm inclined to believe that I don't understand how the "Greyscale" mode works.

At the moment my code looks like this (Yes, it's a bit wrong, but right now I'm trying to understand how OpenGL buffers, texture loading and shaders work, so I didn't do everything perfectly):

Render:

void Render::runGame() {
    logger->info("Running content rendering...");

    glfwShowWindow(window);

    World world(width, height, &logger);

    GLfloat data[] = {
        0.0f, 0.0f,  1.0f, 0.0f, 0.0f, 1.0f,  0.0f, 1.0f, //Top left
        0.0f, 0.0f,  0.0f, 1.0f, 0.0f, 1.0f,  0.0f, 0.0f, //Bottom left
        0.0f, 0.0f,  0.0f, 0.0f, 1.0f, 1.0f,  1.0f, 1.0f, //Top right
        0.0f, 0.0f,  1.0f, 0.0f, 1.0f, 1.0f,  1.0f, 0.0f  //Bottom right
    };
    GLubyte edata[6] = { 0, 1, 2, 2, 1, 3 };

    world.getNormalizedCoord(0, height, 0, data);
    world.getNormalizedCoord(0, 0, 8, data);
    world.getNormalizedCoord(width, height, 16, data);
    world.getNormalizedCoord(width, 0, 24, data);

    GLuint VBO;
    glGenBuffers(1, &VBO);

    GLuint EBO;
    glGenBuffers(1, &EBO);

    GLuint VAO;
    glGenVertexArrays(1, &VAO);

    glBindVertexArray(VAO);

        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(edata), edata, GL_STATIC_DRAW);

        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*) 0);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*) (2 * sizeof(GLfloat)));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*) (6 * sizeof(GLfloat)));
        glEnableVertexAttribArray(2);

    glBindVertexArray(NULL);
    
    Shader shader = Shader("tile", &logger);
    shader.create();
    
    Texture texture = Texture("tile", &logger);
    texture.create();

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    while (!glfwWindowShouldClose(window)) {
        glfwPollEvents();

        glClear(GL_COLOR_BUFFER_BIT);

        if (texture.use() && shader.use()) {
            glBindVertexArray(VAO);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, NULL);
            glBindVertexArray(NULL);
        }

        shader.free();
        texture.free();

        glfwSwapBuffers(window);
    }

    destroy();
}

Texture class:


//Constructor...

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

void Texture::create() {
    if (hasLogger()) logger->info("Loading «" + fileName + "» texture...");

    if (isCreated()) {
        if (hasLogger()) logger->warn("Attempt to create texture «" + fileName + "» that is already created");
        return;
    }

    GLuint texture;
    texture = SOIL_load_OGL_texture(("resources/textures/" + fileName + ".png").c_str(),
        SOIL_LOAD_AUTO, //Problem
        SOIL_CREATE_NEW_ID,
        SOIL_FLAG_INVERT_Y | SOIL_FLAG_MULTIPLY_ALPHA | SOIL_FLAG_NTSC_SAFE_RGB);

    if (texture == 0) {
        if (hasLogger()) logger->fatal("Failed to create texture «" + fileName + "»: " + SOIL_last_result());
        return;
    }

    glBindTexture(GL_TEXTURE_2D, texture);
    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);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);

    this->texture = texture;
    created = true;
}

GLuint Texture::getTexture() const noexcept { return texture; }

bool Texture::use() const {
    if (!isCreated()) {
        if (hasLogger()) logger->warn("Texture «" + fileName + "» using before creating");
        return false;
    }
    else {
        glBindTexture(GL_TEXTURE_2D, texture);
        return true;
    }
}

void Texture::free() const noexcept { glBindTexture(GL_TEXTURE_2D, NULL); }

bool Texture::isCreated() const noexcept { return created; }
bool Texture::hasLogger() const noexcept { return logger != nullptr; }

Verticies shader:

#version 330 core

layout (location = 0) in vec2 position;
layout (location = 1) in vec4 color;
layout (location = 2) in vec2 texCoord;

out vec4 fragColor;
out vec2 fragTexCoord;

void main() {
    gl_Position = vec4(position, 0.0f, 1.0f);
    fragColor = color;
    fragTexCoord = texCoord;
}

Fragment shader:

#version 330 core

in vec4 fragColor;
in vec2 fragTexCoord;

out vec4 color;

uniform sampler2D fragTexture;

void main() {
    color = texture(fragTexture, fragTexCoord);
}

Texture: enter image description here

And my result with with SOIL_LOAD_AUTO or SOIL_LOAD_LA enter image description here

The result I want to get, at the moment it's only possible with SOIL_LOAD_RGBA: enter image description here

I really do not understand what's the matter, what could be the reason for such strange behavior?

P.S. My OpenGL version is 3.3 core profile and forward compatibility

SOLUTION:

I followed the advice from the accepted answer and abandoned SOIL. I used std_image instead, and loaded the texture into OpenGL correctly. My render code hasn't changed, and the texture loading code now looks like this:

void Texture::create() noexcept {
    if (hasLogger()) logger->info("Loading «" + fileName + "» texture...");

    if (isCreated()) {
        if (hasLogger()) logger->warn("Attempt to create texture «" + fileName + "» that is already created");
        return;
    }

    int width, height, nrChannels;
    stbi_set_flip_vertically_on_load(true); //flip y for opengl render correctly
    stbi_uc* data = stbi_load(("resources/textures/" + fileName + ".png").c_str(), &width, &height, &nrChannels, 0);

    if (data) {
        GLuint texture;

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

        GLenum format;
        switch (nrChannels) {
            case 1: { //L
                format = GL_RED;
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED); //Texture swizzle parameters
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED); //for correct store and render
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); //image with different channels
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
                break;
            }
            case 2: { //LA
                format = GL_RG;
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_RED);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_GREEN);

                for (int i = 0; i < 2 * width * height; i += 2) { //Multiply alpha
                    data[i] = (data[i] * data[i + 1] + 128) >> 8;
                }

                break;
            }
            case 3: default: { //RGB
                format = GL_RGB;
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
                break;
            }
            case 4: { //RGBA
                format = GL_RGBA;
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);

                for (int i = 0; i < 4 * width * height; i += 4) { //Multiply alpha
                    data[i + 0] = (data[i + 0] * data[i + 3] + 128) >> 8;
                    data[i + 1] = (data[i + 1] * data[i + 3] + 128) >> 8;
                    data[i + 2] = (data[i + 2] * data[i + 3] + 128) >> 8;
                }

                break;
            }
        }

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, format == GL_RGBA || format == GL_RG ? GL_CLAMP_TO_EDGE : GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, format == GL_RGBA || format == GL_RG ? GL_CLAMP_TO_EDGE : GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glBindTexture(GL_TEXTURE_2D, NULL);


        stbi_image_free(data);

        this->texture = texture;
        created = true;
    }
    else {
        if (hasLogger()) logger->fatal("Failed to create texture «" + fileName + "»");
    }
}

I made loading four texture types like Luminous, Luminous/Alpha, RGB, RGBA. For this I had to use GL_TEXTURE_SWIZZLE. In addition, I did not find an analogue of SOIL_FLAG_MULTIPLY_ALPHA, and without it, transparent textures look a little different than I expected. So I added code to handle this detail


Solution

  • The format of images that may be loaded (force_channels) (SOIL.h).

    • SOIL_LOAD_AUTO (=0) leaves the image in whatever format it was found.
    • SOIL_LOAD_LA (=2) forces the image to load as Luminous with Alpha
    • SOIL_LOAD_RGBA (=4) forces the image to load as Red Green Blue Alpha

    In the function SOIL_internal_create_OGL_texture, line 1187, you'll see, that SOIL uses the GL_LUMINANCE and GL_LUMINANCE_ALPHA symbols if the channel count is 1 or 2.

    The only problem is, that GL_LUMINANCE and GL_LUMINANCE_ALPHA are unknown to the current OpenGL version (4.6). In Legacy OpenGL and OpenGL ES on the other hand, glTexImage2D will recognize those symbols as format argument.

    Solution:

    • use Legacy OpenGL or OpenGL ES,
    • save the image file as rgb(a) (so you can use SOIL_LOAD_AUTO, if you're using current OpenGL),
    • force SOIL_LOAD_RGB(A) (for all OpenGL versions) or
    • abandon SOIL and use stb_image (which SOIL is based on) and create the texture(s) yourself (which, if you want to learn OpenGL, is the better approach anyway).

    Example (using stb_image):

    //if you want to flip the texture data vertically
    //stbi_set_flip_vertically_on_load(1);
    
    const char *filename = ""; //path of image file
    int width;        //width of image
    int height;       //height of image
    int num_channels; //number of channels in image
    
    //since we're interested in the channel count, set last param to 0
    stbi_uc *data = stbi_load(filename, &width, &height, &num_channels, 0);
    
    //num_channels to texture `format`
    GLenum format;
    switch (num_channels) {
        case 1: format = GL_RED;  break; //STBI_grey
        case 2: format = GL_RG;   break; //STBI_grey_alpha
        case 3: format = GL_RGB;  break; //STBI_rgb
        case 4: format = GL_RGBA; break; //STBI_rgb_alpha
        //default: something went wrong
    }
    
    //texture name
    GLuint tex;
    
    //generate texture name
    glGenTextures(1, &tex);
    
    //bind the texture to a specific texture target
    glBindTexture(GL_TEXTURE_2D, tex);
    
    //create the texture image
    glTexImage2D(
        GL_TEXTURE_2D, 
        0,      //mipmap level 0,
                //use glGenerateMipmap(GL_TEXTURE_2D) for automation
        format, //internal format, does not have to be the same as format, but
                //then you have to consider the conversation effects
        width,  
        height, 
        0,      //no border
        format, 
        GL_UNSIGNED_BYTE, //stbi_uc -> unsigned char
        data
    );
    
    //set texture params (min, mag, wrap_s, wrap_t)
    //glTexParameteri...
    
    //unbind texture from target
    glBindTexture(GL_TEXTURE_2D, 0);
    
    //free image data
    stbi_image_free(data);