From 680d9d5b7a61ac955fcec8a5622faa5cf4165c11 Mon Sep 17 00:00:00 2001 From: Mel Date: Sun, 9 Jul 2023 20:51:30 +0200 Subject: Ambient occlusion (without corners) --- assets/shaders/fragment.glsl | 4 +- assets/shaders/vertex.glsl | 3 + src/GFX/Mesh.hpp | 11 +- src/World/BlockSide.hpp | 46 ++++--- src/World/Generation/ChunkMeshing.cpp | 217 ++++++++++++++++++++++------------ src/World/Generation/ChunkMeshing.hpp | 5 +- src/World/World.cpp | 31 +++-- src/World/World.hpp | 1 - src/main.cpp | 1 + 9 files changed, 199 insertions(+), 120 deletions(-) diff --git a/assets/shaders/fragment.glsl b/assets/shaders/fragment.glsl index a7f019d..cc60d65 100644 --- a/assets/shaders/fragment.glsl +++ b/assets/shaders/fragment.glsl @@ -6,6 +6,7 @@ uniform vec3 sky_color; in vec3 surface_normal; in vec2 frag_tex_coord; +in float frag_ambient_occlusion; in float depth; out vec4 color; @@ -18,6 +19,7 @@ void main() { if (texture_color.a < 0.5) { discard; } - color = vec4(diffuse, 1.0) * texture_color; + float ao = 1 - frag_ambient_occlusion / 2; + color = vec4(diffuse, 1.0) * texture_color * ao; color = mix(vec4(sky_color, 1.0), color, 1 - depth); } \ No newline at end of file diff --git a/assets/shaders/vertex.glsl b/assets/shaders/vertex.glsl index 3ca1055..1e56809 100644 --- a/assets/shaders/vertex.glsl +++ b/assets/shaders/vertex.glsl @@ -7,8 +7,10 @@ uniform mat4 projection_matrix; layout (location = 0) in vec3 position; layout (location = 1) in vec3 normal; layout (location = 2) in vec2 tex_coord; +layout (location = 3) in float ambient_occlusion; out vec2 frag_tex_coord; +out float frag_ambient_occlusion; out vec3 surface_normal; out float depth; @@ -19,6 +21,7 @@ void main() { gl_Position = clip_position; frag_tex_coord = tex_coord; + frag_ambient_occlusion = ambient_occlusion; surface_normal = (model_matrix * vec4(normal, 0.0)).xyz; depth = clamp((length(view_position) - 75) / 125, 0.0, 1.0); } \ No newline at end of file diff --git a/src/GFX/Mesh.hpp b/src/GFX/Mesh.hpp index 1c39941..4aea8c6 100644 --- a/src/GFX/Mesh.hpp +++ b/src/GFX/Mesh.hpp @@ -10,7 +10,16 @@ namespace MC::GFX { class Mesh { public: struct Attribute { - template + template + Attribute( + std::vector v + ) : data_size(v.size()), + attribute_size(1), + type_size(sizeof(T)) { + data = copy(v.data(), v.size() * type_size); + } + + template Attribute( std::vector> v ) : data_size(v.size()), diff --git a/src/World/BlockSide.hpp b/src/World/BlockSide.hpp index 69df159..831982a 100644 --- a/src/World/BlockSide.hpp +++ b/src/World/BlockSide.hpp @@ -16,8 +16,8 @@ public: Top, Bottom, - Left, Right, + Left, }; BlockSide() = default; @@ -26,31 +26,27 @@ public: operator Value() const { return m_side; } std::array, 4> face() { + // Winding order: (0, 1, 2) (2, 3, 0) + // Note: OpenGL Coordinate system has a flipped z axis. switch (m_side) { - case Front: - return {{ - {0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 1.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 1.0f}, - }}; - case Back: - return {{ - {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, - }}; - case Top: - return {{ - {0.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, - }}; - case Bottom: - return {{ - {0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, - }}; - case Left: - return {{ - {0.0f, 0.0f, 0.0f},{0.0f, 0.0f, 1.0f},{0.0f, 1.0f, 1.0f}, {0.0f, 1.0f, 0.0f}, - }}; - case Right: - return {{ - {1.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, - }}; + case Front: return {{ + {0, 1, 1}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1} + }}; + case Back: return {{ + {0, 1, 0}, {1, 1, 0}, {1, 0, 0}, {0, 0, 0} + }}; + case Top: return {{ + {0, 1, 1}, {1, 1, 1}, {1, 1, 0}, {0, 1, 0} + }}; + case Bottom: return {{ + {0, 0, 1}, {0, 0, 0}, {1, 0, 0}, {1, 0, 1} + }}; + case Right: return {{ + {1, 1, 0}, {1, 1, 1}, {1, 0, 1}, {1, 0, 0} + }}; + case Left: return {{ + {0, 1, 0}, {0, 0, 0}, {0, 0, 1}, {0, 1, 1} + }}; } } diff --git a/src/World/Generation/ChunkMeshing.cpp b/src/World/Generation/ChunkMeshing.cpp index 8996169..8944c53 100644 --- a/src/World/Generation/ChunkMeshing.cpp +++ b/src/World/Generation/ChunkMeshing.cpp @@ -1,6 +1,35 @@ #include "ChunkMeshing.hpp" -namespace MC::World::Generation { +namespace MC::World::Generation::ChunkMeshing { + +Chunk::BlockData get_block_wrapping(const Chunk& chunk, const ChunkNeighbors& neighbors, Vector<3, I32> pos) { + const Chunk* chunk_to_ask; + + auto overflow = [](I32& c, I32 max) -> I8 { + if (c < 0) { c += max; return -1; } + if (c >= max) { c -= max; return 1; } + return 0; + }; + + auto xo = overflow(pos.x(), Chunk::Width); + auto yo = overflow(pos.y(), Chunk::Height); + auto zo = overflow(pos.z(), Chunk::Width); + + // Blocks above and below a chunk are always Air. + if (yo != 0) return {}; + + if (xo == 1 && zo == 1) { chunk_to_ask = neighbors.south_east; } + else if (xo == 1 && zo == -1) { chunk_to_ask = neighbors.north_east; } + else if (xo == -1 && zo == 1) { chunk_to_ask = neighbors.south_west; } + else if (xo == -1 && zo == -1) { chunk_to_ask = neighbors.north_west; } + else if (xo == 1) { chunk_to_ask = neighbors.east; } + else if (xo == -1) { chunk_to_ask = neighbors.west; } + else if (zo == 1) { chunk_to_ask = neighbors.south; } + else if (zo == -1) { chunk_to_ask = neighbors.north; } + else { chunk_to_ask = &chunk; } + + return chunk_to_ask->get(pos.x(), pos.y(), pos.z()); +} std::array, 4> face_tex_coords(BlockType type, BlockSide side) { U8 atlas_width = 4; @@ -9,15 +38,21 @@ std::array, 4> face_tex_coords(BlockType type, BlockSide side) { Real width_step = 1.0f / atlas_width; Real height_step = 1.0f / atlas_height; - auto block_coords = [=](U8 x, U8 y) { + auto block_coords = [=](U8 x, U8 y) -> std::array, 4> { auto t = y * height_step; auto l = x * width_step; auto b = t + height_step; auto r = l + width_step; - return std::array, 4>{{ - {l, b}, {r, b}, {r, t}, {l, t}, - }}; + // This is horrible and it was better before I restructured the vertex order... + // In the last version the front and back side pairs had the same structure in different winding, + // so a back_side check wasn't needed... + // Note: BlockSide::Front counts as a back side, because OpenGL has an inverted Z-axis compared to us. + Bool back_side = side == BlockSide::Front || side == BlockSide::Bottom || side == BlockSide::Left; + if (back_side) { + return {{{l, t}, {l, b}, {r, b}, {r, t}}}; + } + return {{{r, t}, {l, t}, {l, b}, {r, b}}}; }; switch (type) { @@ -25,15 +60,15 @@ std::array, 4> face_tex_coords(BlockType type, BlockSide side) { return block_coords(1, 0); case BlockType::Grass: switch (side) { - case BlockSide::Front: - case BlockSide::Back: - case BlockSide::Left: - case BlockSide::Right: - return block_coords(2, 0); - case BlockSide::Bottom: - return block_coords(1, 0); - case BlockSide::Top: - return block_coords(0, 0); + case BlockSide::Front: + case BlockSide::Back: + case BlockSide::Left: + case BlockSide::Right: + return block_coords(2, 0); + case BlockSide::Bottom: + return block_coords(1, 0); + case BlockSide::Top: + return block_coords(0, 0); } case BlockType::Stone: return block_coords(3, 0); @@ -43,26 +78,26 @@ std::array, 4> face_tex_coords(BlockType type, BlockSide side) { return block_coords(0, 1); case BlockType::Snow: switch (side) { - case BlockSide::Front: - case BlockSide::Back: - case BlockSide::Left: - case BlockSide::Right: - return block_coords(3, 1); - case BlockSide::Bottom: - return block_coords(1, 0); - case BlockSide::Top: - return block_coords(2, 1); + case BlockSide::Front: + case BlockSide::Back: + case BlockSide::Left: + case BlockSide::Right: + return block_coords(3, 1); + case BlockSide::Bottom: + return block_coords(1, 0); + case BlockSide::Top: + return block_coords(2, 1); } case BlockType::Wood: switch (side) { - case BlockSide::Front: - case BlockSide::Back: - case BlockSide::Right: - case BlockSide::Left: - return block_coords(0, 2); - case BlockSide::Bottom: - case BlockSide::Top: - return block_coords(1, 2); + case BlockSide::Front: + case BlockSide::Back: + case BlockSide::Right: + case BlockSide::Left: + return block_coords(0, 2); + case BlockSide::Bottom: + case BlockSide::Top: + return block_coords(1, 2); } case BlockType::Leaves: return block_coords(2, 2); @@ -84,76 +119,101 @@ std::array, 4> face_normals(BlockSide side) { } Bool is_face_visible(Chunk& chunk, const ChunkMeshing::ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { +std::array face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { + std::array, 4> offsets{}; + switch (side) { + case BlockSide::Front: + offsets = {{{0, 1, 1}, {-1, 0, 1}, {0, -1, 1}, {1, 0, 1}}}; // works! + break; + case BlockSide::Back: + offsets = {{{-1, 0, -1}, {0, 1, -1}, {1, 0, -1}, {0, -1, -1}}}; // works! + break; + case BlockSide::Top: + offsets = {{{-1, 1, 0}, {0, 1, 1}, {1, 1, 0}, {0, 1, -1}}}; + break; + case BlockSide::Bottom: + offsets = {{{0, -1, 1}, {-1, -1, 0}, {0, -1, -1}, {1, -1, 0}}}; + break; + case BlockSide::Right: + offsets = {{{1, 0, -1}, {1, 1, 0}, {1, 0, 1}, {1, -1, 0}}}; + break; + case BlockSide::Left: + offsets = {{{-1, 1, 0}, {-1, 0, -1}, {-1, -1, 0}, {-1, 0, 1}}}; + break; + } + + + std::array vertex_ao{}; + + for (UInt vertex = 0; vertex < 4; vertex++) { + auto a = offsets[vertex]; + auto b = offsets[(vertex + 1) % 4]; + + auto block_a = get_block_wrapping(chunk, neighbors, {x + a.x(), y + a.y(), z + a.z()}); + auto block_b = get_block_wrapping(chunk, neighbors, {x + b.x(), y + b.y(), z + b.z()}); + + F32 occlusion_a = block_a.empty() ? 0 : 1; + F32 occlusion_b = block_b.empty() ? 0 : 1; + + vertex_ao[vertex] = (occlusion_a + occlusion_b) / 2; + } + + return vertex_ao; +} + +Bool is_face_visible(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) { Vector<3, I32> offset{}; switch (side) { - case BlockSide::Front: - offset[2] = 1; - break; - case BlockSide::Back: - offset[2] = -1; - break; - case BlockSide::Top: - offset[1] = 1; - break; - case BlockSide::Bottom: - offset[1] = -1; - break; - case BlockSide::Left: - offset[0] = -1; - break; - case BlockSide::Right: - offset[0] = 1; - break; + case BlockSide::Front: + offset[2] = 1; + break; + case BlockSide::Back: + offset[2] = -1; + break; + case BlockSide::Top: + offset[1] = 1; + break; + case BlockSide::Bottom: + offset[1] = -1; + break; + case BlockSide::Right: + offset[0] = 1; + break; + case BlockSide::Left: + offset[0] = -1; + break; } Vector<3, I32> neighbor_pos{ - (I32)x + offset.x(), (I32)y + offset.y(), (I32)z + offset.z(), + x + offset.x(), y + offset.y(), z + offset.z(), }; - Chunk* chunk_to_ask; - - if (neighbor_pos.z() < 0) { - chunk_to_ask = &neighbors.north; - neighbor_pos.z() += Chunk::Width; - } else if (neighbor_pos.x() >= (I32)Chunk::Width) { - chunk_to_ask = &neighbors.east; - neighbor_pos.x() -= Chunk::Width; - } else if (neighbor_pos.z() >= (I32)Chunk::Width) { - chunk_to_ask = &neighbors.south; - neighbor_pos.z() -= Chunk::Width; - } else if (neighbor_pos.x() < 0) { - chunk_to_ask = &neighbors.west; - neighbor_pos.x() += Chunk::Width; - } else { - chunk_to_ask = &chunk; - } - - auto [neighbor] = chunk_to_ask->get(neighbor_pos.x(), neighbor_pos.y(), neighbor_pos.z()); + auto [neighbor] = get_block_wrapping(chunk, neighbors, neighbor_pos); return neighbor.is_transparent(); } -GFX::Mesh ChunkMeshing::create_mesh_for_chunk(Chunk& chunk, const ChunkNeighbors& neighbors) { +GFX::Mesh create_mesh_for_chunk(Chunk& chunk, const ChunkNeighbors& neighbors) { std::vector> positions{}; std::vector> normals{}; std::vector> tex_coords{}; + std::vector ambient_occlusion_values{}; std::vector indices{}; for (Int x = 0; x < Chunk::Width; x++) { for (Int y = 0; y < Chunk::Height; y++) { for (Int z = 0; z < Chunk::Width; z++) { - auto type = chunk.get(x, y, z).type; - if (type == BlockType::Air) { + auto block = chunk.get(x, y, z); + if (block.empty()) continue; - } for (auto side: BlockSide::all()) { - if (!is_face_visible(chunk, neighbors, x, y, z, side)) { + if (!is_face_visible(chunk, neighbors, x, y, z, side)) continue; - } auto side_positions = side.face(); auto side_normals = face_normals(side); - auto side_tex_coords = face_tex_coords(type, side); + auto side_tex_coords = face_tex_coords(block.type, side); + auto side_ao = face_ao_values(chunk, neighbors, x, y, z, side); for (auto& position : side_positions) { position = position + Vector<3, F32>{x, y, z}; @@ -164,14 +224,15 @@ GFX::Mesh ChunkMeshing::create_mesh_for_chunk(Chunk& chunk, const ChunkNeighbors positions.insert(positions.end(), side_positions.begin(), side_positions.end()); normals.insert(normals.end(), side_normals.begin(), side_normals.end()); tex_coords.insert(tex_coords.end(), side_tex_coords.begin(), side_tex_coords.end()); - indices.insert(indices.end(), {s, s + 1, s + 3, s + 1, s + 2, s + 3}); + ambient_occlusion_values.insert(ambient_occlusion_values.end(), side_ao.begin(), side_ao.end()); + indices.insert(indices.end(), {s + 0, s + 1, s + 2, s + 2, s + 3, s + 0 }); } } } } return { - {positions, normals, tex_coords}, + {positions, normals, tex_coords, ambient_occlusion_values}, indices, }; } diff --git a/src/World/Generation/ChunkMeshing.hpp b/src/World/Generation/ChunkMeshing.hpp index 6ec77af..dd9446b 100644 --- a/src/World/Generation/ChunkMeshing.hpp +++ b/src/World/Generation/ChunkMeshing.hpp @@ -5,7 +5,10 @@ namespace MC::World::Generation::ChunkMeshing { -struct ChunkNeighbors { Chunk &north, &east, &south, &west; }; +struct ChunkNeighbors { + Chunk *north, *east, *south, *west; + Chunk *north_east, *south_east, *south_west, *north_west; +}; GFX::Mesh create_mesh_for_chunk(Chunk& chunk, const ChunkNeighbors& neighbors); } diff --git a/src/World/World.cpp b/src/World/World.cpp index a0b2b0d..d77ebe8 100644 --- a/src/World/World.cpp +++ b/src/World/World.cpp @@ -114,23 +114,28 @@ U64 World::timestamp() { void World::try_to_create_mesh_for_chunk(ChunkData& data) { auto index = data.index; - auto north = get({index.x, index.y - 1}); - auto east = get({index.x + 1, index.y}); - auto south = get({index.x, index.y + 1}); - auto west = get({index.x - 1, index.y}); + UInt neighbor_index = 0; + std::array neighbors; + for (I32 x = -1; x <= 1; x++) { + for (I32 y = -1; y <= 1; y++) { + if (x == 0 && y == 0) continue; - auto no_terrain = [](const ChunkData& d){ - return !d.chunk.has_value(); - }; + auto& neighbor_data = get({index.x + x, index.y + y}); + if (!neighbor_data.chunk.has_value()) return; // All neighbors need to be generated first. - if (no_terrain(north) || no_terrain(east) || no_terrain(south) || no_terrain(west)) { - return; + neighbors[neighbor_index++] = &neighbor_data.chunk.value(); + } } - data.mesh_data = Generation::ChunkMeshing::create_mesh_for_chunk( - data.chunk.value(), - {north.chunk.value(), east.chunk.value(), south.chunk.value(), west.chunk.value()} - ); + // Layout of neighboring chunks in `neighbors` array: + // (-1; -1) > (-1; 0) > (-1; 1) > (0; -1) + // ( 0; 1) > ( 1; -1) > ( 1; 0) > (1; 1) + Generation::ChunkMeshing::ChunkNeighbors chunk_neighbors { + neighbors[3], neighbors[6], neighbors[4], neighbors[1], + neighbors[5], neighbors[7], neighbors[2], neighbors[0], + }; + + data.mesh_data = create_mesh_for_chunk(data.chunk.value(), chunk_neighbors); data.mesh = GFX::Binder::load(data.mesh_data.value()); data.status = ChunkStatus::Done; } diff --git a/src/World/World.hpp b/src/World/World.hpp index ae9f2a4..a094526 100644 --- a/src/World/World.hpp +++ b/src/World/World.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include "Generation/Generator.hpp" diff --git a/src/main.cpp b/src/main.cpp index 6a6f9ee..c690391 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,6 +83,7 @@ void run() { glDepthFunc(GL_LEQUAL); glEnable(GL_CULL_FACE); + glFrontFace(GL_CCW); glCullFace(GL_BACK); U64 time = 0; -- cgit 1.4.1