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);
}
And my result with with SOIL_LOAD_AUTO or SOIL_LOAD_LA
The result I want to get, at the moment it's only possible with SOIL_LOAD_RGBA:
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
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
The format of images that may be loaded (force_channels) (SOIL.h).
=0
) leaves the image in whatever format it was found.=2
) forces the image to load as Luminous with Alpha=4
) forces the image to load as Red Green Blue AlphaIn 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:
SOIL_LOAD_AUTO
, if you're using current OpenGL),SOIL_LOAD_RGB(A)
(for all OpenGL versions) orExample (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);