[Add] Added detailmap for more detailed terrain.

This commit is contained in:
Ritchie Cunningham 2025-09-17 23:14:48 +01:00
parent 6186952313
commit 8c701d39b7
14 changed files with 112 additions and 49 deletions

View File

@ -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);
}

View File

@ -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);
}

View File

@ -14,8 +14,10 @@ const int CHUNK_DATA_HEIGHT = CHUNK_HEIGHT + CHUNK_BORDER_SIZE * 2;
struct Chunk {
std::vector<float> heightmap;
std::vector<float> 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. */

View File

@ -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. */

View File

@ -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);

View File

@ -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<ChunkPos, ChunkMesh*>& get_chunk_meshes(void) const {
return _chunk_meshes;
}

View File

@ -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. */

View File

@ -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);
}

View File

@ -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. */

View File

@ -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:

View File

@ -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);
}

View File

@ -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));

View File

@ -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);
}
}
}

View File

@ -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<ChunkPos, BettolaLib::Game::Chunk> _chunks;
};