summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2023-07-09 20:51:30 +0200
committerMel <einebeere@gmail.com>2023-07-09 20:51:30 +0200
commit680d9d5b7a61ac955fcec8a5622faa5cf4165c11 (patch)
tree385ada2ac5e666ff4e89e073850c9e1a9b29c99e
parentfe2baedc760c2f29e2c720f6b1132a2de33c5430 (diff)
downloadmeowcraft-680d9d5b7a61ac955fcec8a5622faa5cf4165c11.tar.zst
meowcraft-680d9d5b7a61ac955fcec8a5622faa5cf4165c11.zip
Ambient occlusion (without corners)
-rw-r--r--assets/shaders/fragment.glsl4
-rw-r--r--assets/shaders/vertex.glsl3
-rw-r--r--src/GFX/Mesh.hpp11
-rw-r--r--src/World/BlockSide.hpp46
-rw-r--r--src/World/Generation/ChunkMeshing.cpp217
-rw-r--r--src/World/Generation/ChunkMeshing.hpp5
-rw-r--r--src/World/World.cpp31
-rw-r--r--src/World/World.hpp1
-rw-r--r--src/main.cpp1
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<uint S = 3, typename T = float>
+ template<typename T = F32>
+ Attribute(
+ std::vector<T> v
+ ) : data_size(v.size()),
+ attribute_size(1),
+ type_size(sizeof(T)) {
+ data = copy(v.data(), v.size() * type_size);
+ }
+
+ template<uint S = 3, typename T = F32>
Attribute(
std::vector<Vector<S, T>> 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<Vector<3, F32>, 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<Vector<2, F32>, 4> face_tex_coords(BlockType type, BlockSide side) {
U8 atlas_width = 4;
@@ -9,15 +38,21 @@ std::array<Vector<2, F32>, 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<Vector<2, F32>, 4> {
auto t = y * height_step;
auto l = x * width_step;
auto b = t + height_step;
auto r = l + width_step;
- return std::array<Vector<2, F32>, 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<Vector<2, F32>, 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<Vector<2, F32>, 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<Vector<3, F32>, 4> face_normals(BlockSide side) {
}
Bool is_face_visible(Chunk& chunk, const ChunkMeshing::ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) {
- Vector<3, I32> offset{};
+std::array<F32, 4> face_ao_values(Chunk& chunk, const ChunkNeighbors& neighbors, U32 x, U32 y, U32 z, BlockSide side) {
+ std::array<Vector<3, I32>, 4> offsets{};
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:
+ 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;
}
- Vector<3, I32> neighbor_pos{
- (I32)x + offset.x(), (I32)y + offset.y(), (I32)z + offset.z(),
- };
- Chunk* chunk_to_ask;
+ std::array<F32, 4> vertex_ao{};
+
+ for (UInt vertex = 0; vertex < 4; vertex++) {
+ auto a = offsets[vertex];
+ auto b = offsets[(vertex + 1) % 4];
- 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 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;
}
- auto [neighbor] = chunk_to_ask->get(neighbor_pos.x(), neighbor_pos.y(), neighbor_pos.z());
+ 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::Right:
+ offset[0] = 1;
+ break;
+ case BlockSide::Left:
+ offset[0] = -1;
+ break;
+ }
+
+ Vector<3, I32> neighbor_pos{
+ x + offset.x(), y + offset.y(), z + offset.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<Vector<3, F32>> positions{};
std::vector<Vector<3, F32>> normals{};
std::vector<Vector<2, F32>> tex_coords{};
+ std::vector<F32> ambient_occlusion_values{};
std::vector<U32> 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<Chunk*, 8> 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 <memory>
#include <unordered_map>
#include <unordered_set>
#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;