Compare commits

5 Commits

Author SHA1 Message Date
662f10047a feat(renderer): add shadow mapping with PCF soft shadows
Introduce shadow mapping using a dedicated depth framebuffer and shader. The block fragment shader now performs percentage-closer filtering (PCF) with Poisson disk sampling and random rotation for soft shadows. The vertex shader outputs light-space coordinates. A new depth shader pair handles rendering from the light's perspective, discarding transparent fragments. The renderer sets up the light projection based on the camera position and sun direction, and applies the shadow factor to diffuse lighting. Day/night cycle can now be toggled off in the world server thread.
2026-06-16 22:27:08 +08:00
943c6f1f46 fix(game_time): use unsigned tick type and enforce positive tick speed 2026-06-16 18:51:47 +08:00
7ede49da72 feat(renderer): make ambient strength adjustable via dev panel 2026-06-16 18:44:59 +08:00
a4f92e3659 feat(world): add day/night cycle with server tick system 2026-06-16 16:17:47 +08:00
f43ef64691 feat(rendering): add basic diffuse and ambient lighting to block rendering 2026-06-16 13:53:47 +08:00
19 changed files with 536 additions and 30 deletions

View File

@@ -1,15 +1,84 @@
#version 460
in vec2 tc;
in vec3 normal;
in vec3 vert_pos;
in vec4 FragPosLightSpace;
flat in int tex_layer;
out vec4 color;
layout (binding = 0) uniform sampler2D shadowMap;
layout (binding = 1) uniform sampler2DArray samp;
uniform float ambientStrength;
uniform vec3 sunlightColor;
uniform vec3 sunlightDir;
const vec2 poissonDisk[8] = vec2[](
vec2( 0.1440, 0.7659), vec2(-0.5761, 0.4479),
vec2(-0.3220, -0.6058), vec2( 0.5693, -0.4048),
vec2(-0.1276, 0.1657), vec2(-0.0649, -0.0165),
vec2( 0.2773, -0.0305), vec2(-0.1134, -0.2122)
);
float random(vec3 seed) {
return fract(sin(dot(seed, vec3(12.9898,78.233,45.5432))) * 43758.5453);
}
float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm, vec3 lightDir)
{
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.x < 0.0 || projCoords.x > 1.0 ||
projCoords.y < 0.0 || projCoords.y > 1.0 ||
projCoords.z < 0.0 || projCoords.z > 1.0) {
return 0.0;
}
float currentDepth = projCoords.z;
vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0));
float bias = 0.0002;
bias += max(0.005 * texelSize.x * (1.0 - dot(norm, lightDir)), 0.0);
float angle = random(gl_FragCoord.xyy) * 6.2831853; // 2*PI
float s = sin(angle), c = cos(angle);
mat2 rot = mat2(c, -s, s, c);
float radius = 1.5;
float shadow = 0.0;
const int samples = 8;
for (int i = 0; i < samples; ++i) {
vec2 offset = rot * poissonDisk[i] * radius * texelSize;
float pcfDepth = texture(shadowMap, projCoords.xy + offset).r;
shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0);
}
shadow /= float(samples);
return shadow;
}
layout (binding = 0) uniform sampler2DArray samp;
void main(void) {
color = texture(samp, vec3(tc, tex_layer));
if (color.a < 0.8) {
vec4 objectColor = texture(samp, vec3(tc, tex_layer));
if (objectColor.a < 0.8) {
discard;
}
vec3 lightDir = normalize(-sunlightDir);
vec3 ambient = ambientStrength * sunlightColor;
vec3 norm = normalize(normal);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * sunlightColor;
float shadow = ShadowCalculation(FragPosLightSpace, norm, lightDir);
color = vec4((ambient + (1.0 - shadow) * (diffuse)) * objectColor.rgb, objectColor.a);
//color = varyingColor;
}

View File

@@ -3,8 +3,12 @@
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in float layer;
layout (location = 3) in vec3 aNormal;
out vec2 tc;
out vec3 normal;
out vec3 vert_pos;
flat out int tex_layer;
out vec4 FragPosLightSpace;
mat4 buildRotateX(float rad);
mat4 buildRotateY(float rad);
@@ -13,13 +17,21 @@ mat4 buildTranslate(float x, float y, float z);
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 norm_matrix;
uniform mat4 lightSpaceMatrix;
void main(void) {
gl_Position = proj_matrix * mv_matrix * vec4(pos, 1.0);
vec4 viewPos = mv_matrix * vec4(pos, 1.0);
vert_pos = pos;
tc = texCoord;
tex_layer = int(layer);
normal = mat3(norm_matrix) * aNormal;
FragPosLightSpace = lightSpaceMatrix * vec4(pos, 1.0);
gl_Position = proj_matrix * viewPos;
}
mat4 buildTranslate(float x, float y, float z) {

View File

@@ -0,0 +1,12 @@
#version 460
in vec2 tc;
flat in int tex_layer;
layout (binding = 1) uniform sampler2DArray samp;
void main() {
vec4 texColor = texture(samp, vec3(tc, tex_layer));
if (texColor.a < 0.8)
discard;
//gl_FragDepth = gl_FragCoord.z;
}

View File

@@ -0,0 +1,12 @@
#version 460
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in float layer;
uniform mat4 lightSpaceMatrix;
out vec2 tc;
flat out int tex_layer;
void main() {
tc = texCoord;
tex_layer = int(layer);
gl_Position = lightSpaceMatrix * vec4(pos, 1.0);
}

View File

@@ -2,8 +2,10 @@
out vec4 frag_color;
uniform vec3 color;
void main(void) {
frag_color = vec4(0.529, 0.808, 0.922, 1.0);
frag_color = vec4(color, 1.0);
}

View File

@@ -32,6 +32,7 @@ public:
const glm::vec3& get_camera_pos() const;
bool is_under_water() const;
glm::vec3 get_camera_front() const;
};
} // namespace Cubed

