Search code examples
c++openglglfwstb-image

Why is only one image rendering with OpenGL?


I am trying to create a way to load all the images which I plan to use in OpenGL and then render them all on the screen. It seems OpenGL seems to be very code repetitive in it's process of creating VAO,VBA, and EBOs.

Why is this only rendering one image? I thought the image object would be stored in the VAO.

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <vector>
#include "ogl_shaders.h"

const int MAX_TEXTURES = 255;
GLuint vaos[MAX_TEXTURES] = {0};
GLuint s_textures[MAX_TEXTURES] = {0};
int x_pos[] = {0, 300, 600};
int y_pox[] = {0, 100, 50};
int w_size[] = {200,200, 200};
int h_wize[] = {200, 200, 200};
std::vector<std::string> g_image_paths = { "image.png", "image2.png", "image3.png" };

struct Vec2 {
    float x;
    float y;
};

Vec2 toNDC(int pixel_x, int pixel_y, int window_width, int window_height) {
    Vec2 vec;
    vec.x = (static_cast<float>(pixel_x) / window_width) * 2.0f - 1.0f;
    vec.y = (static_cast<float>(pixel_y) / window_height) * 2.0f - 1.0f;
    return vec;
}

// I assume the problem is here  <<<
GLuint setupImageRectangle(int posX, int posY, int sizeX, int sizeY, GLuint shaderProgram, GLuint texture) {
    // Convert position to NDC
    Vec2 vec = toNDC(posX, posY, 800, 600);
    float ndcPosX = vec.x;
    float ndcPosY = vec.y;

    // Convert size to NDC
    float ndcSizeX = static_cast<float>(sizeX) / (800 / 2.0f);
    float ndcSizeY = static_cast<float>(sizeY) / (600 / 2.0f);

    // Vertex data for the rectangle
    float vertices[] = {
        ndcPosX, ndcPosY, 0.0f, 0.0f, // Bottom-left
        ndcPosX + ndcSizeX, ndcPosY, 1.0f, 0.0f, // Bottom-right
        ndcPosX + ndcSizeX, ndcPosY + ndcSizeY, 1.0f, 1.0f, // Top-right
        ndcPosX, ndcPosY + ndcSizeY, 0.0f, 1.0f  // Top-left
    };

    GLuint elements[] = {0, 1, 2, 2, 3, 0};

    // Create and bind the VAO
    GLuint vao;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    // Load the vertex data into a VBO
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // Load the element data into an EBO
    GLuint ebo;
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);

    // Specify the layout of the vertex data
    GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
    glEnableVertexAttribArray(posAttrib);
    glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);

    GLint texAttrib = glGetAttribLocation(shaderProgram, "texcoord");
    glEnableVertexAttribArray(texAttrib);
    glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));

    // Bind the texture to the rectangle
    //glBindTexture(GL_TEXTURE_2D, texture);

    return vao;
}


void CreateTexture(std::vector<std::string> image_path) {
    for (int image = 0; image < image_path.size(); image++) {
        // Generate a texture ID
        glGenTextures(1, &s_textures[image]);
        
        // Bind the texture ID
        glBindTexture(GL_TEXTURE_2D, s_textures[image]);

        // Set texture parameters
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // Load the image data
        int width, height, nrChannels;
        stbi_set_flip_vertically_on_load(true); // Flip image
        unsigned char* data = stbi_load(image_path[image].c_str(), &width, &height, &nrChannels, STBI_rgb_alpha); 
        
        if (data) {
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
            glGenerateMipmap(GL_TEXTURE_2D);
        } else {
            std::cout << "Failed to load texture: " << image_path[image] << std::endl;
        }

        // Free the image data
        stbi_image_free(data);
    }
}

void helper_gl_bindTextures(GLuint vaos[], int size) {
    
    for (int i = 0; i < size && vaos[i] != 0; i++){
        glActiveTexture(GL_TEXTURE0 + i); // activate the texture unit first before binding texture
        glBindTexture(GL_TEXTURE_2D, s_textures[i]);
        glBindVertexArray(vaos[i]);
        std::cout << "image of vaos loaded " << i << std::endl;
        
    }
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

int main()
{
    glfwInit();
    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", nullptr, nullptr);
    glfwMakeContextCurrent(window);
    glewInit();

    // Compile and activate shaders
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexSource, nullptr);
    glCompileShader(vertexShader);
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentSource, nullptr);
    glCompileShader(fragmentShader);
    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    glUseProgram(shaderProgram);



CreateTexture(g_image_paths);

for (int idx = 0 ; idx < g_image_paths.size(); idx++){
    vaos[idx] = setupImageRectangle(x_pos[idx], y_pox[idx], w_size[idx], h_wize[idx], shaderProgram, s_textures[idx]);
}
    while(!glfwWindowShouldClose(window))
    {
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // Draw the texture on the screen
        helper_gl_bindTextures(vaos, sizeof(vaos) / sizeof(vaos[0]));
        
        // Swap buffers and poll window events
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // Cleanup and exit
    for (int idx = 0 ; idx < sizeof(s_textures); idx++){
        glDeleteTextures(1, &s_textures[idx]);
    }
    
    glfwTerminate();
    return 0;
}

Solution

  • "I thought the image object would be stored in the VAO."

    If you mean texture binding, the answer is no. You must bind the texture before you draw the mesh. The Vertex Array Object stores only the vertex specification and the IDs of the vertex buffers and the index buffer.
    Apart from that, you must draw each mesh separately and therefore make a "draw" call for each mesh.

    void helper_gl_bindTextures(GLuint vaos[], int size) {
        
        for (int i = 0; i < size && vaos[i] != 0; i++){
            glActiveTexture(GL_TEXTURE0 + i); // activate the texture unit first before binding texture
            glBindTexture(GL_TEXTURE_2D, s_textures[i]);
            glBindVertexArray(vaos[i]);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        }
    }