2
$\begingroup$

I'm a noob to the OpenGL world and am picking up ideas through practice. I've been writing some code to build retro-arcade games recently and need to load textures to provide the graphic interface. I'm using OpenGL v4.6 with GLFW3 and the stb library to load images.

To load textures, I draw a box and define the texture coordinates of the image on the box. Only some of the png's that I use have loaded properly- often, the image is blurred and distorted. There were two things I tried so far-

  1. Look exclusively for jpg files as these were loading properly. But most of the textures that I download of google are exclusively png

  2. Convert the png into a jpg file. Since I was converting, I decided to use other file formats as well, cropping some of the images if need be. I used GIMP to export them in jpg format. However, when I try to load it OpenGL, I still get bad-poor results.

Another possible approach I'm considering is to convert them into dxt formats and see if that does the trick. However, this seems a tad too tedious and something I'd only go for if nothing else works.

Below is the code that I'm using to load the image.

int width, height, nrChannels;
unsigned char* data = stbi_load(file_path, &width, &height, &nrChannels, 0);
GLenum format{};
if (data)
{
    if (nrChannels == 1)
        format = GL_RED;
    else if (nrChannels == 3)
        format = GL_RGB;
    else if (nrChannels == 4)
        format = GL_RGBA;
    glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}

I'm starting to spend a significant amount of time looking for images that will load properly and less time learning/implementing; this is quite concerning. Can someone please point out where I'm going wrong or a better way of skirting around this issue?

The image to the left is the original as loaded by OpenGL (jfif file format). The one to the bottom is the edited version (made with Gimp, stored as jpg). The image to the right is the render of the bottom image in OpenGL The image to the left is the original as loaded by OpenGL (jfif file format). The one to the bottom is the edited version (made with Gimp, stored as jpg). The image to the right is the render of the bottom image in OpenGL

Example 2 Again, the image to the left is the output from Opengl while the one to the right is the original image. Note the inclination of the render and lack of colour in it.


Edit: Here are some of the implementation details-

I'm believe this portion should be correct because the render is perfect for .jpg files. rect is a vector to hold the box and texture coordinates and ind is a vector that holds the indices that are used by the element buffer to avoid repeat drawing. Both are initialised as arrays and converted into a vector (loop through array and push_back into vector).

PS. Apologies for the verbosity. I was trying to couch the drawing inside a class and this was my first attempt at it. So, please bear with me.

// Box container to hold images
//             x      y   tex-x   tex-y    
const float rect[] = {
            -0.2f, -0.2f, 0.0f, 0.0f,
             0.2f, -0.2f, 1.0f, 0.0f,
             0.2f,  0.2f, 1.0f, 1.0f,
            -0.2f,  0.2f, 0.0f, 1.0f
    };

int ind[] = {
        0,1,2,
        0,2,3
    };

_num_attr_ is the number of attributes given to the vao, which in this case is 2. cumsum contains the sum of the number of attributes and is used to assign the stride and size of each attribute.

sizes = {2,2}; // initialized 
cumsum = 0;
for (int i = 0; i < num_attr; ++i)
{
    attr_num_cumsum.push_back(cumsum);
    cumsum += sizes[i];
    attr_size.push_back(sizes[i]);  
}
attr_num_cumsum.push_back(cumsum);

At the end of this block, cumsum has a value of 4 and _attr_num_cumsum_ = {0,2,4} and _attr_size_ = {2,2}.

_vbo_data_ contains the values in rect while _ebo_data_ contains the values in ind Here is the buffer set-up for drawing-

glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vbo_data.size()*sizeof(GL_FLOAT), &vbo_data[0], GL_DYNAMIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ebo_data.size()*sizeof(GL_INT), &ebo_data[0], GL_DYNAMIC_DRAW);
for (int i = 0; i < num_attr; ++i)
{
    glVertexAttribPointer(i, attr_size[i], GL_FLOAT, GL_FALSE, attr_num_cumsum[num_attr] * sizeof(GL_FLOAT), (void*)(attr_num_cumsum[i] * sizeof(GL_FLOAT)));
    glEnableVertexAttribArray(i);
}

