diff --git a/res/layouts/pages/settings.xml b/res/layouts/pages/settings.xml index 68a71aa04..cff91d395 100644 --- a/res/layouts/pages/settings.xml +++ b/res/layouts/pages/settings.xml @@ -1,4 +1,4 @@ - + diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua index 3b7f7b13a..806f48d0c 100644 --- a/res/layouts/pages/settings_graphics.xml.lua +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -39,11 +39,12 @@ function on_open() create_setting("chunks.load-distance", "Load Distance", 1) create_setting("chunks.load-speed", "Load Speed", 1) create_setting("graphics.fog-curve", "Fog Curve", 0.1) - + create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip") create_checkbox("graphics.soft-lighting", "Soft lighting", "graphics.soft-lighting.tooltip") create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip") create_checkbox("graphics.advanced-render", "Advanced render", "graphics.advanced-render.tooltip") create_setting("graphics.ssao", "SSAO", 1, "", "graphics.ssao.tooltip") create_setting("graphics.shadows-quality", "Shadows quality", 1) + create_setting("graphics.clouds-quality", "Clouds quality", 1) end diff --git a/res/preload.json b/res/preload.json index 558fb1f0e..00948068e 100644 --- a/res/preload.json +++ b/res/preload.json @@ -8,7 +8,8 @@ "translucent", "background", "skybox_gen", - "shadows" + "shadows", + "clouds" ], "post-effects": [ "default", diff --git a/res/presets/weather/cloudy.json b/res/presets/weather/cloudy.json index 645b0a287..5e11a8ac6 100644 --- a/res/presets/weather/cloudy.json +++ b/res/presets/weather/cloudy.json @@ -1,4 +1,7 @@ { - "clouds": 0.8 + "clouds": 1.0, + "clouds_tint": [0.6, 0.7, 0.8], + "sky_tint": [1.5, 1.5, 1.5], + "min_sky_light": [0.05, 0.07, 0.1] } diff --git a/res/presets/weather/fog.json b/res/presets/weather/fog.json index e9c3a2b34..458283969 100644 --- a/res/presets/weather/fog.json +++ b/res/presets/weather/fog.json @@ -2,5 +2,6 @@ "fog_opacity": 0.98, "fog_dencity": 3.6, "fog_curve": 0.25, - "clouds": 0.8 + "clouds": 0.9, + "min_sky_light": [0.0, 0.0, 0.0] } diff --git a/res/presets/weather/rain.json b/res/presets/weather/rain.json index 184a80f0d..90e0512e9 100644 --- a/res/presets/weather/rain.json +++ b/res/presets/weather/rain.json @@ -19,5 +19,8 @@ "fog_opacity": 0.5, "fog_dencity": 1.7, "fog_curve": 0.5, - "clouds": 0.9 + "clouds": 0.96, + "sky_tint": [2.0, 2.0, 2.0], + "clouds_tint": [0.6, 0.7, 0.8], + "min_sky_light": [0.05, 0.07, 0.1] } diff --git a/res/presets/weather/snow.json b/res/presets/weather/snow.json index da80a8560..18763aa85 100644 --- a/res/presets/weather/snow.json +++ b/res/presets/weather/snow.json @@ -11,5 +11,8 @@ "fog_opacity": 0.8, "fog_dencity": 2.5, "fog_curve": 0.5, - "clouds": 0.5 + "clouds": 1.0, + "sky_tint": [1.9, 1.9, 1.9], + "clouds_tint": [0.7, 0.8, 0.9], + "min_sky_light": [0.05, 0.07, 0.1] } diff --git a/res/presets/weather/thunder.json b/res/presets/weather/thunder.json index d2a107f93..0a6f97c96 100644 --- a/res/presets/weather/thunder.json +++ b/res/presets/weather/thunder.json @@ -19,5 +19,7 @@ "fog_dencity": 2.0, "fog_curve": 0.5, "clouds": 0.7, - "thunder_rate": 0.1 + "thunder_rate": 0.1, + "clouds_tint": [0.3, 0.4, 0.5], + "min_sky_light": [0.0, 0.0, 0.0] } diff --git a/res/shaders/clouds.glslf b/res/shaders/clouds.glslf new file mode 100644 index 000000000..9b29ef110 --- /dev/null +++ b/res/shaders/clouds.glslf @@ -0,0 +1,31 @@ +layout (location = 0) out vec4 f_color; +layout (location = 1) out vec4 f_position; +layout (location = 2) out vec4 f_normal; +layout (location = 3) out vec4 f_emission; + +#include + +in vec4 a_torchLight; + +uniform sampler2D u_texture0; +uniform vec3 u_sunDir; + +// flags +uniform bool u_alphaClip; +uniform bool u_debugLights; +uniform bool u_debugNormals; +uniform vec4 u_tint; + +void main() { + f_color.rgb = max(a_torchLight.rgb, a_skyLight); + f_color.a = 1.0; + f_color *= u_tint; + +#ifndef ADVANCED_RENDER + vec3 fogColor = texture(u_skybox, a_dir).rgb; + f_color = mix(f_color, vec4(fogColor, 1.0), a_fog); +#endif + f_position = vec4(a_position, 1.0); + f_normal = vec4(a_normal, 1.0); + f_emission = vec4(vec3(a_emission), 1.0); +} diff --git a/res/shaders/clouds.glslv b/res/shaders/clouds.glslv new file mode 100644 index 000000000..0b9c17774 --- /dev/null +++ b/res/shaders/clouds.glslv @@ -0,0 +1,49 @@ +#include + +layout (location = 0) in vec3 v_position; +layout (location = 1) in vec2 v_texCoord; +layout (location = 2) in vec4 v_light; +layout (location = 3) in vec4 v_normal; + +#include +#include +#include +#include + +uniform float u_dayTime; + +out vec4 a_torchLight; + +void main() { + a_modelpos = u_model * vec4(v_position, 1.0f); + a_modelpos.x += sin(u_timer * 0.05 + a_modelpos.z * 0.002) * 1.5; + a_modelpos.z += sin(u_timer * 0.2 + a_modelpos.x * 0.002) * 1.5; + a_modelpos.y += sin(u_timer * 0.1 + (a_modelpos.x + a_modelpos.z) * 0.001) * 2.0; + + vec3 pos3d = a_modelpos.xyz - u_cameraPos; + + a_realnormal = v_normal.xyz * 2.0 - 1.0; + a_normal = calc_screen_normal(a_realnormal); + + a_torchLight = vec4(calc_torch_light( + v_light.rgb, a_realnormal, a_modelpos.xyz, u_torchlightColor, u_gamma + ), 1.0); + a_texCoord = v_texCoord; + + a_dir = a_modelpos.xyz - u_cameraPos; + vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight); + a_skyLight = skyLightColor.rgb*v_light.a; + + mat4 viewmodel = u_view * u_model; + a_distance = length(viewmodel * vec4(pos3d, 0.0)); + +#ifndef ADVANCED_RENDER + a_fog = calc_fog(length(viewmodel * vec4(pos3d * FOG_POS_SCALE, 0.0)) / 256.0); +#endif + + a_emission = v_normal.w; + + vec4 viewmodelpos = u_view * a_modelpos; + a_position = viewmodelpos.xyz; + gl_Position = u_proj * viewmodelpos; +} diff --git a/res/shaders/entity.glslv b/res/shaders/entity.glslv index d50cd0075..8f534a6dc 100644 --- a/res/shaders/entity.glslv +++ b/res/shaders/entity.glslv @@ -28,7 +28,7 @@ void main() { a_texCoord = v_texCoord; a_dir = a_modelpos.xyz - u_cameraPos; - vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime); + vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight); a_color.rgb = max(a_color.rgb, skyLightColor.rgb * v_light.a) * v_color; a_color.a = u_opacity; diff --git a/res/shaders/lib/constants.glsl b/res/shaders/lib/constants.glsl index 1c2f4062b..a8a9a4bb3 100644 --- a/res/shaders/lib/constants.glsl +++ b/res/shaders/lib/constants.glsl @@ -10,7 +10,6 @@ // lighting #define SKY_LIGHT_MUL 2.5 #define SKY_LIGHT_TINT (vec3(1.0, 0.75, 0.6) * 2.0) -#define MIN_SKY_LIGHT vec3(0.2, 0.25, 0.33) // fog #define FOG_POS_SCALE vec3(1.0, 0.2, 1.0) diff --git a/res/shaders/lib/sky.glsl b/res/shaders/lib/sky.glsl index 97203286b..cfdbc3f2b 100644 --- a/res/shaders/lib/sky.glsl +++ b/res/shaders/lib/sky.glsl @@ -3,12 +3,12 @@ #include -vec3 pick_sky_color(samplerCube cubemap, float dayTime) { +vec3 pick_sky_color(samplerCube cubemap, float dayTime, vec3 minSkyLight) { float elevation = sin(dayTime * PI2) * 0.2; vec3 skyLightColor = texture(cubemap, vec3(0.0f, elevation, -0.4f)).rgb; skyLightColor *= SKY_LIGHT_TINT; skyLightColor = min(vec3(1.0f), skyLightColor * SKY_LIGHT_MUL); - skyLightColor = max(MIN_SKY_LIGHT, skyLightColor); + skyLightColor = max(minSkyLight, skyLightColor); return skyLightColor; } diff --git a/res/shaders/lib/world_uniforms.glsl b/res/shaders/lib/world_uniforms.glsl index 20c8f529e..96dde0110 100644 --- a/res/shaders/lib/world_uniforms.glsl +++ b/res/shaders/lib/world_uniforms.glsl @@ -11,5 +11,6 @@ uniform float u_timer; uniform samplerCube u_skybox; uniform vec3 u_torchlightColor; uniform float u_torchlightDistance; +uniform vec3 u_minSkyLight; #endif // GLSL_WORLD_UNIFORMS_ diff --git a/res/shaders/main.glslv b/res/shaders/main.glslv index 8cc7a7637..08cc6469e 100644 --- a/res/shaders/main.glslv +++ b/res/shaders/main.glslv @@ -27,7 +27,7 @@ void main() { a_texCoord = v_texCoord; a_dir = a_modelpos.xyz - u_cameraPos; - vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime); + vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight); a_skyLight = skyLightColor.rgb*v_light.a; mat4 viewmodel = u_view * u_model; diff --git a/res/shaders/skybox_gen.glslf b/res/shaders/skybox_gen.glslf index 583104ebb..b6f2b7ffb 100644 --- a/res/shaders/skybox_gen.glslf +++ b/res/shaders/skybox_gen.glslf @@ -241,6 +241,8 @@ uniform vec3 u_xaxis; uniform vec3 u_yaxis; uniform vec3 u_zaxis; uniform vec3 u_lightDir; +uniform vec3 u_tint; +uniform vec3 u_hightlight; uniform int u_quality; uniform float u_mie; uniform float u_fog; @@ -295,5 +297,5 @@ void main() { col = 1.0 - exp(-col); col = min(col, vec3(1.0)); // Output to screen - f_color = vec4(col, 1.0); + f_color = vec4(col * u_tint + u_hightlight, 1.0); } diff --git a/res/shaders/translucent.glslv b/res/shaders/translucent.glslv index 16cc8c4e3..d9d8031c3 100644 --- a/res/shaders/translucent.glslv +++ b/res/shaders/translucent.glslv @@ -27,7 +27,7 @@ void main() { a_texCoord = v_texCoord; a_dir = a_modelpos.xyz - u_cameraPos; - vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime); + vec3 skyLightColor = pick_sky_color(u_skybox, u_dayTime, u_minSkyLight); a_skyLight = skyLightColor.rgb*v_light.a; mat4 viewmodel = u_view * u_model; diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 1461f854c..fd35f10ef 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -110,6 +110,7 @@ settings.Windowed=Оконный settings.Borderless=Безрамочный settings.Microphone=Микрофон settings.microphone.None=Нет +settings.Clouds quality=Качество облаков # Управление chunks.reload=Перезагрузить Чанки diff --git a/src/graphics/core/Shadows.cpp b/src/graphics/core/Shadows.cpp index 19ba7d000..c9ecc108d 100644 --- a/src/graphics/core/Shadows.cpp +++ b/src/graphics/core/Shadows.cpp @@ -130,21 +130,22 @@ void Shadows::refresh( const std::function& renderShadowPass ) { static int frameid = 0; - if (shadows) { - if (frameid % 2 == 0) { - generateShadowsMap( - camera, pctx, *shadowMap, shadowCamera, 1.0f, renderShadowPass - ); - } else { - generateShadowsMap( - camera, - pctx, - *wideShadowMap, - wideShadowCamera, - 3.0f, - renderShadowPass - ); - } + if (!shadows) { + return; + } + if (frameid % 2 == 0) { + generateShadowsMap( + camera, pctx, *shadowMap, shadowCamera, 1.0f, renderShadowPass + ); + } else { + generateShadowsMap( + camera, + pctx, + *wideShadowMap, + wideShadowCamera, + 3.0f, + renderShadowPass + ); } frameid++; } @@ -176,10 +177,7 @@ void Shadows::generateShadowsMap( shadowCamera.setAspectRatio(1.0f); float t = worldInfo.daytime - 0.25f; - if (t < 0.0f) { - t += 1.0f; - } - t = fmod(t, 0.5f); + t = glm::mod(t < 0.0f ? t + 1.0f : t, 0.5f); float sunCycleStep = 1.0f / 500.0f; float sunAngle = glm::radians( @@ -206,15 +204,13 @@ void Shadows::generateShadowsMap( shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f)); - { - auto sctx = pctx.sub(); - sctx.setDepthTest(true); - sctx.setCullFace(true); - sctx.setViewport({resolution, resolution}); - shadowMap.bind(); - if (renderShadowPass) { - renderShadowPass(shadowCamera); - } - shadowMap.unbind(); + auto sctx = pctx.sub(); + sctx.setDepthTest(true); + sctx.setCullFace(true); + sctx.setViewport({resolution, resolution}); + shadowMap.bind(); + if (renderShadowPass) { + renderShadowPass(shadowCamera); } + shadowMap.unbind(); } diff --git a/src/graphics/render/CloudsRenderer.cpp b/src/graphics/render/CloudsRenderer.cpp new file mode 100755 index 000000000..e2047eb63 --- /dev/null +++ b/src/graphics/render/CloudsRenderer.cpp @@ -0,0 +1,337 @@ +#define GLM_ENABLE_EXPERIMENTAL +#include "CloudsRenderer.hpp" + +#include "assets/Assets.hpp" +#include "coders/imageio.hpp" +#include "debug/Logger.hpp" +#include "graphics/core/ImageData.hpp" +#include "graphics/core/Mesh.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/core/Texture.hpp" +#include "io/io.hpp" +#include "lighting/Lightmap.hpp" +#include "maths/FastNoiseLite.h" +#include "maths/FrustumCulling.hpp" +#include "maths/voxmaths.hpp" +#include "window/Camera.hpp" +#include "world/Weather.hpp" + +#include +#include +#include + +static debug::Logger logger("clouds-render"); + +static inline constexpr int MAP_SIZE = 512; +static inline constexpr float CLOUD_VOXEL_SCALE = 8.0f; +static inline constexpr float CLOUDS_SPEED = 4.0f; + +class CloudsMap { +public: + glm::ivec3 size; + const bool* voxels; + + CloudsMap() = default; + + CloudsMap(const glm::ivec3& size, const bool* voxels) + : size(size), voxels(voxels) { + } + + bool isOpen(int x, int y, int z) const { + if (x >= 0 && x < size.x && y >= 0 && y < size.y && z >= 0 && z < size.z) { + return !voxels[vox_index(x, y, z, size.x, size.z)]; + } + return true; + } +}; + + +class VolumeRenderer final { +public: + VolumeRenderer(size_t capacity) + : vertices(std::make_unique(capacity)), + indexBuffer(std::make_unique(capacity * 6)), + capacity(capacity) { + } + + ~VolumeRenderer() = default; + + void build(CloudsMap volumeMap) { + this->map = std::move(volumeMap); + overflow = false; + offset = 0; + indexOffset = 0; + indexCount = 0; + + int end = map.size.x * map.size.y * map.size.z; + for (int i = 0; i < end; i++) { + int x = i % map.size.x; + int y = i / (map.size.z * map.size.x); + int z = (i / map.size.x) % map.size.z; + const auto& vox = map.voxels[i]; + if (!vox) { + continue; + } + cube({x, y, z}); + if (overflow) { + return; + } + } + } + + MeshData createMesh() const { + return MeshData( + util::Buffer(vertices.get(), offset), + std::vector> { + util::Buffer(indexBuffer.get(), indexCount), + }, + util::Buffer( + ChunkVertex::ATTRIBUTES, + sizeof(ChunkVertex::ATTRIBUTES) / sizeof(VertexAttribute) + ) + ); + } + +private: + std::unique_ptr vertices; + std::unique_ptr indexBuffer; + size_t capacity; + uint32_t indexOffset = 0; + uint32_t indexCount = 0; + uint32_t offset = 0; + bool overflow = false; + CloudsMap map {}; + + void vertex( + const glm::vec3& coord, + const glm::vec3& normal + ) { + auto& vert = vertices[offset++]; + vert.position = coord; + vert.uv = {}; + vert.normal = { + static_cast(normal.r * 127 + 128), + static_cast(normal.g * 127 + 128), + static_cast(normal.b * 127 + 128), + 255 + }; + vert.color = { + 0, 0, 0, static_cast((coord.y / 8.0f * 0.25f + 0.75f) * 255) + }; + } + + void face( + const glm::vec3& coord, + const glm::vec3& X, + const glm::vec3& Y, + const glm::vec3& Z + ) { + if (!isOpen(coord + Z)) { + return; + } + if (offset + 4 >= capacity) { + overflow = true; + return; + } + + float s = 0.5f; + vertex(coord + (-X - Y + Z) * s, Z); + vertex(coord + ( X - Y + Z) * s, Z); + vertex(coord + ( X + Y + Z) * s, Z); + vertex(coord + (-X + Y + Z) * s, Z); + + const uint32_t indices[] {0, 1, 2, 0, 2, 3}; + for (size_t i = 0; i < 6; i++) { + indexBuffer[indexCount++] = indexOffset + indices[i]; + } + indexOffset += 4; + } + + bool isOpen(const glm::ivec3& pos) const { + return pos.y < 0 || pos.y >= map.size.y || + map.isOpen(pos.x, pos.y, pos.z); + } + + void cube(const glm::ivec3& coord) { + const glm::ivec3 X(1, 0, 0); + const glm::ivec3 Y(0, 1, 0); + const glm::ivec3 Z(0, 0, 1); + + face(coord, X, Y, Z); + face(coord, -X, Y, -Z); + face(coord, X, -Z, Y); + face(coord, X, Z, -Y); + face(coord, -Z, Y, X); + face(coord, Z, Y, -X); + } +}; + +static void generate_heightmap( + float* heightmap, fnl_state& state, int w, int dd, int layerid +) { + float pi2 = glm::two_pi(); + + for (int lz = 0; lz < dd; lz++) { + for (int lx = 0; lx < w; lx++) { + float x = glm::sin(lx / static_cast(w) * pi2) * w / pi2; + float y = -glm::cos(lx / static_cast(w) * pi2) * w / pi2; + float z = lz; + float s = 1.5f; + auto n = fnlGetNoise3D(&state, x * s * 0.7, y * s, z * s * 0.7); + n += fnlGetNoise3D(&state, x * s + fnlGetNoise2D(&state, x * s * 4 + 2, z * s * 4) * 2.0f, y * s, z * 3.0f) * 0.5f; + n += fnlGetNoise3D(&state, x * s * 2, y * s * 2, z * s * 2) * 0.25f; + n += fnlGetNoise3D(&state, x * s * 4, y * s * 4, z * s * 4) * 0.125f * 2; + n += fnlGetNoise3D(&state, x * s * 8, y * s * 8, z * s * 8) * 0.125f * 0.5f * 2; + n += fnlGetNoise3D(&state, x * s * 16, y * s * 16, z * s * 16) * 0.125f * 0.25f * 3; + n = glm::max(0.0f, n); + n += -0.1f - layerid * 0.3f; + + heightmap[lz * w + lx] = n; + } + } +} + +static void sample_voxels( + bool* voxels, + const float* heightmap, + int height, + int segmentSize, + int segmentX, + int segmentZ +) { + for (int y = 0; y < height; y++) { + for (int z = 0; z < segmentSize; z++) { + for (int x = 0; x < segmentSize; x++) { + int gx = (segmentX * segmentSize) + x; + int gz = (segmentZ * segmentSize) + z; + + float n = heightmap[gz * MAP_SIZE + gx]; + if (gz < MAP_SIZE / 2) { + float t = gz / static_cast(MAP_SIZE / 2); + n = n * t + + heightmap[(MAP_SIZE + gz) * MAP_SIZE + gx] * (1.0f - t); + } + bool solid = y <= n * height && y >= (0.5f - n * 0.5f) * height; + voxels[vox_index(x, y, z, segmentSize, segmentSize)] = solid; + } + } + } +} + +CloudsRenderer::CloudsRenderer() { + VolumeRenderer volumeRenderer(1024 * 512); + + const int diameter = 4; + const int segmentSize = MAP_SIZE / diameter; + + const int w = MAP_SIZE; + const int h = 8; + const int d = MAP_SIZE; + const int dd = d * 1.5; + auto heightmap = std::make_unique(w * dd); + + for (int layerid = 0; layerid < 2; layerid++) { + auto& layer = layers[layerid]; + layer.diameter = diameter; + layer.segmentSize = segmentSize; + + fnl_state state = fnlCreateState(); + state.seed = 5265 + layerid * 3521; + + generate_heightmap(heightmap.get(), state, w, dd, layerid); + + bool voxels[segmentSize * h * segmentSize]; + for (int sz = 0; sz < diameter; sz++) { + for (int sx = 0; sx < diameter; sx++) { + sample_voxels(voxels, heightmap.get(), h, segmentSize, sx, sz); + + CloudsMap map({segmentSize, h, segmentSize}, voxels); + + volumeRenderer.build(map); + layer.meshes.push_back(std::make_unique>( + volumeRenderer.createMesh() + )); + } + } + } +} + +CloudsRenderer::~CloudsRenderer() = default; + +void CloudsRenderer::draw( + Layer& layer, + Frustum& frustum, + Shader& shader, + const Camera& camera, + float timer, + int layerId +) { + float scale = CLOUD_VOXEL_SCALE; + int totalDiameter = layer.segmentSize * scale; + + int gcellX = floordiv(camera.position.x, totalDiameter); + int gcellZ = floordiv(camera.position.z, totalDiameter); + + float speed = CLOUDS_SPEED; + float speedX = glm::sin(layerId * 0.3f + 0.4f) * speed / (layerId + 1); + float speedZ = -glm::cos(layerId * 0.3f + 0.4f) * speed / (layerId + 1); + + int radius = 4; + + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + int lcellX = gcellX - floordiv(glm::floor(timer * speedX), totalDiameter); + int lcellZ = gcellZ - floordiv(glm::floor(timer * speedZ), totalDiameter); + glm::vec3 position( + -128 * scale + (x + gcellX) * layer.segmentSize * scale + + glm::mod(timer * speedX, static_cast(totalDiameter)), + 250 + layerId * 200, + -128 * scale + (z + gcellZ) * layer.segmentSize * scale + + glm::mod(timer * speedZ, static_cast(totalDiameter)) + ); + if (glm::distance2( + glm::vec2( + position.x + totalDiameter * 0.5f, + position.z + totalDiameter * 0.5f + ), + glm::vec2(camera.position.x, camera.position.z) + ) > 4e6) { + continue; + } + if (!frustum.isBoxVisible(position, position + glm::vec3(layer.segmentSize * scale))) { + continue; + } + auto matrix = glm::mat4(1.0f); + matrix = glm::translate(matrix, position); + matrix = glm::scale(matrix, glm::vec3(scale, scale, scale)); + shader.uniformMatrix("u_model", matrix); + + int lx = (x + radius + lcellX) % layer.diameter; + int lz = (z + radius + lcellZ) % layer.diameter; + if (lx < 0) lx += layer.diameter; + if (lz < 0) lz += layer.diameter; + + layer.meshes[lz * layer.diameter + lx]->draw(); + } + } +} + +void CloudsRenderer::draw( + Shader& shader, + const Weather& weather, + float timer, + float fogFactor, + const Camera& camera, + int quality +) { + Frustum frustum; + frustum.update(camera.getProjView()); + + shader.uniform4f("u_tint", glm::vec4(weather.cloudsTint(), 1.0f)); + shader.uniform1f("u_fogFactor", fogFactor * 0.03f); + shader.uniform1f("u_fogCurve", 0.7f - 0.3f); + + for (int i = 0; i < std::min(quality, layers.size()); i++) { + draw(layers[i], frustum, shader, camera, timer, i); + } +} diff --git a/src/graphics/render/CloudsRenderer.hpp b/src/graphics/render/CloudsRenderer.hpp new file mode 100755 index 000000000..cb2ee50bd --- /dev/null +++ b/src/graphics/render/CloudsRenderer.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "commons.hpp" + +#include +#include +#include + +class Camera; +class Shader; +struct Weather; +class Frustum; + +class CloudsRenderer final { +public: + CloudsRenderer(); + ~CloudsRenderer(); + + void draw( + Shader& shader, + const Weather& weather, + float timer, + float fogFactor, + const Camera& camera, + int quality + ); +private: + struct Layer { + int diameter; + int segmentSize; + std::vector>> meshes; + }; + + std::array layers; + + void draw( + Layer& layer, + Frustum& frustum, + Shader& shader, + const Camera& camera, + float timer, + int layerId + ); +}; diff --git a/src/graphics/render/PrecipitationRenderer.cpp b/src/graphics/render/PrecipitationRenderer.cpp index 341d94f88..c566a06d2 100644 --- a/src/graphics/render/PrecipitationRenderer.cpp +++ b/src/graphics/render/PrecipitationRenderer.cpp @@ -151,7 +151,7 @@ void PrecipitationRenderer::render( glm::cross(glm::vec3(0, 1, 0), face.right), FACE_SIZE, light_at(chunks, pos.x, y, pos.z), - glm::vec3(3.0f), + glm::vec3(1.0f), calc_uv(pos, face.right, timer, weather) ); } diff --git a/src/graphics/render/Skybox.cpp b/src/graphics/render/Skybox.cpp index b5c5191f2..c546c62e3 100644 --- a/src/graphics/render/Skybox.cpp +++ b/src/graphics/render/Skybox.cpp @@ -170,7 +170,7 @@ void Skybox::draw( drawStars(angle, opacity); } -void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) { +void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality) { frameid++; float dayTime = t; DrawContext ctx = pctx.sub(); @@ -201,11 +201,13 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) shader.uniform1i("u_quality", quality); shader.uniform1f("u_mie", mie); + shader.uniform3f("u_tint", tint); + shader.uniform3f("u_hightlight", hightlight); shader.uniform1f("u_fog", mie - 1.0f); shader.uniform3f("u_lightDir", lightDir); shader.uniform1f("u_dayTime", dayTime); - if (glm::abs(mie-prevMie) + glm::abs(t-prevT) >= 0.01) { + if (glm::abs(mie-prevMie) + glm::abs(t-prevT) + glm::abs(prevHighlight.r - hightlight.r) >= 0.01) { for (uint face = 0; face < 6; face++) { refreshFace(face, cubemap); } @@ -215,6 +217,7 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, uint quality) } prevMie = mie; prevT = t; + prevHighlight = hightlight; cubemap->unbind(); glActiveTexture(GL_TEXTURE0); diff --git a/src/graphics/render/Skybox.hpp b/src/graphics/render/Skybox.hpp index f2b81876c..d2dde6f5e 100644 --- a/src/graphics/render/Skybox.hpp +++ b/src/graphics/render/Skybox.hpp @@ -49,6 +49,7 @@ class Skybox { float prevMie = -1.0f; float prevT = -1.0f; float sunAltitude = 45.0f; + glm::vec3 prevHighlight {1.0f}; glm::mat4 rotation; void drawStars(float angle, float opacity); @@ -68,7 +69,7 @@ public: float fog ); - void refresh(const DrawContext& pctx, float t, float mie, uint quality); + void refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality); void bind() const; void unbind() const; bool isReady() const { diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 98a065f3c..4157eeb98 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -50,6 +50,7 @@ #include "Skybox.hpp" #include "Emitter.hpp" #include "TextNote.hpp" +#include "CloudsRenderer.hpp" #include #include @@ -116,6 +117,7 @@ WorldRenderer::WorldRenderer( ); shadowMapping = std::make_unique(level); debugLines = std::make_unique(level); + cloudsRenderer = std::make_unique(); } WorldRenderer::~WorldRenderer() = default; @@ -124,6 +126,7 @@ static void setup_weather(Shader& shader, const Weather& weather) { shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity()); shader.uniform1f("u_weatherFogDencity", weather.fogDencity()); shader.uniform1f("u_weatherFogCurve", weather.fogCurve()); + shader.uniform3f("u_minSkyLight", weather.minSkyLight()); } static void setup_camera(Shader& shader, const Camera& camera) { @@ -205,6 +208,7 @@ void WorldRenderer::renderOpaque( particles->render(camera); auto& shader = assets.require("main"); + auto& cloudsShader = assets.require("clouds"); auto& linesShader = assets.require("lines"); setupWorldShader(shader, camera, settings, fogFactor); @@ -212,6 +216,14 @@ void WorldRenderer::renderOpaque( chunksRenderer->drawChunks(camera, shader); blockWraps->draw(ctx, player); + int cloudsQuality = settings.graphics.cloudsQuality.get(); + if (cloudsQuality > 0) { + setupWorldShader(cloudsShader, camera, settings, fogFactor); + cloudsRenderer->draw( + cloudsShader, weather, timer, fogFactor, camera, cloudsQuality + ); + } + if (hudVisible) { renderLines(camera, linesShader, ctx); } @@ -314,13 +326,19 @@ void WorldRenderer::renderFrame( auto& mainShader = assets.require("main"); auto& entityShader = assets.require("entity"); + auto& cloudsShader = assets.require("clouds"); auto& translucentShader = assets.require("translucent"); auto& deferredShader = assets.require("deferred_lighting").getShader(); const auto& settings = engine.getSettings(); Shader* affectedShaders[] { - &mainShader, &entityShader, &translucentShader, &deferredShader, nullptr + &mainShader, + &entityShader, + &cloudsShader, + &translucentShader, + &deferredShader, + nullptr }; refreshSettings(affectedShaders); @@ -331,7 +349,15 @@ void WorldRenderer::renderFrame( clouds = glm::max(worldInfo.fog, clouds); float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f; - skybox->refresh(pctx, worldInfo.daytime, mie, 4); + float random = rand() / static_cast(RAND_MAX); + skybox->refresh( + pctx, + worldInfo.daytime, + mie, + weather.skyTint(), + weather.highlight * random, + 4 + ); chunksRenderer->update(); @@ -369,7 +395,7 @@ void WorldRenderer::renderFrame( if (gbufferPipeline) { postProcessing.bindDepthBuffer(); } else { - postProcessing.getFramebuffer()->bind(); + ctx.setFramebuffer(postProcessing.getFramebuffer()); } // Background sky plane @@ -385,15 +411,14 @@ void WorldRenderer::renderFrame( linesShader.uniformMatrix("u_projview", projView); lineBatch->flush(); + skybox->bind(); // Translucent blocks { auto sctx = ctx.sub(); sctx.setCullFace(true); - skybox->bind(); translucentShader.use(); setupWorldShader(translucentShader, camera, settings, fogFactor); chunksRenderer->drawSortedMeshes(camera, translucentShader); - skybox->unbind(); } // Weather effects @@ -412,6 +437,7 @@ void WorldRenderer::renderFrame( precipitation->render(camera, *weather); } } + skybox->unbind(); glBindFramebuffer(GL_FRAMEBUFFER, 0); } @@ -448,43 +474,45 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { int y = std::floor(player.currentCamera->position.y); int z = std::floor(player.currentCamera->position.z); auto block = player.chunks->get(x, y, z); - if (block && block->id) { - const auto& def = level.content.getIndices()->blocks.require(block->id); - if (def.overlayTexture.empty()) { - return; - } - auto textureRegion = util::get_texture_region( - assets, def.overlayTexture, "blocks:notfound" - ); - DrawContext ctx = wctx.sub(); - ctx.setDepthTest(false); - ctx.setCullFace(false); - - auto& shader = assets.require("ui3d"); - shader.use(); - batch3d->begin(); - shader.uniformMatrix("u_projview", glm::mat4(1.0f)); - shader.uniformMatrix("u_apply", glm::mat4(1.0f)); - auto light = player.chunks->getLight(x, y, z); - float s = Lightmap::extract(light, 3) / 15.0f; - glm::vec4 tint( - glm::min(1.0f, Lightmap::extract(light, 0) / 15.0f + s), - glm::min(1.0f, Lightmap::extract(light, 1) / 15.0f + s), - glm::min(1.0f, Lightmap::extract(light, 2) / 15.0f + s), - 1.0f - ); - batch3d->texture(textureRegion.texture); - batch3d->sprite( - glm::vec3(), - glm::vec3(0, 1, 0), - glm::vec3(1, 0, 0), - 2, - 2, - textureRegion.region, - tint - ); - batch3d->flush(); + + if (block == nullptr || block->id == BLOCK_AIR || block->id == BLOCK_VOID) { + return; } + const auto& def = level.content.getIndices()->blocks.require(block->id); + if (def.overlayTexture.empty()) { + return; + } + auto textureRegion = util::get_texture_region( + assets, def.overlayTexture, "blocks:notfound" + ); + DrawContext ctx = wctx.sub(); + ctx.setDepthTest(false); + ctx.setCullFace(false); + + auto& shader = assets.require("ui3d"); + shader.use(); + batch3d->begin(); + shader.uniformMatrix("u_projview", glm::mat4(1.0f)); + shader.uniformMatrix("u_apply", glm::mat4(1.0f)); + auto light = player.chunks->getLight(x, y, z); + float s = Lightmap::extract(light, 3) / 15.0f; + glm::vec4 tint( + glm::min(1.0f, Lightmap::extract(light, 0) / 15.0f + s), + glm::min(1.0f, Lightmap::extract(light, 1) / 15.0f + s), + glm::min(1.0f, Lightmap::extract(light, 2) / 15.0f + s), + 1.0f + ); + batch3d->texture(textureRegion.texture); + batch3d->sprite( + glm::vec3(), + glm::vec3(0, 1, 0), + glm::vec3(1, 0, 0), + 2, + 2, + textureRegion.region, + tint + ); + batch3d->flush(); } void WorldRenderer::clear() { diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index d3b1b1a95..b9a893d16 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -34,6 +34,7 @@ class Shader; class Shadows; class Skybox; class TextsRenderer; +class CloudsRenderer; struct EngineSettings; struct CompileTimeShaderSettings { @@ -57,6 +58,7 @@ class WorldRenderer { std::unique_ptr shadowMapping; std::unique_ptr debugLines; std::unique_ptr precipitation; + std::unique_ptr cloudsRenderer; Weather weather {}; float timer = 0.0f; diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp index 76285d733..27e08c93f 100644 --- a/src/io/settings_io.cpp +++ b/src/io/settings_io.cpp @@ -82,6 +82,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.add("shadows-quality", &settings.graphics.shadowsQuality); builder.add("dense-render-distance", &settings.graphics.denseRenderDistance); builder.add("soft-lighting", &settings.graphics.softLighting); + builder.add("clouds-quality", &settings.graphics.cloudsQuality); builder.addSection("ui"); builder.add("language", &settings.ui.language); diff --git a/src/presets/WeatherPreset.cpp b/src/presets/WeatherPreset.cpp index 6ae0bae7e..3350e2d70 100644 --- a/src/presets/WeatherPreset.cpp +++ b/src/presets/WeatherPreset.cpp @@ -23,6 +23,9 @@ dv::value WeatherPreset::serialize() const { root["fog_opacity"] = fogOpacity; root["fog_dencity"] = fogDencity; root["fog_curve"] = fogCurve; + root["sky_tint"] = dv::to_value(skyTint); + root["clouds_tint"] = dv::to_value(cloudsTint); + root["min_sky_light"] = dv::to_value(minSkyLight); root["clouds"] = clouds; root["thunder_rate"] = thunderRate; @@ -51,6 +54,9 @@ void WeatherPreset::deserialize(const dv::value& src) { src.at("fog_opacity").get(fogOpacity); src.at("fog_dencity").get(fogDencity); src.at("fog_curve").get(fogCurve); + dv::get_vec(src, "sky_tint", skyTint); + dv::get_vec(src, "clouds_tint", cloudsTint); + dv::get_vec(src, "min_sky_light", minSkyLight); src.at("clouds").get(clouds); src.at("thunder_rate").get(thunderRate); } diff --git a/src/presets/WeatherPreset.hpp b/src/presets/WeatherPreset.hpp index e49d1f3cb..d66ef0f63 100644 --- a/src/presets/WeatherPreset.hpp +++ b/src/presets/WeatherPreset.hpp @@ -60,6 +60,15 @@ struct WeatherPreset : Serializable { /// @brief Weather effects intensity float intensity = 0.0f; + /// @brief Skybox color multiplier + glm::vec3 skyTint {1.0f, 1.0f, 1.0f}; + + /// @brief Clouds color multiplier + glm::vec3 cloudsTint {1.0f, 1.0f, 1.0f}; + + /// @brief Minimal sky-emitted lights (moon light) + glm::vec3 minSkyLight {0.2f, 0.25f, 0.33f}; + dv::value serialize() const override; void deserialize(const dv::value& src) override; }; diff --git a/src/settings.hpp b/src/settings.hpp index c98690023..171db217e 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -91,6 +91,8 @@ struct GraphicsSettings { IntegerSetting denseRenderDistance {56, 0, 10'000}; /// @brief Soft lighting for blocks FlagSetting softLighting {true}; + /// @brief Clouds quality level + IntegerSetting cloudsQuality {2, 0, 2}; }; struct PathfindingSettings { diff --git a/src/voxels/VoxelsVolume.cpp b/src/voxels/VoxelsVolume.cpp new file mode 100644 index 000000000..86ba1a32f --- /dev/null +++ b/src/voxels/VoxelsVolume.cpp @@ -0,0 +1,55 @@ +#include "VoxelsVolume.hpp" + +#include "constants.hpp" +#include "content/Content.hpp" +#include "voxels/Block.hpp" + +#include + +void VoxelsVolume::compressInto(VoxelsVolume& dst, const Content& content) const { + assert(dst.w < w && dst.h < h && dst.d < d); + assert(w % dst.w == 0 && h % dst.h == 0 && d % dst.d == 0); + + int stepW = w / dst.w; + int stepH = h / dst.h; + int stepD = d / dst.d; + int height = dst.h; + int depth = dst.d; + int width = dst.w; + + auto dstVoxels = dst.getVoxels(); + auto dstLights = dst.getLights(); + + const auto& blockDefs = content.getIndices()->blocks; + + for (int y = 0; y < height; y++) { + for (int z = 0; z < depth; z++) { + for (int x = 0; x < width; x++) { + voxel selectedVoxel {BLOCK_AIR, {}}; + light_t light = 0; + for (int ly = 0; ly < stepH; ly++) { + for (int lz = 0; lz < stepD; lz++) { + for (int lx = 0; lx < stepW; lx++) { + size_t srcIndex = vox_index( + x * stepW + lx, y * stepH + ly, z * stepD, w, d + ); + auto vox = voxels[srcIndex]; + if (vox.id == BLOCK_VOID) { + continue; + } + const auto& def = blockDefs.require(vox.id); + if (def.rt.solid) { + selectedVoxel = std::move(vox); + } else if (light == 0) { + light = lights[srcIndex]; + } + } + } + } + size_t dstIndex = vox_index(x, y, z, dst.w, dst.d); + dstLights[dstIndex] = light; + dstVoxels[dstIndex] = std::move(selectedVoxel); + } + } + } +} diff --git a/src/voxels/VoxelsVolume.hpp b/src/voxels/VoxelsVolume.hpp index 14ac0a4f4..471b159c5 100644 --- a/src/voxels/VoxelsVolume.hpp +++ b/src/voxels/VoxelsVolume.hpp @@ -6,15 +6,17 @@ #include +class Content; + class VoxelsVolume { public: - static inline constexpr size_t MAX_VOXELS = CHUNK_VOL * 2; - VoxelsVolume(int w, int h, int d) : VoxelsVolume(0, 0, 0, w, h, d) {} VoxelsVolume(int x, int y, int z, int w, int h, int d) - : x(x), y(y), z(z), w(w), h(h), d(d) { + : x(x), y(y), z(z), w(w), h(h), d(d), + voxels(std::make_unique(w * h * d)), + lights(std::make_unique(w * h * d)) { std::memset(voxels.get(), 0xFF, sizeof(voxel) * w * h * d); } @@ -52,10 +54,18 @@ public: return voxels.get(); } + const voxel* getVoxels() const { + return voxels.get(); + } + light_t* getLights() { return lights.get(); } + const light_t* getLights() const { + return lights.get(); + } + inline blockid_t pickBlockId(int bx, int by, int bz) const { if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h || bz >= z + d) { @@ -80,6 +90,8 @@ public: } return lights[vox_index(bx - x, by - y, bz - z, w, d)]; } + + void compressInto(VoxelsVolume& dst, const Content& content) const; private: int x, y, z; int w, h, d; diff --git a/src/world/Weather.hpp b/src/world/Weather.hpp index 6d8af0bb2..3084bf99e 100644 --- a/src/world/Weather.hpp +++ b/src/world/Weather.hpp @@ -12,6 +12,7 @@ struct Weather : Serializable { std::string nameB; float t = 1.0f; float speed = 0.0f; + glm::vec3 highlight {}; void update(float delta) { t += delta * speed; @@ -51,6 +52,18 @@ struct Weather : Serializable { return b.clouds * sqrtT + a.clouds * (1.0f - sqrtT); } + glm::vec3 skyTint() const { + return b.skyTint * t + a.skyTint * (1.0f - t); + } + + glm::vec3 cloudsTint() const { + return b.cloudsTint * t + a.cloudsTint * (1.0f - t); + } + + glm::vec3 minSkyLight() const { + return b.minSkyLight * t + a.minSkyLight * (1.0f - t); + } + dv::value serialize() const override; void deserialize(const dv::value& src) override; };