Use indexing to drastically reduce the number of vertices

main
Brett 2023-02-14 18:19:39 -05:00
parent c955c07ab4
commit c111d5c1b7
23 changed files with 138 additions and 93 deletions

View File

@ -43,7 +43,7 @@ CMAKE_AR:FILEPATH=emar
//Choose the type of build, options are: None Debug Release RelWithDebInfo
// MinSizeRel ...
CMAKE_BUILD_TYPE:STRING=Debug
CMAKE_BUILD_TYPE:STRING=Release
//Enable/Disable color output during build.
CMAKE_COLOR_MAKEFILE:BOOL=ON

View File

@ -6,5 +6,5 @@ CXX_DEFINES =
CXX_INCLUDES = @CMakeFiles/FinalProject.dir/includes_CXX.rsp
CXX_FLAGS = -g -std=c++17 -std=gnu++17
CXX_FLAGS = -O3 -DNDEBUG -std=c++17 -std=gnu++17

View File

@ -1 +1 @@
/usr/bin/em++ -g -sMAX_WEBGL_VERSION=2 -s ASSERTIONS=1 -sUSE_GLFW=3 --preload-file 'assets' @CMakeFiles/FinalProject.dir/objects1 -o FinalProject.js @CMakeFiles/FinalProject.dir/linkLibs.rsp
/usr/bin/em++ -O3 -DNDEBUG -sMAX_WEBGL_VERSION=2 -s ASSERTIONS=1 -sUSE_GLFW=3 --preload-file 'assets' @CMakeFiles/FinalProject.dir/objects1 -o FinalProject.js @CMakeFiles/FinalProject.dir/linkLibs.rsp

View File

@ -327,4 +327,5 @@ CMakeFiles/FinalProject.dir/src/main.cpp.o: \
/home/brett/Documents/Brock/CS\ 3P98/Final\ Project/include/render/camera.h \
/home/brett/Documents/Brock/CS\ 3P98/Final\ Project/include/world/chunk/world.h \
/home/brett/Documents/Brock/CS\ 3P98/Final\ Project/include/world/chunk/storage.h \
/home/brett/Documents/Brock/CS\ 3P98/Final\ Project/include/world/chunk/typedefs.h
/home/brett/Documents/Brock/CS\ 3P98/Final\ Project/include/world/chunk/typedefs.h \
/home/brett/Documents/Brock/CS\ 3P98/Final\ Project/include/world/registry.h

View File

@ -12,7 +12,7 @@ if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
else()
set(CMAKE_INSTALL_CONFIG_NAME "Debug")
set(CMAKE_INSTALL_CONFIG_NAME "Release")
endif()
message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
endif()

View File

@ -6,5 +6,5 @@ CXX_DEFINES =
CXX_INCLUDES = @CMakeFiles/BLT.dir/includes_CXX.rsp
CXX_FLAGS = -g -std=c++17 -std=gnu++17
CXX_FLAGS = -O3 -DNDEBUG -std=c++17 -std=gnu++17

View File

@ -12,7 +12,7 @@ if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
else()
set(CMAKE_INSTALL_CONFIG_NAME "Debug")
set(CMAKE_INSTALL_CONFIG_NAME "Release")
endif()
message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
endif()

View File