And the actual drawing

glBindVertexArray(vao);
glDrawElements(GL_TRIANGLES, num_vertices, GL_UNSIGNED_INT, 0);

Vertex Shader

#version 460 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 tex;
out float C;
out vec2 Tex;
uniform mat4 model;
uniform float col;
void main()
{
    gl_Position = model*vec4(aPos.x,aPos.y,0.0f, 1.0f);
    C=col;
    Tex=tex;
}

Fragment Shader

#version 460 core
out vec4 Fragcolor;
in float C;
in vec2 Tex;
uniform sampler2D texture1;
void main()
{
    Fragcolor = texture(texture1,Tex);
}
$\endgroup$
  • $\begingroup$ Please provide more information (code if possible). How do you define your box (vertex attributes)? How do you populate the buffers? How do your shaders look like? To me, it looks as if your texture coordinates are not set correctly, but I can't tell for sure without the corresponding code. $\endgroup$ – wychmaster Mar 31 at 9:50
  • $\begingroup$ Which version of OpenGL are you using? The second issue (skewed texture) tends to be the result of an image dimension mismatch when uploading (glTexImage2D(...)). Double check you are specifying the correct dimensions when uploading texture to OpenGL. Also maybe check you can use NPOT (non-power of two) textures which depends on the OpenGL context version you create. $\endgroup$ – lfgtm Mar 31 at 22:38
  • $\begingroup$ @lfgtm He is using OpenGL v4.6 as mentioned at the beginning. That's why I don't think that NPOT textures are the problem. Additionally, if there is a dimension mismatch, wouldn't there be parts missing? To me, it just looks like the textures are shifted to the right with increasing y value. $\endgroup$ – wychmaster Apr 1 at 7:34
  • $\begingroup$ @wychmaster It's not texture coordinates. The skew and lack of colour is a dead giveaway that the texel "stride" is incorrect. If the dims and number of channels are correct, the input image is probably not aligned as GL expects it to be. glPixelStorei would probably be the fix, but without knowing more details about the input texture, impossible to say what arguments glPixelStorei should take. $\endgroup$ – lfgtm Apr 1 at 13:30
  • $\begingroup$ @wychmaster I doubt there is a shift in the coordinates because the same code was used to display the multiple roads image. Also, just to be safe, I tested the render on this (also jpg) and it works- imgur.com/Sm3zuMy $\endgroup$ – Prashanth Neelakantan Apr 1 at 15:15
1
$\begingroup$

Briefly, modify the stb_load as follows and everything should work.

unsigned char* data = stbi_load(file_path, &width, &height, &nrChannels, 4);
nrChannels = 4;

Details: Okay, after a lot of heartache trying and failing to get libpng to work, I finally decided to have a look at the stbi implementation that I'm using to load the texture files. This is where the problem comes in from.

@wychmaster was right when he suspected that there was an incorrect stride used. The problem, though, is in the reading of the image. Apparently, this has something to do with alignment of pixel data which is set by glPixelStorei.

Someone posted the same problem in https://github.com/nothings/stb/issues/335 and determined that this was solution (it works!).

The solution is to set the number of desired channels to 4 in the loading function and read the image as an RGBA instead of whatever the image format actually is. This works and all the misaligned images are now perfect. The images that worked before continue to work too.

But, I do not completely understand why nothing else is affected. This seems strange to me... If someone can explain why this works, that would be very helpful. In the event this is a niche question, I'll likely return to make a more detailed post on it.

| improve this answer | |
$\endgroup$
  • $\begingroup$ Usually textures need to be aligned such that the total bytes in a line is divisible by 4. With RGB format your have 3 bytes per pixel so it's easier to misalign. You can also avoid this in RGB format by making sure that image width divisible by 4. $\endgroup$ – PaulHK Apr 7 at 5:47
  • $\begingroup$ That makes sense, all the images that were skewed were not a multiple of 4 in width. Does this mean that the texture input by default expects RGBA values? $\endgroup$ – Prashanth Neelakantan Apr 8 at 2:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.