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:
However, when using instanced rendering...
Another angle...
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.
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.