From 3ba2d75cff9d238625b535e89c1f6b2e6b128d55 Mon Sep 17 00:00:00 2001 From: JISAUAY Date: Tue, 11 Nov 2025 16:05:05 -0600 Subject: [PATCH] added chunk rendering --- .attachments/chunk-render.png | Bin 0 -> 50181 bytes CMakeLists.txt | 2 + README.md | 8 ++- src/Chunk.cpp | 108 ++++++++++++++++++++++++++++++++++ src/Chunk.h | 43 ++++++++++++++ src/Mesh.cpp | 8 ++- src/World.cpp | 102 ++++---------------------------- src/World.h | 31 ++-------- src/main.cpp | 84 +++++++++++++++----------- 9 files changed, 235 insertions(+), 151 deletions(-) create mode 100644 .attachments/chunk-render.png create mode 100644 src/Chunk.cpp create mode 100644 src/Chunk.h diff --git a/.attachments/chunk-render.png b/.attachments/chunk-render.png new file mode 100644 index 0000000000000000000000000000000000000000..0a0e789b80cef65f59b2309a303332793e1fd752 GIT binary patch literal 50181 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 literal 0 HcmV?d00001 diff --git a/CMakeLists.txt b/CMakeLists.txt index ad4cd12..5fb3a3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,8 @@ add_executable(VoxelEngine src/World.cpp src/Mesh.h src/Mesh.cpp + src/Chunk.h + src/Chunk.cpp ) # --- Configure GLAD (from your src/ and include/ folders) --- diff --git a/README.md b/README.md index 3f945bd..475ce8a 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,15 @@ Currently, however, the program only renders a single cube. ![a wireframe cube](.attachments/wireframe-cube.png) -## World Render - 11/10/25 +## World Mesh Rendering - 11/10/25 World now renders as a single mesh! Much faster than rendering cube by cube. ![cube world](.attachments/world-render.png) +## Chunk Mesh Rendering - 11/11/25 + +Instead of making the _entire_ world a single mesh, now the world is made up of 32x32x32 "Chunks" which are areas of the world that are a single mesh. + +![cube world (but in chunks!)](.attachments/chunk-render.png) + diff --git a/src/Chunk.cpp b/src/Chunk.cpp new file mode 100644 index 0000000..208087c --- /dev/null +++ b/src/Chunk.cpp @@ -0,0 +1,108 @@ +#include "Chunk.h" +#include + +const int VERTEX_SIZE = 6; + +const float topFace[24] = { + // x, y, z, nx, ny, nz + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f +}; +const float bottomFace[24] = { + -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, + -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f +}; +const float rightFace[24] = { + 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f +}; +const float leftFace[24] = { + -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, + -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f +}; +const float frontFace[24] = { + -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f +}; +const float backFace[24] = { + -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, + -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f +}; + +void addFace(glm::ivec3 pos, const float *faceVertices, std::vector &vbo, std::vector &ebo) { + unsigned int vIndex = vbo.size() / VERTEX_SIZE; + for (int i = 0; i < 4; i++) { + const float* v = &faceVertices[i * VERTEX_SIZE]; + vbo.push_back(v[0] + pos.x); + vbo.push_back(v[1] + pos.y); + vbo.push_back(v[2] + pos.z); + vbo.push_back(v[3]); + vbo.push_back(v[4]); + vbo.push_back(v[5]); + } + ebo.push_back(vIndex + 0); + ebo.push_back(vIndex + 1); + ebo.push_back(vIndex + 2); + ebo.push_back(vIndex + 0); + ebo.push_back(vIndex + 2); + ebo.push_back(vIndex + 3); +} + +Chunk::Chunk() { + // mesh is initially null (nullptr) +} + +Mesh* Chunk::getMesh() { + // Generate mesh on first access if not already generated + if (!mesh && !voxels.empty()) { + generateMesh(); + } + return mesh.get(); +} + +void Chunk::generateMesh() { + std::vector vboData; + std::vector eboData; + + for (const glm::ivec3 pos : voxels) { + if (voxels.count(pos + glm::ivec3(0, 1, 0)) == 0) { + addFace(pos, topFace, vboData, eboData); + } + if (voxels.count(pos + glm::ivec3(0, -1, 0)) == 0) { + addFace(pos, bottomFace, vboData, eboData); + } + if (voxels.count(pos + glm::ivec3(1, 0, 0)) == 0) { + addFace(pos, rightFace, vboData, eboData); + } + if (voxels.count(pos + glm::ivec3(-1, 0, 0)) == 0) { + addFace(pos, leftFace, vboData, eboData); + } + if (voxels.count(pos + glm::ivec3(0, 0, 1)) == 0) { + addFace(pos, frontFace, vboData, eboData); + } + if (voxels.count(pos + glm::ivec3(0, 0, -1)) == 0) { + addFace(pos, backFace, vboData, eboData); + } + } + + // Create and initialize mesh if it doesn't exist + if (!mesh) { + mesh = std::make_unique(); + mesh->init(); + } + + // Now, upload this data to our mesh object + mesh->uploadData(vboData, eboData); +} diff --git a/src/Chunk.h b/src/Chunk.h new file mode 100644 index 0000000..32e0de0 --- /dev/null +++ b/src/Chunk.h @@ -0,0 +1,43 @@ +#ifndef CHUNK_H +#define CHUNK_H + +#include +#include + +#include + +#include "Mesh.h" + +const int CHUNK_SIZE = 32; + +// Hash function for glm::ivec3 to use with unordered_set +namespace std { + template <> + struct hash { + size_t operator()(const glm::ivec3& v) const { + // Combine hash values of x, y, z components + size_t h1 = hash()(v.x); + size_t h2 = hash()(v.y); + size_t h3 = hash()(v.z); + + // Use a simple hash combination algorithm + return h1 ^ (h2 << 1) ^ (h3 << 2); + } + }; +} + +class Chunk { + public: + std::unordered_set voxels; + + Chunk(); + + Mesh* getMesh(); // Returns pointer, can be null if not generated yet + + void generateMesh(); + + private: + std::unique_ptr mesh; // Nullable mesh +}; + +#endif CHUNK_H diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 4653497..e429e4f 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -1,3 +1,5 @@ +#include + #include "Mesh.h" Mesh::Mesh() : m_VAO(0), m_VBO(0), m_EBO(0), m_indexCount(0) {} @@ -38,10 +40,14 @@ void Mesh::init() { void Mesh::uploadData(const std::vector &vboData, const std::vector &eboData) { m_indexCount = eboData.size(); + std::cout << "Uploading mesh: " << vboData.size() << " floats, " << eboData.size() << " indices" << std::endl; + + // Bind VAO first, then upload buffer data + glBindVertexArray(m_VAO); + glBindBuffer(GL_ARRAY_BUFFER, m_VBO); glBufferData(GL_ARRAY_BUFFER, vboData.size() * sizeof(float), vboData.data(), GL_STATIC_DRAW); - glBindVertexArray(m_VAO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboData.size() * sizeof(unsigned int), eboData.data(), GL_STATIC_DRAW); glBindVertexArray(0); diff --git a/src/World.cpp b/src/World.cpp index a6f7bdc..3ad6728 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -1,98 +1,22 @@ #include "World.h" -#include -const int VERTEX_SIZE = 6; +World::World() {} -const float topFace[24] = { - // x, y, z, nx, ny, nz - -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, - 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, - 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, - -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f -}; -const float bottomFace[24] = { - -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, - 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, - 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, - -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f -}; -const float rightFace[24] = { - 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, - 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, - 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, - 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f -}; -const float leftFace[24] = { - -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, - -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, - -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, - -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f -}; -const float frontFace[24] = { - -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, - 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, - 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, - -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f -}; -const float backFace[24] = { - -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, - 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, - 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, - -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f -}; +void World::flip_voxel(glm::ivec3 position) { + glm::ivec3 chunk_position = position / CHUNK_SIZE; + glm::ivec3 local_position = position % CHUNK_SIZE; -void addFace(glm::ivec3 pos, const float *faceVertices, std::vector &vbo, std::vector &ebo) { - unsigned int vIndex = vbo.size() / VERTEX_SIZE; - for (int i = 0; i < 4; i++) { - const float* v = &faceVertices[i * VERTEX_SIZE]; - vbo.push_back(v[0] + pos.x); - vbo.push_back(v[1] + pos.y); - vbo.push_back(v[2] + pos.z); - vbo.push_back(v[3]); - vbo.push_back(v[4]); - vbo.push_back(v[5]); + Chunk * chunk = get_or_create_chunk(chunk_position); + if (chunk->voxels.count(local_position) != 0) { + chunk->voxels.erase(chunk->voxels.find(local_position)); + } else { + chunk->voxels.insert(local_position); } - ebo.push_back(vIndex + 0); - ebo.push_back(vIndex + 1); - ebo.push_back(vIndex + 2); - ebo.push_back(vIndex + 0); - ebo.push_back(vIndex + 2); - ebo.push_back(vIndex + 3); } -World::World() { - m_mesh.init(); -} - -Mesh& World::getMesh() { - return m_mesh; -} - -void World::generateMesh() { - std::vector vboData; - std::vector eboData; - - for (const glm::ivec3 pos : voxels) { - if (voxels.count(pos + glm::ivec3(0, 1, 0)) == 0) { - addFace(pos, topFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(0, -1, 0)) == 0) { - addFace(pos, bottomFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(1, 0, 0)) == 0) { - addFace(pos, rightFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(-1, 0, 0)) == 0) { - addFace(pos, leftFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(0, 0, 1)) == 0) { - addFace(pos, frontFace, vboData, eboData); - } - if (voxels.count(pos + glm::ivec3(0, 0, -1)) == 0) { - addFace(pos, backFace, vboData, eboData); - } +Chunk* World::get_or_create_chunk(glm::ivec3 chunk_position) { + if (this->chunks.count(chunk_position) == 0) { + this->chunks[chunk_position] = Chunk(); } - - // Now, upload this data to our mesh object - m_mesh.uploadData(vboData, eboData); + return &this->chunks[chunk_position]; } diff --git a/src/World.h b/src/World.h index 3367d1b..927ac8f 100644 --- a/src/World.h +++ b/src/World.h @@ -1,41 +1,22 @@ #ifndef WORLD_H -#define CAMERA_H +#define WORLD_H -#include +#include #include -#include "Mesh.h" - -// Hash function for glm::ivec3 to use with unordered_set -namespace std { - template <> - struct hash { - size_t operator()(const glm::ivec3& v) const { - // Combine hash values of x, y, z components - size_t h1 = hash()(v.x); - size_t h2 = hash()(v.y); - size_t h3 = hash()(v.z); - - // Use a simple hash combination algorithm - return h1 ^ (h2 << 1) ^ (h3 << 2); - } - }; -} +#include "Chunk.h" class World { public: - std::unordered_set voxels; + std::unordered_map chunks; World(); - void generateMesh(); + void flip_voxel(glm::ivec3 position); - Mesh& getMesh(); - - private: - Mesh m_mesh; + Chunk* get_or_create_chunk(glm::ivec3 chunk_position); }; #endif WORLD_H diff --git a/src/main.cpp b/src/main.cpp index e11c433..c080291 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #include -#include #include // Must be included before GLFW #include @@ -171,28 +170,6 @@ void processInput(GLFWwindow *window) camera.speed = CAMERA_SPEED * 20; } -// Draw a cube at some x, y, z -// [UPDATED] Added a new 'color' parameter -void draw_cube(glm::ivec3 coords, Shader& shader, unsigned int VAO, size_t indexCount, const glm::mat4& view, const glm::mat4& projection, const glm::vec3& color) { - // Create model matrix for positioning the cube - glm::mat4 model = glm::mat4(1.0f); - model = glm::translate(model, glm::vec3(coords)); - - model = glm::scale(model, glm::vec3(0.5f, 0.5f, 0.5f)); - - // Calculate MVP matrix - glm::mat4 mvp = projection * view * model; - - // Set the uniform and draw - shader.use(); - shader.setMat4("u_mvp", mvp); - // [NEW] Set the color uniform in the fragment shader - shader.setVec3("u_Color", color); - - glBindVertexArray(VAO); - glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr); -} - int main() { // 1. --- Initialize GLFW --- if (!glfwInit()) { @@ -231,16 +208,15 @@ int main() { Object3D cube = Object3D("objs/cube.obj"); // Place cubes in the world - for (int z = 0; z <= 32; z++) { - for (int x = 0; x <= 32; x++) { - world.voxels.insert(glm::ivec3(x, -1, z)); + for (int z = 0; z <= 256; z++) { + for (int x = 0; x <= 256; x++) { + world.flip_voxel(glm::ivec3(x, -1, z)); } } - world.voxels.insert(glm::ivec3(7, 0, 13)); - std::cout << "Generating world mesh..." << std::endl; - world.generateMesh(); - std::cout << "Mesh generated!" << std::endl; + world.flip_voxel(glm::ivec3(7, 0, 13)); + std::cout << "World created with " << world.chunks.size() << " chunks" << std::endl; + // Mesh generation now happens automatically when first rendering if (WIREFRAME_MODE) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); @@ -257,6 +233,7 @@ int main() { double move_time = glfwGetTime(); glEnable(GL_DEPTH_TEST); + bool debug_printed = false; // 6. --- The Render Loop --- while (!glfwWindowShouldClose(window)) { @@ -269,9 +246,13 @@ int main() { view = camera.getView(); shader.use(); - glm::mat4 model = glm::mat4(1.0f); - glm::mat4 mvp = projection * view * model; - shader.setMat4("u_mvp", mvp); + + if (!debug_printed) { + std::cout << "\n=== RENDER DEBUG ===" << std::endl; + std::cout << "Camera pos: (" << camera.position.x << ", " << camera.position.y << ", " << camera.position.z << ")" << std::endl; + std::cout << "Camera target: (" << camera.target.x << ", " << camera.target.y << ", " << camera.target.z << ")" << std::endl; + std::cout << "Number of chunks to draw: " << world.chunks.size() << std::endl; + } // ---- PASS 1: Draw solid grey cubes ---- glEnable(GL_POLYGON_OFFSET_FILL); @@ -279,7 +260,28 @@ int main() { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); shader.setVec3("u_Color", glm::vec3(0.5, 0.5, 0.5)); - world.getMesh().draw(); + for (auto& [chunk_position, chunk] : world.chunks) { + + glm::ivec3 chunk_offset = chunk_position * CHUNK_SIZE; + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(chunk_offset)); + glm::mat4 mvp = projection * view * model; + shader.setMat4("u_mvp", mvp); + + if (!debug_printed) { + std::cout << "Drawing chunk at (" << chunk_position.x << ", " << chunk_position.y << ", " << chunk_position.z << ")" << std::endl; + std::cout << "Chunk offset: (" << chunk_offset.x << ", " << chunk_offset.y << ", " << chunk_offset.z << ")" << std::endl; + } + + Mesh* chunkMesh = chunk.getMesh(); + if (chunkMesh) { + chunkMesh->draw(); + } + } + + if (!debug_printed) { + debug_printed = true; + } glDisable(GL_POLYGON_OFFSET_FILL); @@ -287,7 +289,19 @@ int main() { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Just the lines shader.setVec3("u_Color", glm::vec3(1.0, 1.0, 1.0)); - world.getMesh().draw(); + for (auto& [chunk_position, chunk] : world.chunks) { + + glm::ivec3 chunk_offset = chunk_position * CHUNK_SIZE; + glm::mat4 model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(chunk_offset)); + glm::mat4 mvp = projection * view * model; + shader.setMat4("u_mvp", mvp); + + Mesh* chunkMesh = chunk.getMesh(); + if (chunkMesh) { + chunkMesh->draw(); + } + } // --- Reset polygon mode for next frame (good practice) --- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);