@ -12,6 +12,7 @@
#include "blt/std/logging.h"
#include <world/chunk/typedefs.h>
#include <world/registry.h>
#include <unordered_map>
// contains storage classes for block IDs inside chunks plus eventual lookup of block states
@ -51,26 +52,24 @@ namespace fp {
class mesh_storage {
private:
std::vector<float> vertices;
inline void add_and_translate(const float* array, const block_pos& pos) {
// since a chunk mesh contains all the faces for all the blocks inside the chunk
// we can add the translated values of predefined "unit" faces. This is for the simple "fast" chunk mesh generator.
for (int i = 0; i < VTX_ARR_SIZE; i+=3){
auto new_x = array[i] + (float)pos.x;
auto new_y = array[i + 1] + (float)pos.y;
auto new_z = array[i + 2] + (float)pos.z;
// BLT_TRACE("Creating translated vertex {%f, %f, %f} from array position [%d, %d, %d]", new_x, new_y, new_z, i, i + 1, i + 2);
vertices.push_back(new_x);
vertices.push_back(new_y);
vertices.push_back(new_z);
}
}
std::unordered_map<vertex, unsigned int, _static::vertex_hash, _static::vertex_equality> created_vertices_index;
std::vector<vertex> vertices;
std::vector<unsigned int> indices;
public:
/**
* since a chunk mesh contains all the faces for all the blocks inside the chunk
* we can add the translated values of predefined "unit" faces. This is for the simple "fast" chunk mesh generator.
* @param face the direction the face is facing to be added to the mesh.
* @param pos position of the face
*/
void addFace(face face, const block_pos& pos);
inline std::vector<float>& getVertices() {
inline std::vector<vertex>& getVertices() {
return vertices;
}
inline std::vector<unsigned int>& getIndices() {
return indices;
}
};
namespace mesh {

View File

@ -11,7 +11,9 @@
constexpr int CHUNK_SIZE = 32;
const int CHUNK_SHIFT = (int)(log(CHUNK_SIZE) / log(2));
// size that the base vertex arrays are assumed to be (per face)
constexpr int VTX_ARR_SIZE = 18;
constexpr int VTX_ARR_SIZE = 4;
constexpr float EPSILON = 0.0001f;
namespace fp {
@ -52,6 +54,13 @@ namespace fp {
block_pos(float x, float y, float z): block_pos(int(x), int(y), int(z)) {}
};
// to ensure this is a POD we define the vertex as a C-struct. This allows us to store one large vertex array and pass that to the GPU
// instead of sending arrays for the positions, UVs, normals, etc.
// since OpenGL allows us to specify attributes based on offsets from the same VBO.
typedef struct {
float x, y, z;
} vertex;
namespace _static {
// std::unordered_map requires a type. As a result the functions are encapsulated.
@ -64,11 +73,29 @@ namespace fp {
}
};
struct vertex_hash {
inline size_t operator()(const vertex& pos) const {
size_t p1 = std::hash<float>()(pos.x);
size_t p2 = std::hash<float>()(pos.y);
size_t p3 = std::hash<float>()(pos.z);
return (p1 ^ (p2 << 1)) ^ p3;
}
};
struct chunk_pos_equality {
inline bool operator()(const chunk_pos& p1, const chunk_pos& p2) const {
return p1.x == p2.x && p1.y == p2.y && p1.z == p2.z;
}
};
struct vertex_equality {
inline bool operator()(const vertex& p1, const vertex& p2) const {
return p1.x >= p2.x - EPSILON && p1.x <= p2.x + EPSILON && p1.y >= p2.y - EPSILON && p1.y <= p2.y + EPSILON && p1.z >= p2.z - EPSILON && p1.z <= p2.z + EPSILON;
}
// inline bool operator()(const vertex& p1, const vertex& p2) const {
// return p1.x == p2.x && p1.y == p2.y && p1.z == p2.z;
// }
};
}
}

View File

@ -85,7 +85,8 @@ namespace fp {
// since they both use the same amount of memory we will only store the vertices and draw with drawArrays, since it is less complex.
// set up the VBOs which will be later updated when the mesh is generated.
chunk_vao->bindVBO(new VBO(ARRAY_BUFFER, nullptr, 0), 0, 3);
chunk_vao->bindVBO(new VBO(ARRAY_BUFFER, nullptr, 0), 0, 3, GL_FLOAT, 3 * sizeof(float), 0);
chunk_vao->bindElementVBO(new VBO(ELEMENT_BUFFER, nullptr, 0));
}
~chunk() {

View File

@ -45,8 +45,10 @@ int main() {
chunk_shader = new fp::shader(shader_chunk_vert, shader_chunk_frag);
world = new fp::world();
world->setBlock({0,0,0}, 1);
for (int i = 1; i < CHUNK_SIZE; i++)
for (int j = 0; j < 3; j++)
for (int j = 0; j < 2; j++)
for (int k = 5; k < CHUNK_SIZE; k++)
world->setBlock({i,j,k}, 1);
world->setBlock({-2, 2, 2}, 1);

View File

@ -8,69 +8,55 @@
constexpr float scale = 0.5f;
const float x_positive_vertices[VTX_ARR_SIZE] = {
// +x first triangle
scale, -scale, scale, // +x top left
scale, scale, -scale, // +x bottom right
scale, scale, scale, // +x top right
// +x second triangle
scale, -scale, scale, // +x top left
scale, -scale, -scale, // +x bottom left
scale, scale, -scale, // +x bottom right
const fp::vertex x_positive_vertices[VTX_ARR_SIZE] = {
{scale, scale, scale}, // +x top right
{scale, scale, -scale}, // +x bottom right
{scale, -scale, -scale}, // +x bottom left
{scale, -scale, scale} // +x top left
};
const float x_negative_vertices[VTX_ARR_SIZE] = {
// -x first triangle
-scale, scale, scale, // -x top right
-scale, scale, -scale, // -x bottom right
-scale, -scale, scale, // -x top left
// -x second triangle
-scale, scale, -scale, // -x bottom right
-scale, -scale, -scale, // -x bottom left
-scale, -scale, scale, // -x top left
const fp::vertex x_negative_vertices[VTX_ARR_SIZE] = {
{-scale, scale, scale}, // -x top right
{-scale, scale, -scale}, // -x bottom right
{-scale, -scale, -scale}, // -x bottom left
{-scale, -scale, scale} // -x top left
};
const float y_positive_vertices[VTX_ARR_SIZE] = {
// first triangle
scale, scale, -scale, // top left
-scale, scale, scale, // bottom right
scale, scale, scale, // top right
// second triangle
scale, scale, -scale, // top left
-scale, scale, -scale, // bottom left
-scale, scale, scale, // bottom right
const fp::vertex y_positive_vertices[VTX_ARR_SIZE] = {
{scale, scale, scale}, // +y top right
{-scale, scale, scale}, // +y bottom right
{-scale, scale, -scale}, // +y bottom left
{scale, scale, -scale}, // +y top left
};
const float y_negative_vertices[VTX_ARR_SIZE] = {
// first triangle
scale, -scale, scale, // top right
-scale, -scale, scale, // bottom right
scale, -scale, -scale, // top left
// second triangle
-scale, -scale, scale, // bottom right
-scale, -scale, -scale, // bottom left
scale, -scale, -scale, // top left
const fp::vertex y_negative_vertices[VTX_ARR_SIZE] = {
{scale, -scale, scale}, // -y top right
{-scale, -scale, scale}, // -y bottom right
{-scale, -scale, -scale}, // -y bottom left
{scale, -scale, -scale}, // -y top left
};
const float z_positive_vertices[VTX_ARR_SIZE] = {
// first triangle
-scale, scale, scale, // top left
scale, -scale, scale, // bottom right
scale, scale, scale, // top right
// second triangle
-scale, scale, scale, // top left
-scale, -scale, scale, // bottom left
scale, -scale, scale, // bottom right
const fp::vertex z_positive_vertices[VTX_ARR_SIZE] = {
{scale, scale, scale}, // +z top right
{scale, -scale, scale}, // +z bottom right
{-scale, -scale, scale}, // +z bottom left
{-scale, scale, scale}, // +z top left
};
const float z_negative_vertices[VTX_ARR_SIZE] = {
// first triangle
scale, scale, -scale, // top right
scale, -scale, -scale, // bottom right
-scale, scale, -scale, // top left
// second triangle
scale, -scale, -scale, // bottom right
-scale, -scale, -scale, // bottom left
-scale, scale, -scale, // top left
const fp::vertex z_negative_vertices[VTX_ARR_SIZE] = {
{scale, scale, -scale}, // -z top right
{scale, -scale, -scale}, // -z bottom right
{-scale, -scale, -scale}, // -z bottom left
{-scale, scale, -scale}, // -z top left
};
// indices are the same on all axis but are flipped between negative / positive as a result of back-face culling.
const std::vector<unsigned int> negative_indices = {
0, 1, 3,
1, 2, 3
};
const std::vector<unsigned int> positive_indices = {
3, 1, 0,
3, 2, 1
};
// always ordered the same as the enum!
const float* face_decode[] = {
const fp::vertex* face_decode[] = {
x_positive_vertices,
x_negative_vertices,
y_positive_vertices,
@ -80,5 +66,35 @@ const float* face_decode[] = {
};
void fp::mesh_storage::addFace(fp::face face, const block_pos& pos) {
add_and_translate(face_decode[face], pos);
const auto* face_vertices = face_decode[face];
// negatives are odd numbered, positives are even.
const auto& face_indices = face % 2 == 0 ? positive_indices : negative_indices;
vertex translated_face_vertices[VTX_ARR_SIZE];
// generate translated vertices
for (int i = 0; i < VTX_ARR_SIZE; i++) {
translated_face_vertices[i].x = face_vertices[i].x + (float) pos.x;
translated_face_vertices[i].y = face_vertices[i].y + (float) pos.y;
translated_face_vertices[i].z = face_vertices[i].z + (float) pos.z;
}
for (unsigned int face_index : face_indices) {
// vertex associated with the index
auto index_vertex = translated_face_vertices[face_index];
// search to see if the vertex exists already inside the vertices face_vertices
auto find_existing_vertex = created_vertices_index.find(index_vertex);
if (find_existing_vertex == created_vertices_index.end()) {
// vertex doesn't already exist in the face_vertices.
// the current size contains the position we are inserting the vertex into. This is our index in the vertex face_vertices
auto current_index_pos = vertices.size();
vertices.push_back(index_vertex);
// Since we are inserting using the order of the face_indices this will ensure that the triangle vertices are ordered correctly (outward facing)
created_vertices_index.insert({index_vertex, current_index_pos});
indices.push_back(current_index_pos);
} else {
// does exist in the face_vertices we can use that knowledge to reduce the total # of vertices
indices.push_back(find_existing_vertex->second);
}
}
}

View File

@ -9,9 +9,9 @@ void fp::world::generateFullMesh(mesh_storage* mesh, fp::chunk* chunk) {
// checks to outside the bounds of the chunk should not have faces added. this will be handled by the partial mesh!
bool outside = false;
for (int i = 1; i < CHUNK_SIZE - 1; i++) {
for (int j = 1; j < CHUNK_SIZE - 1; j++) {
for (int k = 1; k < CHUNK_SIZE - 1; k++) {
for (int i = 0; i < CHUNK_SIZE; i++) {
for (int j = 0; j < CHUNK_SIZE; j++) {
for (int k = 0; k < CHUNK_SIZE; k++) {
auto block = chunk->storage->get({i, j, k});
// opaque visibility is always 0. Non-zero values (true) are what we care about since opaque blocks are completely hidden
if (!fp::registry::get(block).visibility) {
@ -46,7 +46,6 @@ inline void checkEdgeFaces(
}
void fp::world::generateEdgeMesh(mesh_storage* mesh, fp::chunk* chunk) {
BLT_TRACE("NOPE");
// don't try to regen the chunk mesh unless there is a chance all neighbours are not null
if (chunk->status != chunk_update_status::NEIGHBOUR_CREATE)
return;
@ -54,7 +53,6 @@ void fp::world::generateEdgeMesh(mesh_storage* mesh, fp::chunk* chunk) {
chunk_neighbours neighbours{};
getNeighbours(chunk->pos, neighbours);
BLT_TRACE("GOODBYE");
// if none of the neighbours exist we cannot continue!
for (auto* neighbour : neighbours.neighbours) {
@ -75,8 +73,6 @@ void fp::world::generateEdgeMesh(mesh_storage* mesh, fp::chunk* chunk) {
}
}
BLT_TRACE("HELLO");
chunk->status = NONE;
chunk->dirtiness = REFRESH;
}
@ -91,8 +87,6 @@ void fp::world::generateChunkMesh(fp::chunk* chunk) {
if (chunk->dirtiness == PARTIAL_MESH) { // partial chunk mesh (had null neighbours)
generateEdgeMesh(chunk->mesh, chunk);
}
chunk->dirtiness = REFRESH;
}
void fp::world::update() {
@ -114,14 +108,18 @@ void fp::world::render(fp::shader& shader) {
if (chunk->dirtiness == REFRESH) {
auto& vertices = chunk->mesh->getVertices();
auto& indices = chunk->mesh->getIndices();
// 11436 vert, 137,232 bytes
BLT_INFO("Chunk [%d, %d, %d] mesh updated with %d vertices and %d indices taking (%d, %d) bytes!",
chunk->pos.x, chunk->pos.y, chunk->pos.z,
vertices.size(), 0, vertices.size() * sizeof(float), 0 * sizeof(unsigned int));
vertices.size(), indices.size(), vertices.size() * sizeof(vertex), indices.size() * sizeof(unsigned int));
// upload the new vertices to the GPU
chunk->chunk_vao->getVBO(0)->update(vertices);
chunk->render_size = vertices.size() / 3;
chunk->chunk_vao->getVBO(-1)->update(indices);
chunk->render_size = indices.size();
// delete the memory from the CPU.
delete (chunk->mesh);
@ -135,7 +133,8 @@ void fp::world::render(fp::shader& shader) {
shader.setMatrix("translation", translation);
chunk->chunk_vao->bind();
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES, 0, (int) chunk->render_size);
//glDrawArrays(GL_TRIANGLES, 0, (int) chunk->render_size);
glDrawElements(GL_TRIANGLES, (int)chunk->render_size, GL_UNSIGNED_INT, nullptr);
glDisableVertexAttribArray(0);
}
}