From 8c701d39b77958627a5874525e7ea9e7dbbabcb7 Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Wed, 17 Sep 2025 23:14:48 +0100 Subject: [PATCH] [Add] Added detailmap for more detailed terrain. --- assets/shaders/simple.frag | 37 +++++++-- assets/shaders/simple.vert | 5 ++ libbettola/include/bettola/game/chunk.h | 4 +- .../include/bettola/network/chunk_message.h | 1 + src/game/world.cpp | 3 +- src/game/world.h | 2 +- src/game_client.cpp | 2 +- src/graphics/chunk_mesh.cpp | 12 ++- src/graphics/renderer.cpp | 81 ++++++++++--------- src/graphics/shader.h | 1 + src/graphics/shader_uniforms.cpp | 4 + srv/game/game_send_chunks.cpp | 1 + srv/game/world.cpp | 7 ++ srv/game/world.h | 1 + 14 files changed, 112 insertions(+), 49 deletions(-) diff --git a/assets/shaders/simple.frag b/assets/shaders/simple.frag index a763c51..3d8c905 100644 --- a/assets/shaders/simple.frag +++ b/assets/shaders/simple.frag @@ -3,21 +3,48 @@ out vec4 FragColor; in vec3 FragPos; in vec3 Normal; +in vec3 WorldPos; +in float Detail; uniform vec3 objectColor; uniform vec3 lightColor; uniform vec3 lightDir; /* Normalised direction vector for the light source. */ +uniform bool u_IsTerrain; void main() { - /* Ambient lighting (cheap, constant base light) */ - float ambientStrength = 0.3; + vec3 finalColor; + vec3 norm = normalize(Normal); + + /* Define colours for different terrain types. */ + if(u_IsTerrain) { + vec3 grassColor1 = vec3(0.4, 0.6, 0.2); + vec3 grassColor2 = vec3(0.3, 0.5, 0.15); + vec3 rockColor = vec3(0.5, 0.5, 0.4); + vec3 steepColor = vec3(0.35, 0.3, 0.25); + + /* Create patchy grass colur using the detail noise. */ + float detailFactor = smoothstep(-0.2, 0.2, Detail); + vec3 finalGrassColor = mix(grassColor1, grassColor2, detailFactor); + + /* Blend from grass to rock between height of 2.0 and 4.0. */ + float heightFactor = smoothstep(2.0, 4.0, WorldPos.y); + finalColor = mix(finalGrassColor, rockColor, heightFactor); + + /* Flat surface has a normal.y of 1.0. A steep slope is closer to 0. */ + float slopeFactor = 1.0 - smoothstep(0.6, 0.8, norm.y); + finalColor = mix(finalColor, steepColor, slopeFactor); + + } else { + finalColor = objectColor; + } + + /* Standard lighting calculations. */ + float ambientStrength = 0.4; vec3 ambient = ambientStrength * lightColor; - /* Diffuse lighting (depends on angle of the surface) */ - vec3 norm = normalize(Normal); float diff = max(dot(norm, normalize(-lightDir)), 0.0); vec3 diffuse = diff * lightColor; - vec3 result = (ambient + diffuse) * objectColor; + vec3 result = (ambient + diffuse) * finalColor; FragColor = vec4(result, 1.0); } diff --git a/assets/shaders/simple.vert b/assets/shaders/simple.vert index f2a6086..98f6203 100644 --- a/assets/shaders/simple.vert +++ b/assets/shaders/simple.vert @@ -1,9 +1,12 @@ #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; +layout (location = 2) in float aDetail; out vec3 FragPos; out vec3 Normal; +out vec3 WorldPos; +out float Detail; uniform mat4 model; uniform mat4 view; @@ -12,7 +15,9 @@ uniform mat4 projection; void main() { /* Pass position and normal vectors to the fragment shader in world space. */ FragPos = vec3(model * vec4(aPos, 1.0)); + WorldPos = FragPos; Normal = mat3(transpose(inverse(model))) * aNormal; + Detail = aDetail; gl_Position = projection * view * vec4(FragPos, 1.0); } diff --git a/libbettola/include/bettola/game/chunk.h b/libbettola/include/bettola/game/chunk.h index 2b66dfb..4be36db 100644 --- a/libbettola/include/bettola/game/chunk.h +++ b/libbettola/include/bettola/game/chunk.h @@ -14,8 +14,10 @@ const int CHUNK_DATA_HEIGHT = CHUNK_HEIGHT + CHUNK_BORDER_SIZE * 2; struct Chunk { std::vector heightmap; + std::vector detailmap; - Chunk(void) : heightmap(CHUNK_DATA_WIDTH * CHUNK_DATA_HEIGHT) {} + Chunk(void) : heightmap(CHUNK_DATA_WIDTH * CHUNK_DATA_HEIGHT), + detailmap(CHUNK_DATA_WIDTH * CHUNK_DATA_HEIGHT) {} }; } /* namespace Game. */ diff --git a/libbettola/include/bettola/network/chunk_message.h b/libbettola/include/bettola/network/chunk_message.h index d367ae5..7caf421 100644 --- a/libbettola/include/bettola/network/chunk_message.h +++ b/libbettola/include/bettola/network/chunk_message.h @@ -8,6 +8,7 @@ struct ChunkMessage { int chunk_x; int chunk_z; float heightmap[Game::CHUNK_DATA_WIDTH*Game::CHUNK_DATA_HEIGHT]; + float detailmap[Game::CHUNK_DATA_WIDTH*Game::CHUNK_DATA_HEIGHT]; }; } /* namespace Network. */ diff --git a/src/game/world.cpp b/src/game/world.cpp index 04c9df9..6d511d9 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -10,7 +10,7 @@ World::~World(void) { } } -void World::add_chunk(int chunk_x, int chunk_z, const float* heightmap) { +void World::add_chunk(int chunk_x, int chunk_z, const float* heightmap, const float* detailmap) { ChunkPos pos = {chunk_x, chunk_z}; if(_chunk_meshes.count(pos)) { /* TODO: Chunk already exists, perhaps update it later.. */ @@ -19,6 +19,7 @@ void World::add_chunk(int chunk_x, int chunk_z, const float* heightmap) { BettolaLib::Game::Chunk chunk; memcpy(chunk.heightmap.data(), heightmap, chunk.heightmap.size()*sizeof(float)); + memcpy(chunk.detailmap.data(), heightmap, chunk.detailmap.size()*sizeof(float)); _chunks[pos] = chunk; _chunk_meshes[pos] = new ChunkMesh(chunk_x, chunk_z, chunk); diff --git a/src/game/world.h b/src/game/world.h index 0b023e9..4fd73af 100644 --- a/src/game/world.h +++ b/src/game/world.h @@ -20,7 +20,7 @@ public: ~World(void); float get_height(float world_x, float world_z) const; - void add_chunk(int chunk_x, int chunk_z, const float* heightmap); + void add_chunk(int chunk_x, int chunk_z, const float* heightmap, const float* detailmap); const std::map& get_chunk_meshes(void) const { return _chunk_meshes; } diff --git a/src/game_client.cpp b/src/game_client.cpp index 3dac4b8..b7a735c 100644 --- a/src/game_client.cpp +++ b/src/game_client.cpp @@ -79,7 +79,7 @@ void GameClient::process_network_messages(void) { if(header.size == sizeof(BettolaLib::Network::ChunkMessage)) { BettolaLib::Network::ChunkMessage msg; _tcp_socket.recv(&msg, sizeof(msg)); - _world.add_chunk(msg.chunk_x, msg.chunk_z, msg.heightmap); + _world.add_chunk(msg.chunk_x, msg.chunk_z, msg.heightmap, msg.detailmap); } } else { /* Discard message payloads we don't understand to prevent de-sync. */ diff --git a/src/graphics/chunk_mesh.cpp b/src/graphics/chunk_mesh.cpp index fc4d017..11078b7 100644 --- a/src/graphics/chunk_mesh.cpp +++ b/src/graphics/chunk_mesh.cpp @@ -24,6 +24,10 @@ ChunkMesh::ChunkMesh(int chunk_x, int chunk_z, const BettolaLib::Game::Chunk& ch _vertices.push_back(normal.x); _vertices.push_back(normal.y); _vertices.push_back(normal.z); + + /* Pack the detail noise into the 4th component of the vertex data. */ + float detail_noise = chunk.detailmap[data_z*BettolaLib::Game::CHUNK_DATA_WIDTH+data_x]; + _vertices.push_back(detail_noise); } } @@ -95,13 +99,17 @@ void ChunkMesh::_setup_mesh(void) { glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indices.size()*sizeof(unsigned int), _indices.data(), GL_STATIC_DRAW); /* Position attrib. */ - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 7*sizeof(float), (void*)0); glEnableVertexAttribArray(0); /* Normal attrib. */ - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float))); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 7*sizeof(float), (void*)(3*sizeof(float))); glEnableVertexAttribArray(1); + /* Detail noise attrib. */ + glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 7*sizeof(float), (void*)(6*sizeof(float))); + glEnableVertexAttribArray(2); + glBindVertexArray(0); } diff --git a/src/graphics/renderer.cpp b/src/graphics/renderer.cpp index e39eaf0..2c5d049 100644 --- a/src/graphics/renderer.cpp +++ b/src/graphics/renderer.cpp @@ -53,47 +53,48 @@ bool Renderer::init(int screen_width, int screen_height) { // Definitive, correct 3D cube vertices float vertices[] = { - -0.5f, -0.5f, -0.5f, - 0.5f, -0.5f, -0.5f, - 0.5f, 0.5f, -0.5f, - 0.5f, 0.5f, -0.5f, - -0.5f, 0.5f, -0.5f, - -0.5f, -0.5f, -0.5f, + /* Positions. */ /* Normals. */ + -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, + -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.5f, -0.5f, 0.5f, - 0.5f, 0.5f, 0.5f, - 0.5f, 0.5f, 0.5f, - -0.5f, 0.5f, 0.5f, - -0.5f, -0.5f, 0.5f, + -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, + -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.5f, 0.5f, -0.5f, - -0.5f, -0.5f, -0.5f, - -0.5f, -0.5f, -0.5f, - -0.5f, -0.5f, 0.5f, - -0.5f, 0.5f, 0.5f, + -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, + -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, - 0.5f, 0.5f, -0.5f, - 0.5f, -0.5f, -0.5f, - 0.5f, -0.5f, -0.5f, - 0.5f, -0.5f, 0.5f, - 0.5f, 0.5f, 0.5f, + 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, + 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, - 0.5f, -0.5f, -0.5f, - 0.5f, -0.5f, 0.5f, - 0.5f, -0.5f, 0.5f, - -0.5f, -0.5f, 0.5f, - -0.5f, -0.5f, -0.5f, + -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, + -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.5f, 0.5f, -0.5f, - 0.5f, 0.5f, 0.5f, - 0.5f, 0.5f, 0.5f, - -0.5f, 0.5f, 0.5f, - -0.5f, 0.5f, -0.5f + -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, + -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f }; glGenVertexArrays(1, &_vao); @@ -101,8 +102,11 @@ bool Renderer::init(int screen_width, int screen_height) { glBindVertexArray(_vao); glBindBuffer(GL_ARRAY_BUFFER, _vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0); + /* Position attribute. */ + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)0); glEnableVertexAttribArray(0); + /* Normal attribute. */ + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float))); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); @@ -154,15 +158,16 @@ void Renderer::render(const Camera& camera, const Player& player, /* Render world. */ /* Set lighting uniforms for terrain. */ - _shader.set_vec3("objectColor", { 0.4f, 0.6f, 0.2f}); /* Green. */ _shader.set_vec3("lightColor", { 1.0f, 1.0f, 1.0f}); _shader.set_vec3("lightDir", {-0.5f, -1.0f, -0.5f}); /* From above and to the side. */ + _shader.set_bool("u_IsTerrain", true); for(auto const& [pos, mesh] : world.get_chunk_meshes()) { BettolaMath::Mat4 model; _shader.set_mat4("model", model); mesh->draw(); } + _shader.set_bool("u_IsTerrain", false); /* Draw the local player's cube. */ _shader.set_vec3("objectColor", {0.2f, 0.5f, 0.8f}); /* Player colour. */ diff --git a/src/graphics/shader.h b/src/graphics/shader.h index 65cd257..90af8f7 100644 --- a/src/graphics/shader.h +++ b/src/graphics/shader.h @@ -13,6 +13,7 @@ public: void use(void); void set_mat4(const std::string& name, const BettolaMath::Mat4& matrix) const; + void set_bool(const std::string& name, bool value) const; void set_vec3(const std::string& name, const BettolaMath::Vec3& value) const; unsigned int get_id(void) const { return _program_id; } private: diff --git a/src/graphics/shader_uniforms.cpp b/src/graphics/shader_uniforms.cpp index a2796a4..7a5a3f1 100644 --- a/src/graphics/shader_uniforms.cpp +++ b/src/graphics/shader_uniforms.cpp @@ -10,3 +10,7 @@ void Shader::set_mat4(const std::string& name, const BettolaMath::Mat4& matrix) void Shader::set_vec3(const std::string& name, const BettolaMath::Vec3& value) const { glUniform3f(glGetUniformLocation(_program_id, name.c_str()), value.x, value.y, value.z); } + +void Shader::set_bool(const std::string& name, bool value) const { + glUniform1i(glGetUniformLocation(_program_id, name.c_str()), (int)value); +} diff --git a/srv/game/game_send_chunks.cpp b/srv/game/game_send_chunks.cpp index 2ca899b..e46dc48 100644 --- a/srv/game/game_send_chunks.cpp +++ b/srv/game/game_send_chunks.cpp @@ -21,6 +21,7 @@ void Game::_send_chunks_around(Player* player, int center_x, int center_z) { msg.chunk_x = x; msg.chunk_z = z; memcpy(msg.heightmap, chunk.heightmap.data(), chunk.heightmap.size() * sizeof(float)); + memcpy(msg.detailmap, chunk.detailmap.data(), chunk.detailmap.size() * sizeof(float)); player->get_socket().send(&header, sizeof(header)); player->get_socket().send(&msg, sizeof(msg)); diff --git a/srv/game/world.cpp b/srv/game/world.cpp index 0518096..d23d95e 100644 --- a/srv/game/world.cpp +++ b/srv/game/world.cpp @@ -14,6 +14,12 @@ World::World(void) { _noise.SetFractalOctaves(5); _noise.SetFractalLacunarity(2.0f); _noise.SetFractalGain(0.5); + + /* Use a different seed for the detail noise. */ + _detail_noise.SetSeed(std::chrono::high_resolution_clock::now().time_since_epoch().count()+1); + + _detail_noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); + _detail_noise.SetFrequency(0.2f); /* Higher frequency for smaller details. */ } BettolaLib::Game::Chunk& World::get_chunk(int x, int z) { @@ -40,6 +46,7 @@ void World::_generate_chunk(BettolaLib::Game::Chunk& chunk, int chunk_x, int chu /* generate noise value. */ chunk.heightmap[z * BettolaLib::Game::CHUNK_DATA_WIDTH + x] = fabsf(_noise.GetNoise(world_x, world_z)); + chunk.detailmap[z * BettolaLib::Game::CHUNK_DATA_WIDTH + x] = _detail_noise.GetNoise(world_x, world_z); } } } diff --git a/srv/game/world.h b/srv/game/world.h index 1fb4c6f..b734869 100644 --- a/srv/game/world.h +++ b/srv/game/world.h @@ -24,6 +24,7 @@ private: void _generate_chunk(BettolaLib::Game::Chunk& chunk, int chunk_x, int chunk_z); FastNoiseLite _noise; + FastNoiseLite _detail_noise; std::map _chunks; };