[Add] Terrain lighting.
* Chunks are now rendered as solid, lit meshes instead of wireframe. * The GLSL shaders have been updated to a directional lighting model. * Chunkmes now calculates normal vectors for each vertex to enable the lighting. * Shader class now has a 'set_vec3' method for sending lighting date to the GPU. Bug Fix: * Resolves visual artifacts and "seams" at chunk boundaries. Next up: * To calculate the correct angle for a vertex at the edge of chunk A, we need to know the height of the terrain in the neighboiring Chunk B. Since it doesn't have that information, the GPU's making an appoximate guess. We need to generate and send a small "border" of height data along with each chunk, I'll do this by increasing the size of the heightmap array in the network message so it can hold the 32x32 chunk plus 1 vertex border all around. MAking it 34x34 for each chunk.
This commit is contained in:
parent
744c41b8ce
commit
fa7159587d
@ -1,9 +1,23 @@
|
|||||||
#version 330 core
|
#version 330 core
|
||||||
|
|
||||||
out vec4 FragColor;
|
out vec4 FragColor;
|
||||||
|
|
||||||
uniform vec3 overrideColor;
|
in vec3 FragPos;
|
||||||
|
in vec3 Normal;
|
||||||
|
|
||||||
|
uniform vec3 objectColor;
|
||||||
|
uniform vec3 lightColor;
|
||||||
|
uniform vec3 lightDir; /* Normalised direction vector for the light source. */
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
FragColor = vec4(overrideColor, 1.0f);
|
/* Ambient lighting (cheap, constant base light) */
|
||||||
|
float ambientStrength = 0.3;
|
||||||
|
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;
|
||||||
|
FragColor = vec4(result, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,18 @@
|
|||||||
#version 330 core
|
#version 330 core
|
||||||
|
|
||||||
layout (location = 0) in vec3 aPos;
|
layout (location = 0) in vec3 aPos;
|
||||||
|
layout (location = 1) in vec3 aNormal;
|
||||||
|
|
||||||
|
out vec3 FragPos;
|
||||||
|
out vec3 Normal;
|
||||||
|
|
||||||
uniform mat4 model;
|
uniform mat4 model;
|
||||||
uniform mat4 view;
|
uniform mat4 view;
|
||||||
uniform mat4 projection;
|
uniform mat4 projection;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = projection * view * model * vec4(aPos, 1.0);
|
/* Pass position and normal vectors to the fragment shader in world space. */
|
||||||
|
FragPos = vec3(model * vec4(aPos, 1.0));
|
||||||
|
Normal = mat3(transpose(inverse(model))) * aNormal;
|
||||||
|
|
||||||
|
gl_Position = projection * view * vec4(FragPos, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,8 +25,8 @@ void World::add_chunk(int chunk_x, int chunk_z, const float* heightmap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float World::get_height(float world_x, float world_z) const {
|
float World::get_height(float world_x, float world_z) const {
|
||||||
float chunk_width = (float)(BettolaLib::Game::CHUNK_WIDTH -1);
|
float chunk_width = (float)(BettolaLib::Game::CHUNK_WIDTH - 1);
|
||||||
float chunk_height = (float)(BettolaLib::Game::CHUNK_HEIGHT-1);
|
float chunk_height = (float)(BettolaLib::Game::CHUNK_HEIGHT - 1);
|
||||||
|
|
||||||
int chunk_x = floor(world_x / chunk_width);
|
int chunk_x = floor(world_x / chunk_width);
|
||||||
int chunk_z = floor(world_z / chunk_height);
|
int chunk_z = floor(world_z / chunk_height);
|
||||||
|
|||||||
@ -146,6 +146,7 @@ void GameClient::_process_game_state(const BettolaLib::Network::GameStateMessage
|
|||||||
BettolaMath::Vec3 cam_front = { input_msg.cam_front_x, input_msg.cam_front_y,
|
BettolaMath::Vec3 cam_front = { input_msg.cam_front_x, input_msg.cam_front_y,
|
||||||
input_msg.cam_front_z };
|
input_msg.cam_front_z };
|
||||||
_player.set_velocity_direction(input_state, cam_front);
|
_player.set_velocity_direction(input_state, cam_front);
|
||||||
|
_player.update(input_msg.dt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Remote player. Find if we already know about them.. */
|
/* Remote player. Find if we already know about them.. */
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
#include "bettola/game/chunk.h"
|
#include "bettola/game/chunk.h"
|
||||||
|
#include "bettola/math/vec3.h"
|
||||||
#include "chunk_mesh.h"
|
#include "chunk_mesh.h"
|
||||||
|
|
||||||
ChunkMesh::ChunkMesh(int chunk_x, int chunk_z, const BettolaLib::Game::Chunk& chunk) {
|
ChunkMesh::ChunkMesh(int chunk_x, int chunk_z, const BettolaLib::Game::Chunk& chunk) {
|
||||||
@ -7,9 +10,15 @@ ChunkMesh::ChunkMesh(int chunk_x, int chunk_z, const BettolaLib::Game::Chunk& ch
|
|||||||
for(int z = 0; z < BettolaLib::Game::CHUNK_HEIGHT; ++z) {
|
for(int z = 0; z < BettolaLib::Game::CHUNK_HEIGHT; ++z) {
|
||||||
for(int x = 0; x < BettolaLib::Game::CHUNK_WIDTH; ++x) {
|
for(int x = 0; x < BettolaLib::Game::CHUNK_WIDTH; ++x) {
|
||||||
/* Vertex position. */
|
/* Vertex position. */
|
||||||
_vertices.push_back((float)(chunk_x * (BettolaLib::Game::CHUNK_WIDTH-1) + x));
|
_vertices.push_back((float)(chunk_x * (BettolaLib::Game::CHUNK_WIDTH-1) + x));
|
||||||
_vertices.push_back(chunk.heightmap[z * BettolaLib::Game::CHUNK_WIDTH+x] * 5.0f); /* 5x scale height */
|
_vertices.push_back(chunk.heightmap[z * BettolaLib::Game::CHUNK_WIDTH+x] * 5.0f); /* 5x scale height */
|
||||||
_vertices.push_back((float)(chunk_z * (BettolaLib::Game::CHUNK_HEIGHT-1) + z));
|
_vertices.push_back((float)(chunk_z * (BettolaLib::Game::CHUNK_HEIGHT-1) + z));
|
||||||
|
|
||||||
|
/* Vertex normal. */
|
||||||
|
BettolaMath::Vec3 normal = _calculate_normal(x, z, chunk);
|
||||||
|
_vertices.push_back(normal.x);
|
||||||
|
_vertices.push_back(normal.y);
|
||||||
|
_vertices.push_back(normal.z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +45,28 @@ ChunkMesh::ChunkMesh(int chunk_x, int chunk_z, const BettolaLib::Game::Chunk& ch
|
|||||||
_setup_mesh();
|
_setup_mesh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BettolaMath::Vec3 ChunkMesh::_calculate_normal(int x, int z, const BettolaLib::Game::Chunk& chunk) {
|
||||||
|
/* Get heights of adjacent certices. */
|
||||||
|
float height_l = chunk.heightmap[z*BettolaLib::Game::CHUNK_WIDTH+(x>0?x-1 : x)] * 5.0f;
|
||||||
|
float height_r = chunk.heightmap[z*BettolaLib::Game::CHUNK_WIDTH+
|
||||||
|
(x<BettolaLib::Game::CHUNK_WIDTH-1?x+1 : x)]*5.0f;
|
||||||
|
float height_d = chunk.heightmap[(z>0?z-1 : z)*BettolaLib::Game::CHUNK_WIDTH+x]*5.0f;
|
||||||
|
float height_u = chunk.heightmap[(z<BettolaLib::Game::CHUNK_HEIGHT-1?z+1 : z)
|
||||||
|
* BettolaLib::Game::CHUNK_WIDTH+x]*5.0f;
|
||||||
|
|
||||||
|
BettolaMath::Vec3 normal = { height_l - height_r, 2.0f, height_d - height_u };
|
||||||
|
|
||||||
|
/* Normalise the normal. */
|
||||||
|
float mag = sqrt(normal.x*normal.x + normal.y*normal.y + normal.z*normal.z);
|
||||||
|
if(mag > 0.0f) {
|
||||||
|
normal.x /= mag;
|
||||||
|
normal.y /= mag;
|
||||||
|
normal.z /= mag;
|
||||||
|
}
|
||||||
|
|
||||||
|
return normal;
|
||||||
|
}
|
||||||
|
|
||||||
ChunkMesh::~ChunkMesh(void) {
|
ChunkMesh::~ChunkMesh(void) {
|
||||||
glDeleteVertexArrays(1, &_vao);
|
glDeleteVertexArrays(1, &_vao);
|
||||||
glDeleteBuffers(1, &_vbo);
|
glDeleteBuffers(1, &_vbo);
|
||||||
@ -56,9 +87,13 @@ void ChunkMesh::_setup_mesh(void) {
|
|||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indices.size()*sizeof(unsigned int), _indices.data(), GL_STATIC_DRAW);
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indices.size()*sizeof(unsigned int), _indices.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
/* Position attrib. */
|
/* Position attrib. */
|
||||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void*)0);
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)0);
|
||||||
glEnableVertexAttribArray(0);
|
glEnableVertexAttribArray(0);
|
||||||
|
|
||||||
|
/* Normal attrib. */
|
||||||
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float)));
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "bettola/math/vec3.h"
|
||||||
|
|
||||||
#include "bettola//game/chunk.h"
|
#include "bettola/game/chunk.h"
|
||||||
|
|
||||||
class ChunkMesh {
|
class ChunkMesh {
|
||||||
public:
|
public:
|
||||||
@ -12,6 +13,7 @@ public:
|
|||||||
void draw(void);
|
void draw(void);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
BettolaMath::Vec3 _calculate_normal(int x, int z, const BettolaLib::Game::Chunk& chunk);
|
||||||
void _setup_mesh(void);
|
void _setup_mesh(void);
|
||||||
|
|
||||||
unsigned int _vao, _vbo, _ebo;
|
unsigned int _vao, _vbo, _ebo;
|
||||||
|
|||||||
@ -146,47 +146,30 @@ void Renderer::render(const Camera& camera, const Player& player,
|
|||||||
_shader.use();
|
_shader.use();
|
||||||
GL_CHECK_ERROR();
|
GL_CHECK_ERROR();
|
||||||
|
|
||||||
BettolaMath::Mat4 view_matrix = camera.get_view_matrix();
|
BettolaMath::Mat4 view = camera.get_view_matrix();
|
||||||
BettolaMath::Mat4 projection = BettolaMath::Mat4::perspective((45.0f * M_PI) / 180.0f, 800.0f/600.0f, 0.1f, 100.0f);
|
BettolaMath::Mat4 projection = BettolaMath::Mat4::perspective((45.0f * M_PI) / 180.0f, 800.0f/600.0f, 0.1f, 100.0f);
|
||||||
|
|
||||||
GLint view_loc = glGetUniformLocation(_shader.get_id(), "view");
|
_shader.set_mat4("view", view);
|
||||||
GLint proj_loc = glGetUniformLocation(_shader.get_id(), "projection");
|
_shader.set_mat4("projection", projection);
|
||||||
GLint model_loc = glGetUniformLocation(_shader.get_id(), "model");
|
|
||||||
GLint color_loc = glGetUniformLocation(_shader.get_id(), "overrideColor");
|
|
||||||
|
|
||||||
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view_matrix.get_ptr());
|
|
||||||
GL_CHECK_ERROR();
|
|
||||||
glUniformMatrix4fv(proj_loc, 1, GL_FALSE, projection.get_ptr());
|
|
||||||
GL_CHECK_ERROR();
|
|
||||||
|
|
||||||
/* Draw the ground. */
|
|
||||||
BettolaMath::Mat4 ground_model = BettolaMath::Mat4::translation(0.0f, 0.0f, 0.0f);
|
|
||||||
glUniformMatrix4fv(model_loc, 1, GL_FALSE, ground_model.get_ptr());
|
|
||||||
glUniform3f(color_loc, 0.2f, 0.4f, 0.2f);
|
|
||||||
glBindVertexArray(_ground_vao);
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
|
||||||
glBindVertexArray(0); /* WHOOAAA! Got to make sure we unbind the VAO! */
|
|
||||||
|
|
||||||
/* Set players colour back. */
|
|
||||||
glUniform3f(color_loc, 0.2f, 0.5f, 0.8f);
|
|
||||||
|
|
||||||
/* Render world. */
|
/* Render world. */
|
||||||
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); /* Wireframe to see geometry. */
|
/* 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. */
|
||||||
|
|
||||||
for(auto const& [pos, mesh] : world.get_chunk_meshes()) {
|
for(auto const& [pos, mesh] : world.get_chunk_meshes()) {
|
||||||
BettolaMath::Mat4 model;
|
BettolaMath::Mat4 model;
|
||||||
_shader.set_mat4("model", model);
|
_shader.set_mat4("model", model);
|
||||||
mesh->draw();
|
mesh->draw();
|
||||||
}
|
}
|
||||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); /* Reset to default. */
|
|
||||||
|
|
||||||
/* Draw the local player's cube. */
|
/* Draw the local player's cube. */
|
||||||
|
_shader.set_vec3("objectColor", {0.2f, 0.5f, 0.8f}); /* Player colour. */
|
||||||
const auto& player_pos = player.get_position();
|
const auto& player_pos = player.get_position();
|
||||||
BettolaMath::Mat4 trans_matrix = BettolaMath::Mat4::translation(player_pos.x,
|
BettolaMath::Mat4 trans_matrix = BettolaMath::Mat4::translation(player_pos.x,
|
||||||
player_pos.y, player_pos.z);
|
player_pos.y, player_pos.z);
|
||||||
BettolaMath::Mat4 rot_matrix = BettolaMath::Mat4::rotation(-camera.get_yaw()-90.0f,
|
_shader.set_mat4("model", trans_matrix);
|
||||||
{0.0f,1.0f,0.0f});
|
|
||||||
BettolaMath::Mat4 model = trans_matrix.multiply(rot_matrix);
|
|
||||||
glUniformMatrix4fv(model_loc, 1, GL_FALSE, model.get_ptr());
|
|
||||||
GL_CHECK_ERROR();
|
GL_CHECK_ERROR();
|
||||||
|
|
||||||
glBindVertexArray(_vao);
|
glBindVertexArray(_vao);
|
||||||
@ -202,7 +185,7 @@ void Renderer::render(const Camera& camera, const Player& player,
|
|||||||
BettolaMath::Mat4 remote_rot = BettolaMath::Mat4::rotation(-remote_player.get_yaw()-90.0f,
|
BettolaMath::Mat4 remote_rot = BettolaMath::Mat4::rotation(-remote_player.get_yaw()-90.0f,
|
||||||
{0.0f,1.0f,0.0f});
|
{0.0f,1.0f,0.0f});
|
||||||
BettolaMath::Mat4 remote_model = remote_trans.multiply(remote_rot);
|
BettolaMath::Mat4 remote_model = remote_trans.multiply(remote_rot);
|
||||||
glUniformMatrix4fv(model_loc, 1, GL_FALSE, remote_model.get_ptr());
|
_shader.set_mat4("model", remote_model);
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 36);
|
glDrawArrays(GL_TRIANGLES, 0, 36);
|
||||||
glBindVertexArray(0); /* Unbind it! */
|
glBindVertexArray(0); /* Unbind it! */
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include "bettola/math/vec3.h"
|
||||||
#include "bettola/math/mat4.h"
|
#include "bettola/math/mat4.h"
|
||||||
|
|
||||||
class Shader {
|
class Shader {
|
||||||
@ -12,6 +13,7 @@ public:
|
|||||||
void use(void);
|
void use(void);
|
||||||
|
|
||||||
void set_mat4(const std::string& name, const BettolaMath::Mat4& matrix) const;
|
void set_mat4(const std::string& name, const BettolaMath::Mat4& matrix) const;
|
||||||
|
void set_vec3(const std::string& name, const BettolaMath::Vec3& value) const;
|
||||||
unsigned int get_id(void) const { return _program_id; }
|
unsigned int get_id(void) const { return _program_id; }
|
||||||
private:
|
private:
|
||||||
bool compile_shader(unsigned int& shader_id, const char* shader_source, int shader_type);
|
bool compile_shader(unsigned int& shader_id, const char* shader_source, int shader_type);
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
|
#include "bettola/math/vec3.h"
|
||||||
#include "bettola/math/mat4.h"
|
#include "bettola/math/mat4.h"
|
||||||
#include "shader.h"
|
#include "shader.h"
|
||||||
|
|
||||||
void Shader::set_mat4(const std::string& name, const BettolaMath::Mat4& matrix) const {
|
void Shader::set_mat4(const std::string& name, const BettolaMath::Mat4& matrix) const {
|
||||||
glUniformMatrix4fv(glGetUniformLocation(_program_id, name.c_str()), 1, GL_FALSE, matrix.get_ptr());
|
glUniformMatrix4fv(glGetUniformLocation(_program_id, name.c_str()), 1, GL_FALSE, matrix.get_ptr());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@ -24,8 +24,8 @@ void World::_generate_chunk(BettolaLib::Game::Chunk& chunk, int chunk_x, int chu
|
|||||||
for(int z = 0; z < BettolaLib::Game::CHUNK_HEIGHT; ++z) {
|
for(int z = 0; z < BettolaLib::Game::CHUNK_HEIGHT; ++z) {
|
||||||
for(int x = 0; x < BettolaLib::Game::CHUNK_WIDTH; ++x) {
|
for(int x = 0; x < BettolaLib::Game::CHUNK_WIDTH; ++x) {
|
||||||
/* Calculate world coordinates. */
|
/* Calculate world coordinates. */
|
||||||
float world_x = (float)(chunk_x * BettolaLib::Game::CHUNK_WIDTH + x);
|
float world_x = (float)(chunk_x * (BettolaLib::Game::CHUNK_WIDTH - 1) + x);
|
||||||
float world_z = (float)(chunk_z * BettolaLib::Game::CHUNK_HEIGHT + z);
|
float world_z = (float)(chunk_z * (BettolaLib::Game::CHUNK_HEIGHT - 1) + z);
|
||||||
|
|
||||||
/* generate noise value. */
|
/* generate noise value. */
|
||||||
chunk.heightmap[z * BettolaLib::Game::CHUNK_WIDTH + x] = m_noise.GetNoise(world_x, world_z);
|
chunk.heightmap[z * BettolaLib::Game::CHUNK_WIDTH + x] = m_noise.GetNoise(world_x, world_z);
|
||||||
|
|||||||
@ -8,7 +8,7 @@ float World::get_height(float world_x, float world_z) {
|
|||||||
* Mesh generation uses (WIDTH-1), so account for it
|
* Mesh generation uses (WIDTH-1), so account for it
|
||||||
* when calculating the chunk and local coords.
|
* when calculating the chunk and local coords.
|
||||||
*/
|
*/
|
||||||
float chunk_width = (float)(BettolaLib::Game::CHUNK_WIDTH-1);
|
float chunk_width = (float)(BettolaLib::Game::CHUNK_WIDTH -1);
|
||||||
float chunk_height = (float)(BettolaLib::Game::CHUNK_HEIGHT-1);
|
float chunk_height = (float)(BettolaLib::Game::CHUNK_HEIGHT-1);
|
||||||
|
|
||||||
int chunk_x = floor(world_x / chunk_width);
|
int chunk_x = floor(world_x / chunk_width);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user