View File

@@ -23,9 +23,9 @@ constexpr float DEFAULT_MAX_RUN_SPEED = 7.0f;
constexpr float DEFAULT_ACCELERATION = 10.0f;
constexpr float DEFAULT_DECELERATION = 15.0f;
constexpr float DEFAULT_G = 22.5f;
static constexpr int SIZE_X = CHUNK_SIZE;
static constexpr int SIZE_Y = WORLD_SIZE_Y;
static constexpr int SIZE_Z = CHUNK_SIZE;
constexpr int SIZE_X = CHUNK_SIZE;
constexpr int SIZE_Y = WORLD_SIZE_Y;
constexpr int SIZE_Z = CHUNK_SIZE;
constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
{1, 1}, {-1, 1}, {1, -1}, {-1, -1}};

View File

@@ -44,8 +44,11 @@ private:
bool m_need_save_config = false;
bool m_gen_thread_running = true;
int m_theme = 0;
int m_pre_set_day_tick = 0;
int m_pre_set_tick_speed = 1;
void show_about_table_bar();
void show_biome_table_bar();
void show_time_table_bar();
void show_cave_table_bar();
void show_river_table_bar();
void show_settings_tab_item();

View File

@@ -0,0 +1,9 @@
#pragma once
using TickType = unsigned long long;
constexpr int DEFAULT_PER_TICK_TIME = 50;
constexpr TickType DAY_TIME = 24000;
constexpr TickType PER_HOUR = 1000;

View File

