Search code examples
c++openglgraphicsrenderingopengl-4

OpenGL Instanced Rendering drawing one triangle


I'm trying to build a voxel engine, and to do this I have to create hundreds of thousands of voxels, and I was hoping I could use instanced rendering. However, the drawing is very unexpected. I'm primarily following the LearnOpenGL guide.

When rendering each voxel individually, the program works fine:

Voxel working

However, when using instanced rendering...

Voxel not working 1

Another angle...

Voxel not working 2

I'm trying to render the voxels in a big chunk, so this is what my code looks like:

voxel.hpp

#pragma once

#include <stdio.h>

#include <iostream>
#include <vector>

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

using std::vector;
using glm::mat4;
using glm::vec3;

class Voxel {
    float radius;
    bool visible;
    vec3 centerPoint;
public:
    unsigned int VBO, VAO, EBO;

    Voxel(vec3 center, float size, bool vis = false, bool single = false);
    void setMVP(mat4 mvp);
    void setVisible(bool v);
    void generateElement();
};

voxel.cpp

#include "voxel.hpp"

Voxel::Voxel(vec3 center, float size, bool vis, bool single) {
    visible = vis;
        
    centerPoint = center;
    radius = size;
    
    generateElement();
}

void Voxel::setVisible(bool v) {
    visible = v;
}

void Voxel::generateElement() {
    // this time we need all 8 vertices and a length 36 index array
    
    vec3 maxV(centerPoint.x + radius, centerPoint.y + radius, centerPoint.z + radius);
    vec3 minV(centerPoint.x - radius, centerPoint.y - radius, centerPoint.z - radius);
    
    float vertices[24] = {
        maxV.x, maxV.y, maxV.z,
        maxV.x, maxV.y, minV.z,
        maxV.x, minV.y, minV.z,
        maxV.x, minV.y, maxV.z,
        minV.x, minV.y, maxV.z,
        minV.x, maxV.y, maxV.z,
        minV.x, maxV.y, minV.z,
        minV.x, minV.y, minV.z,
    };
    
    unsigned int indices[36] = {
        0, 2, 1, // maxV.x
        0, 2, 3,

        2, 6, 1, // minV.z
        2, 6, 7,

        2, 4, 3, // minV.y
        2, 4, 7,

        4, 6, 5, // minV.x
        4, 6, 7,

        1, 5, 0, // maxV.y
        1, 5, 6,

        0, 4, 3, // maxV.z
        0, 4, 5,
    };
    
    // for individual rendering there would be shader code here
    
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);
    // load data into vertex buffers
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
//    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_STATIC_DRAW);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices[0], GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

//    // set the vertex attribute pointers
//    // vertex Positions
    glEnableVertexAttribArray(0);
//    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vec3), (void*)0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

//    glBindVertexArray(VAO);
    
    // set attribute pointers for matrix (4 times vec4)
    glEnableVertexAttribArray(3);
    glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);
    glEnableVertexAttribArray(4);
    glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));
    glEnableVertexAttribArray(5);
    glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4) * 2));
    glEnableVertexAttribArray(6);
    glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4) * 3));

    glVertexAttribDivisor(3, 1);
    glVertexAttribDivisor(4, 1);
    glVertexAttribDivisor(5, 1);
    glVertexAttribDivisor(6, 1);

    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

chunk.hpp

#pragma once

#include <stdio.h>

#include <iostream>
#include <vector>

#include <algorithm>

#include "voxel.hpp"

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

using std::vector;
using glm::mat4;
using glm::vec3;


class Chunk {
    vec3 centerPoint;
    int voxelNum;
    int shaderProgram;
    unsigned int VBO, EBO, VAO;
    mat4 VP;
public:
    Chunk(vec3 center, float radius, int rinv);
    void setVP(mat4 vp);
    void setVisible(bool v);
    void draw();
};

chunk.cpp

#include "chunk.hpp"

#include <iostream>
#include <vector>

#include <algorithm>

#include <stdlib.h>

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
 
using std::vector;
using glm::mat4;
using glm::vec3;

