0

I try to render skybox on my scene, but it renders black like texture is not loaded properly. I use FreeImage library to load a texture:

#include <FreeImage.h>

Here's my Skybox class:

#include "Skybox.h"


CSkybox::CSkybox(){
    el::Logger* logger = el::Loggers::getLogger("Skybox");
}

void CSkybox::init(string home_dir, string pos_x, string neg_x, string pos_y, string neg_y, string pos_z, string neg_z)
{
    cubeFilesPaths[0] = home_dir+pos_x;
    cubeFilesPaths[1] = home_dir+neg_x;
    cubeFilesPaths[2] = home_dir+pos_y;
    cubeFilesPaths[3] = home_dir+neg_y;
    cubeFilesPaths[4] = home_dir+pos_z;
    cubeFilesPaths[5] = home_dir+neg_z;

    loadShaders("shaders/skyboxShader.vp", "shaders/skyboxShader.fp");

    glGenTextures(1, &cubeTexture);
    glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);

    for(int i = 0; i < 6; i++){        
        verify(readTexture(i), "Texture "+cubeFilesPaths[i]+" not loaded"); 
    }

    CLOG(INFO, "Skybox")<<"Skybox texture loaded.";

    vao.createVAO();
    vao.bindVAO();
}

void CSkybox::loadShaders(string vsPath, string fsPath){
    verify(skyboxVertexShader.loadShader(vsPath, GL_VERTEX_SHADER), "Skybox vertex shader not loaded");
    verify(skyboxFragmentShader.loadShader(fsPath, GL_FRAGMENT_SHADER), "Skybox fragment shader not loaded");

    skyboxShaderProgram.createProgram();
    verify(skyboxShaderProgram.addShaderToProgram(&skyboxVertexShader), "Skybox vertex shader not added to a program");
    verify(skyboxShaderProgram.addShaderToProgram(&skyboxFragmentShader), "Skybox fragment shader not added to a program");

    verify(skyboxShaderProgram.linkProgram(), "Shader program not linked"); 
}

void CSkybox::render(glm::mat4 viewMatrix){
    //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    //glDisable(GL_CULL_FACE);

    skyboxShaderProgram.bindProgram();
    vao.bindVAO();

    skyboxShaderProgram.setUniform("view_matrix", &viewMatrix);
    glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);

    glDisable(GL_DEPTH_TEST);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glEnable(GL_DEPTH_TEST);

    vao.unbindVAO();
    //glEnable(GL_CULL_FACE);
    skyboxShaderProgram.unbindProgram();
    //glPolygonMode(GL_FRONT, GL_FILL);
}

void CSkybox::release(){
    glDeleteTextures(1, &cubeTexture);
    skyboxShaderProgram.deleteProgram();
    skyboxVertexShader.deleteShader();
    skyboxFragmentShader.deleteShader();
    vao.releaseVAO();
}

bool CSkybox::readTexture(int i){
    cout<<"Reading: "<<i<<endl;

    FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
    FIBITMAP* dib(0);

    fif = FreeImage_GetFileType(cubeFilesPaths[i].c_str(), 0); // Check the file signature and deduce its format

    if(fif == FIF_UNKNOWN) // If still unknown, try to guess the file format from the file extension
        fif = FreeImage_GetFIFFromFilename(cubeFilesPaths[i].c_str());

    if(fif == FIF_UNKNOWN) // If still unknown, return failure
        return false;

    if(FreeImage_FIFSupportsReading(fif)) // Check if the plugin has reading capabilities and load the file
        dib = FreeImage_Load(fif, cubeFilesPaths[i].c_str());
    if(!dib)
        return false;

    BYTE* bits = FreeImage_GetBits(dib); // Retrieve the image data

    // If somehow one of these failed (they shouldn't), return failure
    if(bits == NULL || FreeImage_GetWidth(dib) == 0 || FreeImage_GetHeight(dib) == 0)
        return false;

    cout<<FreeImage_GetBPP(dib)<<endl;

    GLenum format = FreeImage_GetBPP(dib) == 24 ? GL_BGR : FreeImage_GetBPP(dib) == 8 ? GL_LUMINANCE : 0;
    GLenum internalFormat = FreeImage_GetBPP(dib) == 24 ? GL_RGB : GL_DEPTH_COMPONENT;
    GLsizei iWidth = FreeImage_GetWidth(dib);
    GLsizei iHeight = FreeImage_GetHeight(dib);

    cout<<iWidth<<endl;
    cout<<iHeight<<endl;

    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalFormat, iWidth, iHeight, 0, format, GL_UNSIGNED_BYTE, bits);

    GLenum error = glGetError();
    if(error!= GL_NO_ERROR){
        cout<<error<<endl;
    }

    FreeImage_Unload(dib);
    return true;
}

void CSkybox::verify(bool value, string message){
    if(!value){
        CLOG(ERROR, "Skybox")<<"Error: "<<message;
    }
}

Vertex shader:

#version 430 core

out VS_OUT
{
    vec3 tc;
} vs_out;

uniform mat4 view_matrix;