@@ -7,7 +7,7 @@
namespace Cubed {
class World;
struct VertexData {
std::vector<Vertex> m_vertices;
std::vector<Vertex3D> m_vertices;
GLuint m_vbo = 0;
GLuint m_vao = 0;
std::atomic<std::size_t> m_sum{0};

View File

@@ -2,6 +2,7 @@
#include "Cubed/AABB.hpp"
#include "Cubed/gameplay/cave_carver.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/river_worm.hpp"
#include <atomic>
@@ -39,12 +40,21 @@ private:
std::unordered_map<ChunkPos, const Chunk*, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using ChunkHashMap = std::unordered_map<ChunkPos, Chunk, ChunkPos::Hash>;
glm::vec3 m_gen_player_pos{0.0f, 0.0f, 0.0f};
ChunkHashMap m_chunks;
std::unordered_map<std::size_t, Player> m_players;
std::vector<glm::vec4> m_planes;
std::thread m_gen_thread;
std::thread m_server_thread;
std::stop_source m_server_stop_source;
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
std::atomic<TickType> m_day_tick = 6000;
mutable std::mutex m_chunks_mutex;
std::mutex m_gen_signal_mutex;
std::mutex m_new_chunk_queue_mutex;
@@ -59,8 +69,12 @@ private:
std::atomic<bool> m_is_rebuilding{false};
std::atomic<bool> m_chunk_gen_finished{false};
std::atomic<bool> m_could_gen{true};
std::atomic<bool> m_day_night_cycle{true};
std::atomic<int> m_rendering_distance{24};
std::atomic<float> m_chunk_gen_fraction{0.0f};
std::atomic<TickType> m_game_ticks{0};
std::vector<ChunkPos> m_dirty_queue;
std::vector<ChunkRenderSnapshot> m_render_snapshots;
std::vector<std::pair<ChunkPos, Chunk>> m_new_chunk;
@@ -119,12 +133,22 @@ public:
int rendering_distance() const;
void rendering_distance(int rendering_distance);
void start_gen_thread();
void start_server_thread();
void stop_gen_thread();
void stop_server_thread();
void serever_run(std::stop_token stoken);
CaveCarver& cave_carcer();
RiverWorm& river_worm();
std::vector<glm::vec4>& planes();
std::vector<ChunkRenderSnapshot>& render_snapshots();
glm::vec3 sunlight_dir() const;
TickType game_tick() const;
TickType day_tick() const;
void day_tick(TickType tick);
int per_tick_time() const;
void per_tick_time(int ms);
};
} // namespace Cubed

View File