Chunk::Chunk(vec3 centerPoint, float radius, int rinv) {
    vec3 endPoint(centerPoint.x - radius, centerPoint.y - radius, centerPoint.z - radius);
    
    float distVox = 2 * radius/rinv;
    
    voxelNum = pow(rinv, 3);
    
    mat4* modelMatrices = new mat4[voxelNum];
    srand(glfwGetTime()); // initialize random seed
    
    for (int z = 0; z < rinv; z++) {
        for (int y = 0; y < rinv; y++) {
            for (int x = 0; x < rinv; x++) {
                glm::mat4 model = glm::mat4(1.0f);
                
                model = translate(model, vec3(endPoint.x + (x + 0.5) * distVox, endPoint.y + (y + 0.5) * distVox, endPoint.z + (z + 0.5) * distVox));
                model = scale(model, vec3(radius));
                
                int index = x + y * rinv + z * pow(rinv, 2);
                modelMatrices[index] = model;
            }
        }
    }
    
    const char *vertexShaderSource = "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;\n"
        "layout (location = 3) in mat4 aInstanceMatrix;\n"
        "uniform mat4 VP;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = VP * aInstanceMatrix * vec4(aPos, 1.0);\n"
        "}\n\0";
    
    const char *fragmentShaderSource = "#version 330 core\n"
        "out vec4 FragColor;\n"
        "uniform vec3 color;\n"
        "void main()\n"
        "{\n"
        "   FragColor = vec4(color, 1.0f);\n"
        "}\n\0";

    // vertex shader
    int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // fragment shader
    int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // link shaders
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, voxelNum * sizeof(mat4), &modelMatrices[0], GL_STATIC_DRAW);
    
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

void Chunk::setVP(mat4 vp) {
    VP = vp;
}

void Chunk::draw() {
    glUseProgram(shaderProgram);
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "VP"), 1, GL_FALSE, &VP[0][0]);

    Voxel eVox(vec3(0.0f), 1.0f, true, false);

    glBindVertexArray(eVox.VAO);
    glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0, voxelNum);
    glBindVertexArray(0);
}

main.cpp

#include <iostream>
using namespace std;

#include "chunk.hpp"

#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
using namespace glm;

//Global Variables
GLFWwindow* window;
const char* SCR_TITLE = "WORKINGPLANET";
const int SCR_WIDTH = 500, SCR_HEIGHT = 500;

float x_rot = 0.0f;
float y_rot = 0.0f;

float y_rot_clamp = 89.999f;

// timing
float deltaTime = 0.0f; // time between current frame and last frame
float lastFrame = 0.0f;

void mouseCallback(GLFWwindow *window, int button, int action, int mods);

vec3 X_AXIS = vec3(1.0f, 0.0f, 0.0f);
vec3 Y_AXIS = vec3(0.0f, 1.0f, 0.0f);

//Main Program
int main()
{
    //Constructor Code
    if(!glfwInit())
    {
        cerr << "Error!!GLFW";
        return -1;
    }
    
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    
    if(!(window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, SCR_TITLE, NULL, NULL)))
    {
        cerr << "Error!!GLFW window";
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);

    if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
        std::cout << "Failed to initialize OpenGL context" << std::endl;
        return -1;
    }
    
    Chunk chunk(vec3(0.0f), 0.5, 2);

    mat4 view = mat4(1.0);
    vec3 cameraPos = glm::vec3(0.0f, 0.0f, 4.0f);
    view = lookAt(cameraPos, vec3(0,0,0), vec3(0,1,0));
    //Loop Events
    while(!glfwWindowShouldClose(window))
    {
        // per-frame time logic
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;
        
        glClearColor(1.0, 1.0, 1.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);


        // Tweak these values to change the sensitivity
        float scale_x = 7.0f / SCR_WIDTH;
        float scale_y = 7.0f / SCR_HEIGHT;
        float rotSpeed = 350.0f;
        float rot = scale_x * rotSpeed;

        if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
            rot = scale_y * rotSpeed;
            if (y_rot + rot > y_rot_clamp)
                rot = y_rot_clamp - y_rot;

            view = rotate(view, (float)radians(rot), X_AXIS);
            y_rot += rot;
        } if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
            rot = scale_y * rotSpeed;
            if (y_rot - rot < -y_rot_clamp)
                rot = y_rot + y_rot_clamp;

            view = rotate(view, (float)radians(-rot), X_AXIS);
            y_rot -= rot;
        } if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
            view = rotate(view, (float)radians(-rot), Y_AXIS);
            x_rot -= rot;
        } if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
            view = rotate(view, (float)radians(rot), Y_AXIS);
            x_rot += rot;
        } if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) {
            view = lookAt(cameraPos, vec3(0,0,0), vec3(0,1,0));
            x_rot = 0.0f;
            y_rot = 0.0f;
        }

        mat4 projection = perspective(radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);

        //Rendering
        chunk.setVP(projection * view);
        chunk.draw();
        
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;

}

I'm totally stuck. Adding more voxels doesn't change how the instanced bug looks.

Interestingly, commenting out the glLinkProgram(shaderProgram); in chunk.cpp makes this bug entirely different, with the chunk appearing as one huge voxel that emcompasses the entire cube.


Solution

  • Your VBO setup doesn't make the slightest sense. You set up your per-instance transformation matrix to use the same data as your geometry in Voxel::generateElement().

    You later upload all your transformation matrixes into a separate VBO, but the attribute pointers still point to the geometry VBO. YOu need to move the attribute setup for the instanced attribute out of Voxel::generateElement() and into Chunk::Chunk() so you can tell it to use that VBO as source for the model matrices.