void main(void)
{
    vec3[4] vertices = vec3[4](vec3(-1.0, -1.0, 1.0),
                               vec3( 1.0, -1.0, 1.0),
                               vec3(-1.0,  1.0, 1.0),
                               vec3( 1.0,  1.0, 1.0));

    vs_out.tc = mat3(view_matrix) * vertices[gl_VertexID];

    gl_Position = vec4(vertices[gl_VertexID], 1.0);
}

and fragment shader:

#version 430 core

layout (binding = 0) uniform samplerCube tex_cubemap;

in VS_OUT
{
    vec3    tc;
} fs_in;

layout (location = 0) out vec4 color;

void main(void)
{
    color = texture(tex_cubemap, fs_in.tc);
}

Unfortunately render() method renders only a black rectangle. If i hardcode an other color in a fragment shader or vertices coords in a vertex shader, a rectangle changes a color and a placement. I use shader and vao helper classes to render particles as well, so they are probably good and problem is with a texture loading.

Final effect looks like this:

enter image description here

aerion
  • 702
  • 1
  • 11
  • 28
  • Did you try calling `glGetError()` to see if any errors are reported? I haven't studied all your code in detail. For example, one constraint is that images used for cube maps have to be square (width == height). Is that the case for the image you use? – Reto Koradi May 12 '14 at 18:20
  • Invoking glTexImage2D() makes glGetError() return 1280 (GL_INVALID_ENUM) but still i don't know what is wrong here. Texture files are fine. I used them in old (OpenGL 2) applications. – aerion May 12 '14 at 18:34
  • And the image is square? I suspect this error condition: "GL_INVALID_ENUM is generated if target is one of the six cube map 2D image targets and the width and height parameters are not equal." – Reto Koradi May 12 '14 at 18:40
  • BPP is 24 (GL_RGB, GL_BGR). Width and height are 512 both. That's not the problem. I told you that texture files are fine. I used them before to create skyboxes. None of my skybox sets work. – aerion May 12 '14 at 18:43
  • @aerion: Well, of all the enums you are using the only one that is invalid in modern GL is `GL_LUMINANCE`. You need to use `GL_RED` and an appropriate texture swizzle (the extension) or swizzle the result of the texture lookup in your shader (e.g. `texture (foobar, coords).rrra`); `a` is implicitly **1.0** in any texture format that has no `a` component. – Andon M. Coleman May 12 '14 at 20:11
  • can you render the texture (in a non sky box manner) – ratchet freak May 12 '14 at 20:12
  • @AndonM.Coleman GL_LIMINANCE isnt in use. BPP is 24 so only formats used are GL_RGB (for internal) and GL_BGR (for standard format). I tried to hardcode GL_RGB8 and many others but nothing changes. I guess the problem is to pick a right method (glTexStorage2D, glTexSubImage2D, glTexImage2D etc.) and use it well. – aerion May 12 '14 at 20:48

1 Answers1

3

You have at least two different variables named cubeEnum

There is a cubeEnum declared at function scope in the CSkybox constructor:

CSkybox::CSkybox(){
    el::Logger* logger = el::Loggers::getLogger("Skybox");

    GLenum  cubeEnum[6] = {  GL_TEXTURE_CUBE_MAP_POSITIVE_X,
        GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
        GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
        GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
        GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
        GL_TEXTURE_CUBE_MAP_NEGATIVE_Z };
}

There is another cubeEnum declared at a different scope used by readTexture (...):

glTexImage2D(cubeEnum[i], 0, internalFormat, iWidth, iHeight, 0, format, GL_UNSIGNED_BYTE, bits);

I suspect that the other array that is also called cubeEnum is uninitialized so you are passing garbage to glTexImage2D (...). However, the simple truth is that you do not need an array in the first place.

This particular set of enums is sequential, thus you can write the following:

glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalFormat, iWidth, iHeight, 0, format, GL_UNSIGNED_BYTE, bits);

And get rid of all of the separate variables you have named cubeEnum. Then it will no longer matter that you are shadowing a variable of the same name at a different scope.


Update:

Additionally, you were using the default texture minification filter: GL_NEAREST_MIPMAP_LINEAR on a cubemap that did not have the appropriate set of mipmap LODs (GL textures have 1000 LODs by default and you only defined LOD 0). Using that filter on a "mipmap incomplete" texture results in undefined behavior, and this was the cause of your black texture.

Andon M. Coleman
  • 42,359
  • 2
  • 81
  • 106
  • You were right about cubeEnum. I fixed it, glGetError() returns GL_NO_ERROR now, but rectangle is black as it were. I will update a code in a minute. – aerion May 12 '14 at 21:04
  • "Sure it can, that is one of the only places where those particular enums make any sense." Yup, I've read it wrong... and removed stupid comment 30 secs after writing it. :) – aerion May 12 '14 at 21:12
  • 1
    @aerion: Can you try setting the texture minification filter to `GL_LINEAR` and see if that changes anything? The default minification filter is `GL_NEAREST_MIPMAP_LINEAR`, and your textures do not currently have mipmaps. That is an invalid combination of filter and image, it produces something known as a *"mipmap incomplete"* texture and trying to sample it *may* produce nothing but black (or any number of other things since the behavior is undefined). – Andon M. Coleman May 12 '14 at 21:29
  • `glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);` helped. Thanks! – aerion May 12 '14 at 21:48