@@ -91,6 +91,51 @@ constexpr float TEX_COORDS[6][6][2] = {
{0.0f, 1.0f}, // back left
{0.0f, 0.0f}} // front left
};
constexpr float NORMALS[6][6][3] = {
// ===== front (z = +1) =====
{{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f}},
// ===== right (x = +1) =====
{{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f},
{1.0f, 0.0f, 0.0f}},
// ===== back (z = -1) =====
{{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f},
{0.0f, 0.0f, -1.0f}},
// ===== left (x = -1) =====
{{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f},
{-1.0f, 0.0f, 0.0f}},
// ===== top (y = +1) =====
{{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f}},
// ===== bottom (y = -1) =====
{{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f},
{0.0f, -1.0f, 0.0f}}};
#pragma endregion
constexpr float CUBE_VER[24] = {0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0,
0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0,
@@ -148,6 +193,24 @@ constexpr float CROSS_TEX_COORDS[2][6][2] = {
{1.0f, 1.0f}, // bottom right
{0.0f, 1.0f}}, // bottom left
};
constexpr float CROSS_NORMALS[2][6][3] = {
// ===== Plane 1: upward =====
{{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f}},
// ===== Plane 2: upward =====
{{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 1.0f, 0.0f}}};
#pragma endregion
constexpr float QUAD_VERTICES[] = {
@@ -156,10 +219,11 @@ constexpr float QUAD_VERTICES[] = {
-1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f};
struct Vertex {
struct Vertex3D {
float x = 0.0f, y = 0.0f, z = 0.0f;
float s = 0.0f, t = 0.0f;
float layer = 0.0f;
float nx = 0.0f, ny = 0.0f, nz = 0.0f;
};
struct Vertex2D {

View File

@@ -28,8 +28,21 @@ public:
void update_fov(float fov);
void update_proj_matrix(float aspect, float width, float height);
void updata_framebuffer(int width, int height);
float& ambient_strength();
private:
static constexpr glm::vec3 SUNLIGHT_COLOR{1.0f, 1.0f, 1.0f};
static constexpr glm::vec3 SUN_COLOR{1.00f, 0.95f, 0.80f};
static constexpr glm::vec3 MOON_COLOR{0.75f, 0.80f, 1.00f};
static constexpr glm::vec3 SKY_COLOR{0.529, 0.808, 0.922};
static constexpr float FAR_PLANE = 1000.0f;
static constexpr float NEAR_PLANE = 0.1f;
static constexpr float SUN_SIZE = 50.0f;
static constexpr float MOON_SIZE = 50.0f;
static constexpr float DEPTH_MAP_WIDTH = 2048.0f;
static constexpr float DEPTH_MAP_HEIGHT = 2048.0f;
float m_ambient_strength = 0.1f;
const Camera& m_camera;
DevPanel& m_dev_panel;
const TextureManager& m_texture_manager;
@@ -43,7 +56,7 @@ private:
float m_width = 0.0f;
float m_height = 0.0f;
glm::mat4 m_p_mat, m_v_mat, m_m_mat, m_mv_mat, m_mvp_mat;
glm::mat4 m_p_mat, m_v_mat, m_m_mat, m_mv_mat, m_mvp_mat, m_norm_mat;
GLuint m_mv_loc = 0;
GLuint m_proj_loc = 0;
@@ -62,6 +75,10 @@ private:
GLuint m_accum_texture = 0;
GLuint m_reveal_texture = 0;
GLuint m_oit_depth_render_buffer = 0;
GLuint m_depth_map_fbo = 0;
GLuint m_depth_map_texture = 0;
GLuint m_quad_vbo = 0;
glm::mat4 m_ui_proj;
@@ -74,6 +91,7 @@ private:
2 - outline vao
3 - ui vao
4 - text vao
*/
std::vector<GLuint> m_vao;
std::vector<Vertex2D> m_ui;

View File

@@ -60,4 +60,6 @@ const glm::vec3& Camera::get_camera_pos() const { return m_camera_pos; }
bool Camera::is_under_water() const { return m_under_water; }
glm::vec3 Camera::get_camera_front() const { return m_player->get_front(); }
} // namespace Cubed

View File

@@ -263,6 +263,25 @@ void DevPanel::show_biome_table_bar() {
}
}
void DevPanel::show_time_table_bar() {
World& world = m_app.world();
ImGui::Text("Game Tick %llu", world.game_tick());
ImGui::Text("Day Tick %llu", world.day_tick());
if (ImGui::SliderInt("SetDayTick", &m_pre_set_day_tick, 0, DAY_TIME)) {
}
ImGui::SameLine();
if (ImGui::Button("Set##DayTick")) {
world.day_tick(static_cast<TickType>(m_pre_set_day_tick));
}
ImGui::Text("MSPT %d", world.per_tick_time());
if (ImGui::SliderInt("SetMSPT", &m_pre_set_tick_speed, 1, 200)) {
}
ImGui::SameLine();
if (ImGui::Button("Set##MSPT")) {
world.per_tick_time(m_pre_set_tick_speed);
}
}
void DevPanel::show_cave_table_bar() {
auto& cave_carcer = m_app.world().cave_carcer();
@@ -336,6 +355,10 @@ void DevPanel::show_settings_tab_item() {
static_cast<double>(m_config.mouse_sensitivity));
m_player->hot_reload();
}
if (ImGui::SliderFloat("AmbientStrength",
&m_app.renderer().ambient_strength(), 0.0f,
0.35f))
;
if (ImGui::SliderInt("Distance", &m_config.rendering_distance, 2,
128)) {
Config::get().set("world.rendering_distance",
@@ -457,6 +480,10 @@ void DevPanel::show_world_tab_item() {
ImGui::Text("Chunk Build Progress\n");
ImGui::ProgressBar(m_app.world().chunk_gen_fraction());
if (ImGui::BeginTabBar("World Settings")) {
if (ImGui::BeginTabItem("Time")) {
show_time_table_bar();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Cave")) {
show_cave_table_bar();
ImGui::EndTabItem();

View File

@@ -366,13 +366,18 @@ void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
cur_id);
}
for (int i = 0; i < 6; i++) {
Vertex vex = {
Vertex3D vex = {
VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
TEX_COORDS[face][i][0],
TEX_COORDS[face][i][1],
static_cast<float>(cur_id * 6 + face)
static_cast<float>(cur_id * 6 + face),
NORMALS[face][i][0],
NORMALS[face][i][1],
NORMALS[face][i][2]
};
if (BlockManager::is_transparent(cur_id)) {
@@ -413,13 +418,16 @@ void Chunk::gen_cross_plane_vertices(int world_x, int world_y, int world_z,
}
for (int face = 0; face < 2; face++) {
for (int i = 0; i < 6; i++) {
Vertex vex = {
Vertex3D vex = {
CROSS_VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
CROSS_VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
CROSS_VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
CROSS_TEX_COORDS[face][i][0],
CROSS_TEX_COORDS[face][i][1],
static_cast<float>(BlockManager::cross_plane_index(id))
static_cast<float>(BlockManager::cross_plane_index(id)),
CROSS_NORMALS[face][i][0],
CROSS_NORMALS[face][i][1],
CROSS_NORMALS[face][i][2]
};
m_vertex_data[1].m_vertices.emplace_back(vex);

View File

@@ -41,18 +41,20 @@ void VertexData::upload() {
}
glBindVertexArray(m_vao);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(Vertex),
glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(Vertex3D),
m_vertices.data(), GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, s));
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void*)offsetof(Vertex, layer));
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D), (void*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, s));
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, layer));
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, nx));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);

View File

@@ -7,6 +7,8 @@
#include <execution>
using namespace std::chrono;
namespace Cubed {
struct ChunkRenderData {
@@ -18,6 +20,7 @@ World::World() {}
World::~World() {
stop_gen_thread();
stop_server_thread();
m_chunks.clear();
{
std::lock_guard lk(m_delete_vbo_mutex);
@@ -86,6 +89,8 @@ void World::init_world() {
auto d = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
Logger::info("Chunk Block Init Finish, Time Consuming: {}", d);
start_server_thread();
Logger::info("TestPlayer Create Finish");
}
void World::init_chunks() {
@@ -727,6 +732,11 @@ void World::start_gen_thread() {
});
}
void World::start_server_thread() {
m_server_thread = std::thread(
[this]() { serever_run(m_server_stop_source.get_token()); });
}
void World::stop_gen_thread() {
m_gen_running = false;
m_gen_cv.notify_all();
@@ -736,6 +746,25 @@ void World::stop_gen_thread() {
Logger::info("Gen Thread Stopped");
}
void World::stop_server_thread() {
m_server_stop_source.request_stop();
if (m_server_thread.joinable()) {
m_server_thread.join();
}
}
void World::serever_run(std::stop_token stoken) {
Logger::info("Server Thread Started!");
while (!stoken.stop_requested()) {
std::this_thread::sleep_for(milliseconds(m_per_tick_time));
if (m_day_night_cycle) {
++m_game_ticks;
m_day_tick = (++m_day_tick) % DAY_TIME;
}
}
Logger::info("Server Thread Stopped!");
}
void World::need_gen() {
if (!m_could_gen) {
Logger::warn("It is generating or consuming new chunks");
@@ -1006,4 +1035,47 @@ std::vector<glm::vec4>& World::planes() { return m_planes; }
std::vector<ChunkRenderSnapshot>& World::render_snapshots() {
return m_render_snapshots;
};
/*
glm::vec3 World::sunlight_dir() const {
float t = static_cast<float>(m_day_tick) / DAY_TIME;
float azimuth = glm::radians(90.0f - t * 360.0f);
float altitude =
glm::half_pi<float>() * sin((t - 0.25f) * glm::two_pi<float>());
glm::vec3 dir{cos(altitude) * cos(azimuth), sin(altitude),
cos(altitude) * sin(azimuth)};
return glm::normalize(dir);
}
*/
glm::vec3 World::sunlight_dir() const {
float altitude = sin((m_day_tick - 6 * PER_HOUR) /
static_cast<float>(DAY_TIME / 2) * std::numbers::pi) *
90.0f;
float t = static_cast<float>(m_day_tick) / DAY_TIME;
float azimuth = 90.0f - 360.0f * (t - 0.25f);
float alt = glm::radians(altitude);
float az = glm::radians(azimuth);
glm::vec3 dir;
dir.x = cos(alt) * sin(az);
dir.y = sin(alt);
dir.z = cos(alt) * cos(az);
return glm::normalize(-dir);
}
TickType World::game_tick() const { return m_game_ticks.load(); }
TickType World::day_tick() const { return m_day_tick.load(); }
void World::day_tick(TickType tick) {
tick %= DAY_TIME;
m_day_tick = tick;
}
int World::per_tick_time() const { return m_per_tick_time.load(); }
void World::per_tick_time(int ms) { m_per_tick_time = ms; }
} // namespace Cubed

View File

@@ -42,6 +42,9 @@ Renderer::~Renderer() {
glDeleteTextures(1, &m_accum_texture);
glDeleteTextures(1, &m_reveal_texture);
glDeleteRenderbuffers(1, &m_oit_depth_render_buffer);
glDeleteFramebuffers(1, &m_depth_map_fbo);
glDeleteTextures(1, &m_depth_map_texture);
}
void Renderer::hot_reload() {
@@ -76,6 +79,8 @@ void Renderer::init() {
Shader composite_block_shader{"composite",
"shaders/block_composite_v_shader.glsl",
"shaders/block_composite_f_shader.glsl"};
Shader depth_shader{"depth_shader", "shaders/depth_shader.glsl",
"shaders/depth_fragment_shader.glsl"};
m_shaders.insert({world_shader.hash(), std::move(world_shader)});
m_shaders.insert({outline_shader.hash(), std::move(outline_shader)});
m_shaders.insert({sky_shdaer.hash(), std::move(sky_shdaer)});
@@ -86,6 +91,7 @@ void Renderer::init() {
m_shaders.insert({accum_shader.hash(), std::move(accum_shader)});
m_shaders.insert(
{composite_block_shader.hash(), std::move(composite_block_shader)});
m_shaders.insert({depth_shader.hash(), std::move(depth_shader)});
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
@@ -192,8 +198,8 @@ void Renderer::init_text() {
}
void Renderer::render() {
glDisable(GL_FRAMEBUFFER_SRGB);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@@ -202,12 +208,13 @@ void Renderer::render() {
render_outline();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glEnable(GL_FRAMEBUFFER_SRGB);
glDisable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
render_underwater();
glDisable(GL_FRAMEBUFFER_SRGB);
render_ui();
render_text();
render_dev_panel();
@@ -257,13 +264,44 @@ void Renderer::render_sky() {
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glUniform3fv(shader.loc("color"), 1, glm::value_ptr(SKY_COLOR));
glBindVertexArray(m_vao[1]);
glDisable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);
glEnable(GL_DEPTH_TEST);
// draw sun and moon
glDepthMask(GL_FALSE);
glBindVertexArray(m_vao[0]);
// draw sum
glm::vec3 sun_pos = m_camera.get_camera_pos() +
normalize(-m_world.sunlight_dir()) * (FAR_PLANE * 0.9f);
glm::vec3 sun_view_pos = glm::vec3(m_v_mat * glm::vec4(sun_pos, 1.0f));
m_mv_mat = glm::translate(glm::mat4(1.0f), sun_view_pos) *
glm::scale(glm::mat4(1.0f), glm::vec3(SUN_SIZE)) *
glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, -0.5f, 0.0f));
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glUniform3fv(shader.loc("color"), 1, glm::value_ptr(SUN_COLOR));
glDrawArrays(GL_TRIANGLES, 0, 6);
glm::vec3 moon_pos = m_camera.get_camera_pos() +
normalize(m_world.sunlight_dir()) * (FAR_PLANE * 0.9f);
glm::vec3 moon_view_pos = glm::vec3(m_v_mat * glm::vec4(moon_pos, 1.0f));
m_mv_mat = glm::translate(glm::mat4(1.0f), moon_view_pos) *
glm::scale(glm::mat4(1.0f), glm::vec3(MOON_SIZE)) *
glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, -0.5f, 0.0f));
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glUniform3fv(shader.loc("color"), 1, glm::value_ptr(MOON_COLOR));
glDrawArrays(GL_TRIANGLES, 0, 6);
glDepthMask(GL_TRUE);
}
void Renderer::render_text() {
@@ -343,7 +381,8 @@ void Renderer::update_fov(float fov) {
void Renderer::update_proj_matrix(float aspect, float width, float height) {
m_aspect = aspect;
m_p_mat = glm::perspective(glm::radians(m_fov), aspect, 0.1f, 1000.0f);
m_p_mat =
glm::perspective(glm::radians(m_fov), aspect, NEAR_PLANE, FAR_PLANE);
m_ui_proj = glm::ortho(0.0f, width, height, 0.0f, -1.0f, 1.0f);
// scale and then translate
m_ui_m_matrix =
@@ -423,11 +462,124 @@ void Renderer::updata_framebuffer(int width, int height) {
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// depth map fbo
if (m_depth_map_fbo == 0) {
glGenFramebuffers(1, &m_depth_map_fbo);
}
glDeleteTextures(1, &m_depth_map_texture);
glGenTextures(1, &m_depth_map_texture);
glBindTexture(GL_TEXTURE_2D, m_depth_map_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, DEPTH_MAP_WIDTH,
DEPTH_MAP_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float border_color[] = {1.0f, 1.0f, 1.0f, 1.0f};
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
// Manually compare shadows
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
// GL_COMPARE_REF_TO_TEXTURE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
glBindFramebuffer(GL_FRAMEBUFFER, m_depth_map_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
m_depth_map_texture, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
Logger::error("FBO incomplete after resize!");
} else {
Logger::info("Frame Buffer Complete!");
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
m_width = width;
m_height = height;
}
void Renderer::render_world() {
// shader map
const auto& depth_shader = get_shader("depth_shader");
depth_shader.use();
glm::vec3 cam_pos = m_camera.get_camera_pos();
glm::vec3 cam_fwd = m_camera.get_camera_front();
float half_extent = 256.0f;
glm::vec3 center = cam_pos + cam_fwd * (half_extent * 0.5f);
glm::vec3 sundir = glm::normalize(m_world.sunlight_dir());
glm::vec3 up =
fabs(sundir.y) > 0.99f ? glm::vec3(0, 0, 1) : glm::vec3(0, 1, 0);
glm::mat4 light_basis = glm::lookAt(glm::vec3(0.0f), sundir, up);
float texels_per_unit = DEPTH_MAP_WIDTH / (half_extent * 2.0f);
glm::vec3 ls_center = glm::vec3(light_basis * glm::vec4(center, 1.0f));
ls_center.x = std::round(ls_center.x * texels_per_unit) / texels_per_unit;
ls_center.y = std::round(ls_center.y * texels_per_unit) / texels_per_unit;
glm::vec3 snapped_center =
glm::vec3(glm::inverse(light_basis) * glm::vec4(ls_center, 1.0f));
float distance = 128.0f;
glm::vec3 light_pos = snapped_center - sundir * distance;
glm::mat4 light_view = glm::lookAt(light_pos, snapped_center, up);
glm::mat4 light_projection = glm::ortho(
-half_extent, half_extent, -half_extent, half_extent, 1.0f, 500.0f);
glm::mat4 light_space_matrix = light_projection * light_view;
glUniformMatrix4fv(depth_shader.loc("lightSpaceMatrix"), 1, GL_FALSE,
glm::value_ptr(light_space_matrix));
glViewport(0, 0, DEPTH_MAP_WIDTH, DEPTH_MAP_WIDTH);
glCullFace(GL_FRONT);
glBindFramebuffer(GL_FRAMEBUFFER, m_depth_map_fbo);
glClear(GL_DEPTH_BUFFER_BIT);
auto& m_render_snapshots = m_world.render_snapshots();
auto& camera_pos = m_camera.get_camera_pos();
glActiveTexture(GL_TEXTURE1);
glEnable(GL_DEPTH_TEST);
for (const auto& snapshot : m_render_snapshots) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.normal_vertices_count);
}
// cross_plane and discard
for (const auto& snapshot : m_render_snapshots) {
glm::vec2 camera_pos_xz{camera_pos.x, camera_pos.z};
if (snapshot.cross_vertices_count != 0) {
glm::vec2 center_xz{snapshot.center.x, snapshot.center.z};
float dist2d = glm::distance(camera_pos_xz, center_xz);
if (dist2d <= CROSS_PLANE_DISTANCE * 16) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.cross_vao);
glDrawArrays(GL_TRIANGLES, 0, snapshot.cross_vertices_count);
}
}
if (snapshot.normal_discard_vertices_count != 0) {
glBindTexture(GL_TEXTURE_2D_ARRAY,
m_texture_manager.get_texture_array());
glBindVertexArray(snapshot.normal_discard_vao);
glDrawArrays(GL_TRIANGLES, 0,
snapshot.normal_discard_vertices_count);
}
}
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glCullFace(GL_BACK);
glViewport(0, 0, m_width, m_height);
const auto& normal_block_shader = get_shader("normal_block");
normal_block_shader.use();
@@ -435,17 +587,31 @@ void Renderer::render_world() {
m_proj_loc = normal_block_shader.loc("proj_matrix");
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_depth_map_texture);
glActiveTexture(GL_TEXTURE1);
m_m_mat = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f));
m_v_mat = m_camera.get_camera_lookat();
m_mv_mat = m_v_mat * m_m_mat;
m_norm_mat = glm::transpose(glm::inverse(m_mv_mat));
glm::vec3 light_dir_view =
glm::normalize(glm::mat3(m_v_mat) * m_world.sunlight_dir());
glUniformMatrix4fv(m_mv_loc, 1, GL_FALSE, glm::value_ptr(m_mv_mat));
glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_p_mat));
glUniformMatrix4fv(normal_block_shader.loc("norm_matrix"), 1, GL_FALSE,
glm::value_ptr(m_norm_mat));
glUniformMatrix4fv(normal_block_shader.loc("lightSpaceMatrix"), 1, GL_FALSE,
glm::value_ptr(light_space_matrix));
glUniform1f(normal_block_shader.loc("ambientStrength"), m_ambient_strength);
glUniform3fv(normal_block_shader.loc("sunlightColor"), 1,
glm::value_ptr(SUNLIGHT_COLOR));
glUniform3fv(normal_block_shader.loc("sunlightDir"), 1,
glm::value_ptr(light_dir_view));
m_mvp_mat = m_p_mat * m_mv_mat;
auto& camera_pos = m_camera.get_camera_pos();
auto& m_planes = m_world.planes();
auto& m_render_snapshots = m_world.render_snapshots();
Math::extract_frustum_planes(m_mvp_mat, m_planes);
@@ -526,6 +692,7 @@ void Renderer::render_world() {
glBlendFunci(0, GL_ONE, GL_ONE);
glBlendFunci(1, GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
glActiveTexture(GL_TEXTURE0);
for (const auto& snapshot : m_render_snapshots) {
if (!Math::is_aabb_in_frustum(snapshot.center, snapshot.half_extents,
m_planes)) {
@@ -571,4 +738,6 @@ void Renderer::render_dev_panel() {
glEnable(GL_DEPTH_TEST);
}
float& Renderer::ambient_strength() { return m_ambient_strength; }
} // namespace Cubed