Merge pull request #852 from MihailRis/refactor-world-renderer

update world renderer (part 1)
This commit is contained in:
MihailRis 2026-08-11 19:32:41 +03:00 • committed by GitHub
commit 33053f07ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 737 additions and 593 deletions

View file

@ -1,6 +1,9 @@
#include <shadows> #include <shadows>
#include <fog> #include <fog>
#define CLOUDS_FOG_FACTOR_MUL 0.3f
#define CLOUDS_FOG_CURVE_MUL 0.4f
vec4 effect() { vec4 effect() {
vec4 pos = texture(u_position, v_uv); vec4 pos = texture(u_position, v_uv);
float light = 1.0; float light = 1.0;
@ -32,6 +35,10 @@ vec4 effect() {
light = pow(light, u_gamma); light = pow(light, u_gamma);
vec3 fogColor = texture(u_skybox, dir).rgb; vec3 fogColor = texture(u_skybox, dir).rgb;
float fog = calc_fog(length(u_view * vec4((modelpos.xyz - u_cameraPos) * FOG_POS_SCALE, 0.0)) / 256.0); float fog = calc_fog(
length(u_view * vec4((modelpos.xyz - u_cameraPos) * FOG_POS_SCALE, 0.0)) / 256.0,
mix(1.0, CLOUDS_FOG_FACTOR_MUL, emission),
mix(1.0, CLOUDS_FOG_CURVE_MUL, emission)
);
return vec4(mix(texture(u_screen, v_uv).rgb * mix(1.0, light, 1.0), fogColor, fog), 1.0); return vec4(mix(texture(u_screen, v_uv).rgb * mix(1.0, light, 1.0), fogColor, fog), 1.0);
} }

View file

@ -15,11 +15,33 @@ uniform float u_fogCurve;
uniform bool u_alphaClip; uniform bool u_alphaClip;
uniform vec3 u_sunDir; uniform vec3 u_sunDir;
const float BAYER_MATRIX[64] = float[](
0.0, 32.0, 8.0, 40.0, 2.0, 34.0, 10.0, 42.0,
48.0, 16.0, 56.0, 24.0, 50.0, 18.0, 58.0, 26.0,
12.0, 44.0, 4.0, 36.0, 14.0, 46.0, 6.0, 38.0,
60.0, 28.0, 52.0, 20.0, 62.0, 30.0, 54.0, 22.0,
3.0, 35.0, 11.0, 43.0, 1.0, 33.0, 9.0, 41.0,
51.0, 19.0, 59.0, 27.0, 49.0, 17.0, 57.0, 25.0,
15.0, 47.0, 7.0, 39.0, 13.0, 45.0, 5.0, 37.0,
63.0, 31.0, 55.0, 23.0, 61.0, 29.0, 53.0, 21.0
);
float calc_bayer(vec2 pos){
ivec2 p = ivec2(mod(pos, 8.0));
return BAYER_MATRIX[p.y * 8 + p.x] / 64.0;
}
void main() { void main() {
vec4 texColor = texture(u_texture0, a_texCoord); vec4 texColor = texture(u_texture0, a_texCoord);
float alpha = a_color.a * texColor.a; float alpha = a_color.a * texColor.a;
// anyway it's any alpha-test alternative required // anyway it's any alpha-test alternative required
if (alpha < (u_alphaClip ? 0.5f : 0.15f)) { if (u_alphaClip) {
float bayer = calc_bayer(gl_FragCoord.xy);
if (alpha - bayer < 0.01f) {
discard;
}
alpha = 1.0;
} else if (alpha < 0.05f) {
discard; discard;
} }
f_color = a_color * texColor; f_color = a_color * texColor;

View file

@ -7,13 +7,17 @@ uniform float u_weatherFogOpacity;
uniform float u_weatherFogDencity; uniform float u_weatherFogDencity;
uniform float u_weatherFogCurve; uniform float u_weatherFogCurve;
float calc_fog(float depth) { float calc_fog(float depth, float fogFactor, float fogCurve) {
return min( return min(
1.0, 1.0,
max(pow(depth * u_fogFactor, u_fogCurve), max(pow(depth * u_fogFactor * fogFactor, u_fogCurve * fogCurve),
min(pow(depth * u_weatherFogDencity, u_weatherFogCurve), min(pow(depth * u_weatherFogDencity, u_weatherFogCurve),
u_weatherFogOpacity)) u_weatherFogOpacity))
); );
} }
float calc_fog(float depth) {
return calc_fog(depth, 1.0, 1.0);
}
#endif // FOG_GLSL_ #endif // FOG_GLSL_

View file

@ -48,7 +48,7 @@ void ServerMainloop::run() {
delta = time.getDelta(); delta = time.getDelta();
} }
if (controller) { if (controller) {
controller->getLevel()->getWorld()->updateTimers(delta); controller->getLevel()->getWorld().updateTimers(delta);
controller->update(glm::min(delta, 0.2), false); controller->update(glm::min(delta, 0.2), false);
} }
engine.applicationTick(); engine.applicationTick();

View file

@ -147,3 +147,7 @@ const ContentGfxCache& LevelFrontend::getContentGfxCache() const {
LevelController& LevelFrontend::getController() const { LevelController& LevelFrontend::getController() const {
return controller; return controller;
} }
Weather& LevelFrontend::getWeather() {
return weather;
}

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "world/Weather.hpp"
#include <memory> #include <memory>
class Assets; class Assets;
@ -15,6 +17,7 @@ class LevelFrontend {
LevelController& controller; LevelController& controller;
Assets& assets; Assets& assets;
std::unique_ptr<ContentGfxCache> contentCache; std::unique_ptr<ContentGfxCache> contentCache;
Weather weather {};
public: public:
LevelFrontend( LevelFrontend(
Engine& engine, Engine& engine,
@ -28,4 +31,5 @@ public:
const ContentGfxCache& getContentGfxCache() const; const ContentGfxCache& getContentGfxCache() const;
ContentGfxCache& getContentGfxCache(); ContentGfxCache& getContentGfxCache();
LevelController& getController() const; LevelController& getController() const;
Weather& getWeather();
}; };

View file

@ -254,7 +254,7 @@ std::shared_ptr<UINode> create_debug_panel(
} }
})); }));
panel->add(create_label(gui, [&]() { panel->add(create_label(gui, [&]() {
return L"seed: " + std::to_wstring(level.getWorld()->getSeed()); return L"seed: " + std::to_wstring(level.getWorld().getSeed());
})); }));
for (int ax = 0; ax < 3; ax++) { for (int ax = 0; ax < 3; ax++) {
@ -294,7 +294,7 @@ std::shared_ptr<UINode> create_debug_panel(
sub->add(box, glm::vec2(20, 0)); sub->add(box, glm::vec2(20, 0));
panel->add(sub); panel->add(sub);
} }
auto& worldInfo = level.getWorld()->getInfo(); auto& worldInfo = level.getWorld().getInfo();
panel->add(create_label(gui, [&]() { panel->add(create_label(gui, [&]() {
int hour, minute, second; int hour, minute, second;
timeutil::from_value(worldInfo.daytime, hour, minute, second); timeutil::from_value(worldInfo.daytime, hour, minute, second);
@ -310,12 +310,6 @@ std::shared_ptr<UINode> create_debug_panel(
bar->setConsumer([&](double val) { worldInfo.daytime = val; }); bar->setConsumer([&](double val) { worldInfo.daytime = val; });
panel->add(bar); panel->add(bar);
} }
if (allowDebugCheats) {
auto bar = std::make_shared<TrackBar>(gui, 0.0f, 1.0f, 0.0f, 0.005f, 8);
bar->setSupplier([&]() { return worldInfo.fog; });
bar->setConsumer([&](double val) { worldInfo.fog = val; });
panel->add(bar);
}
{ {
auto checkbox = std::make_shared<FullCheckBox>( auto checkbox = std::make_shared<FullCheckBox>(
gui, L"Show Chunk Borders", glm::vec2(400, 24) gui, L"Show Chunk Borders", glm::vec2(400, 24)

View file

@ -43,7 +43,7 @@ LevelScreen::LevelScreen(
Engine& engine, std::unique_ptr<Level> levelPtr, int64_t localPlayer Engine& engine, std::unique_ptr<Level> levelPtr, int64_t localPlayer
) )
: Screen(engine), : Screen(engine),
world(*levelPtr->getWorld()), world(levelPtr->getWorld()),
postProcessing(std::make_unique<PostProcessing>( postProcessing(std::make_unique<PostProcessing>(
levelPtr->content.getIndices(ResourceType::POST_EFFECT_SLOT).size() levelPtr->content.getIndices(ResourceType::POST_EFFECT_SLOT).size()
)), )),
@ -81,7 +81,7 @@ LevelScreen::LevelScreen(
auto resetChunks = [=](bool) { auto resetChunks = [=](bool) {
player->chunks->saveAndClear(); player->chunks->saveAndClear();
renderer->clear(); renderer->resetCache();
}; };
keepAlive(settings.graphics.backlight.observe(resetChunks)); keepAlive(settings.graphics.backlight.observe(resetChunks));
keepAlive(settings.graphics.softLighting.observe(resetChunks)); keepAlive(settings.graphics.softLighting.observe(resetChunks));
@ -94,11 +94,11 @@ LevelScreen::LevelScreen(
})); }));
keepAlive(input.addCallback(BIND_CHUNKS_RELOAD, [=]() { keepAlive(input.addCallback(BIND_CHUNKS_RELOAD, [=]() {
player->chunks->saveAndClear(); player->chunks->saveAndClear();
renderer->clear(); renderer->resetCache();
return false; return false;
})); }));
controller->preQuitCallbacks.listen([this]() { controller->preQuitCallbacks.listen([this]() {
if (!controller->getLevel()->getWorld()->isNameless()) { if (!controller->getLevel()->getWorld().isNameless()) {
saveWorldPreview(); saveWorldPreview();
} }
}); });
@ -109,7 +109,7 @@ LevelScreen::LevelScreen(
} }
LevelScreen::~LevelScreen() { LevelScreen::~LevelScreen() {
if (!controller->getLevel()->getWorld()->isNameless()) { if (!controller->getLevel()->getWorld().isNameless()) {
saveDecorations(); saveDecorations();
} }
scripting::on_frontend_close(); scripting::on_frontend_close();
@ -153,7 +153,7 @@ void LevelScreen::loadDecorations() {
} }
auto data = io::read_object(CLIENT_FILE); auto data = io::read_object(CLIENT_FILE);
if (data.has("weather")) { if (data.has("weather")) {
renderer->getWeather().deserialize(data["weather"]); frontend->getWeather().deserialize(data["weather"]);
} }
} }
@ -161,7 +161,7 @@ void LevelScreen::saveDecorations() {
io::create_directory("world:client"); io::create_directory("world:client");
auto data = dv::object(); auto data = dv::object();
data["weather"] = renderer->getWeather().serialize(); data["weather"] = frontend->getWeather().serialize();
io::write_json(CLIENT_FILE, data, true); io::write_json(CLIENT_FILE, data, true);
} }
@ -257,7 +257,7 @@ void LevelScreen::update(float delta) {
hud->update(hudVisible); hud->update(hudVisible);
const Weather& weather = renderer->getWeather(); const Weather& weather = frontend->getWeather();
const Player& player = playerController->getPlayer(); const Player& player = playerController->getPlayer();
const Camera& camera = *player.currentCamera; const Camera& camera = *player.currentCamera;
decorator->update(paused ? 0.0f : delta, camera, weather); decorator->update(paused ? 0.0f : delta, camera, weather);

View file

@ -372,7 +372,7 @@ void Batch2D::sprite(
void Batch2D::flush() { void Batch2D::flush() {
if (index == 0) if (index == 0)
return; return;
mesh->reload(buffer.get(), index); mesh->reload(buffer.get(), index, true);
mesh->draw(gl::to_glenum(primitive)); mesh->draw(gl::to_glenum(primitive));
index = 0; index = 0;
} }

View file

@ -292,13 +292,13 @@ void Batch3D::point(const glm::vec3& coord, const glm::vec4& tint) {
} }
void Batch3D::flush() { void Batch3D::flush() {
mesh->reload(buffer.get(), index); mesh->reload(buffer.get(), index, true);
mesh->draw(); mesh->draw();
index = 0; index = 0;
} }
void Batch3D::flushPoints() { void Batch3D::flushPoints() {
mesh->reload(buffer.get(), index); mesh->reload(buffer.get(), index, true);
mesh->draw(GL_POINTS); mesh->draw(GL_POINTS);
index = 0; index = 0;
} }

View file

@ -57,7 +57,7 @@ void LineBatch::box(float x, float y, float z, float w, float h, float d,
void LineBatch::flush(){ void LineBatch::flush(){
if (index == 0) if (index == 0)
return; return;
mesh->reload(buffer.get(), index); mesh->reload(buffer.get(), index, true);
mesh->draw(GL_LINES); mesh->draw(GL_LINES);
index = 0; index = 0;
} }

View file

@ -47,12 +47,15 @@ public:
void reload( void reload(
const VertexStructure* vertexBuffer, const VertexStructure* vertexBuffer,
size_t vertexCount, size_t vertexCount,
const std::vector<IndexBufferData>& indices const std::vector<IndexBufferData>& indices,
bool streaming
); );
void reload(const VertexStructure* vertexBuffer, size_t vertexCount) { void reload(
const VertexStructure* vertexBuffer, size_t vertexCount, bool streaming
) {
static const std::vector<IndexBufferData> indices {}; static const std::vector<IndexBufferData> indices {};
reload(vertexBuffer, vertexCount, indices); reload(vertexBuffer, vertexCount, indices, streaming);
} }
/// @brief Draw mesh with specified primitives type /// @brief Draw mesh with specified primitives type
@ -61,6 +64,8 @@ public:
/// @brief Draw mesh as triangles /// @brief Draw mesh as triangles
void draw() const; void draw() const;
private:
}; };
#include "graphics/core/Mesh.inl" #include "graphics/core/Mesh.inl"

View file

@ -46,9 +46,9 @@ Mesh<VertexStructure>::Mesh(
glGenVertexArrays(1, &vao); glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo); glGenBuffers(1, &vbo);
reload(vertexBuffer, vertices, std::move(indices));
glBindVertexArray(vao); glBindVertexArray(vao);
reload(vertexBuffer, vertices, std::move(indices), false);
// attributes // attributes
int offset = 0; int offset = 0;
for (int i = 0; attrs[i].count; i++) { for (int i = 0; attrs[i].count; i++) {
@ -82,20 +82,22 @@ template <typename VertexStructure>
void Mesh<VertexStructure>::reload( void Mesh<VertexStructure>::reload(
const VertexStructure* vertexBuffer, const VertexStructure* vertexBuffer,
size_t vertexCount, size_t vertexCount,
const std::vector<IndexBufferData>& indices const std::vector<IndexBufferData>& indices,
bool streaming
) { ) {
this->vertexCount = vertexCount; this->vertexCount = vertexCount;
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo);
if (vertexBuffer != nullptr && vertexCount != 0) { if (vertexBuffer != nullptr && vertexCount != 0) {
glBufferData( glBufferData(
GL_ARRAY_BUFFER, GL_ARRAY_BUFFER,
vertexCount * sizeof(VertexStructure), vertexCount * sizeof(VertexStructure),
vertexBuffer, vertexBuffer,
GL_STREAM_DRAW streaming ? GL_STREAM_DRAW : GL_STATIC_DRAW
); );
} else { } else {
glBufferData(GL_ARRAY_BUFFER, 0, {}, GL_STREAM_DRAW); glBufferData(
GL_ARRAY_BUFFER, 0, {}, streaming ? GL_STREAM_DRAW : GL_STATIC_DRAW
);
} }
for (int i = 0; i < ibos.size(); i++) { for (int i = 0; i < ibos.size(); i++) {
@ -116,7 +118,6 @@ void Mesh<VertexStructure>::reload(
GL_STATIC_DRAW GL_STATIC_DRAW
); );
} }
glBindVertexArray(0);
} }
template <typename VertexStructure> template <typename VertexStructure>

View file

@ -186,7 +186,8 @@ void Shader::recompile(const std::vector<std::string>& defines) {
glDeleteProgram(id); glDeleteProgram(id);
id = newProgram; id = newProgram;
uniformLocations.clear(); uniformLocations.clear();
logger.info() << "shader " << id << " has been recompiled"; logger.debug() << "shader program (" << vertexSource.file << ", "
<< fragmentSource.file << ") has been recompiled";
} }
std::unique_ptr<Shader> Shader::create( std::unique_ptr<Shader> Shader::create(

View file

@ -99,8 +99,8 @@ void Shadows::setup(Shader& shader, const Weather& weather) {
if (!shadows) { if (!shadows) {
return; return;
} }
const auto& worldInfo = level.getWorld()->getInfo(); const auto& worldInfo = level.getWorld().getInfo();
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds()); float cloudsIntensity = weather.clouds();
float shadowsOpacity = 1.0f - cloudsIntensity; float shadowsOpacity = 1.0f - cloudsIntensity;
shadowsOpacity *= glm::sqrt(glm::abs( shadowsOpacity *= glm::sqrt(glm::abs(
glm::mod((worldInfo.daytime + 0.5f) * 2.0f, 1.0f) * 2.0f - 1.0f glm::mod((worldInfo.daytime + 0.5f) * 2.0f, 1.0f) * 2.0f - 1.0f
@ -158,8 +158,8 @@ void Shadows::generateShadowsMap(
float scale, float scale,
const std::function<void(Camera&)>& renderShadowPass const std::function<void(Camera&)>& renderShadowPass
) { ) {
auto world = level.getWorld(); const auto& world = level.getWorld();
const auto& worldInfo = world->getInfo(); const auto& worldInfo = world.getInfo();
int resolution = shadowMap.getResolution(); int resolution = shadowMap.getResolution();
float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale; float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale;

View file

@ -10,15 +10,14 @@
#include "frontend/ContentGfxCache.hpp" #include "frontend/ContentGfxCache.hpp"
const glm::vec3 BlocksRenderer::SUN_VECTOR(0.528265, 0.833149, -0.163704); const glm::vec3 BlocksRenderer::SUN_VECTOR(0.528265, 0.833149, -0.163704);
const float DIRECTIONAL_LIGHT_FACTOR = 0.3f; constexpr float DIRECTIONAL_LIGHT_FACTOR = 0.3f;
BlocksRenderer::BlocksRenderer( BlocksRenderer::BlocksRenderer(
size_t capacity, size_t capacity,
const Content& content, const Block* const* blockDefs,
const ContentGfxCache& cache, const ContentGfxCache& cache,
const EngineSettings& settings const EngineSettings& settings
) : content(content), ) : vertexBuffer(std::make_unique<ChunkVertex[]>(capacity)),
vertexBuffer(std::make_unique<ChunkVertex[]>(capacity)),
indexBuffer(std::make_unique<uint32_t[]>(capacity)), indexBuffer(std::make_unique<uint32_t[]>(capacity)),
denseIndexBuffer(std::make_unique<uint32_t[]>(capacity)), denseIndexBuffer(std::make_unique<uint32_t[]>(capacity)),
vertexCount(0), vertexCount(0),
@ -28,7 +27,7 @@ BlocksRenderer::BlocksRenderer(
cache(cache), cache(cache),
settings(settings) settings(settings)
{ {
blockDefsCache = content.getIndices()->blocks.getDefs(); blockDefsCache = blockDefs;
} }
BlocksRenderer::~BlocksRenderer() = default; BlocksRenderer::~BlocksRenderer() = default;
@ -482,83 +481,78 @@ glm::vec4 BlocksRenderer::pickSoftLight(
} }
void BlocksRenderer::render( void BlocksRenderer::render(
const voxel* voxels, const int beginEnds[256][2] const voxel* voxels, int totalBegin, int totalEnd
) { ) {
bool denseRender = this->denseRender; bool denseRender = this->denseRender;
bool densePass = this->densePass; bool densePass = this->densePass;
bool enableAO = settings.graphics.softLighting.get(); bool enableAO = settings.graphics.softLighting.get();
for (const auto drawGroup : *content.drawGroups) {
int begin = beginEnds[drawGroup][0]; for (int i = totalBegin; i < totalEnd; i++) {
if (begin == 0) { const voxel& vox = voxels[i];
blockid_t id = vox.id;
blockstate state = vox.state;
const auto& def = *blockDefsCache[id];
uint8_t variantId = def.getVariantIndex(state.userbits);
const auto& variant = def.getVariant(variantId);
if (id == 0 || state.segment) {
continue; continue;
} }
int end = beginEnds[drawGroup][1]; if (denseRender != (variant.culling == CullingMode::OPTIONAL)) {
for (int i = begin-1; i <= end; i++) { continue;
const voxel& vox = voxels[i]; }
blockid_t id = vox.id; if (def.translucent) {
blockstate state = vox.state; continue;
const auto& def = *blockDefsCache[id]; }
uint8_t variantId = def.getVariantIndex(state.userbits); const UVRegion texfaces[6] {
const auto& variant = def.getVariant(variantId); cache.getRegion(id, variantId, 0, densePass),
if (id == 0 || variant.drawGroup != drawGroup || state.segment) { cache.getRegion(id, variantId, 1, densePass),
continue; cache.getRegion(id, variantId, 2, densePass),
cache.getRegion(id, variantId, 3, densePass),
cache.getRegion(id, variantId, 4, densePass),
cache.getRegion(id, variantId, 5, densePass)
};
int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W);
int z = (i / CHUNK_D) % CHUNK_W;
switch (def.getModel(state.userbits).type) {
case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion && enableAO);
break;
case BlockModelType::XSPRITE: {
if (!denseRender)
blockXSprite(x, y, z, glm::vec3(1.0f),
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
break;
} }
if (denseRender != (variant.culling == CullingMode::OPTIONAL)) { case BlockModelType::AABB: {
continue; if (!denseRender)
blockAABB({x, y, z}, texfaces, &def, vox.state.rotation,
!def.shadeless, def.ambientOcclusion && enableAO);
break;
} }
if (def.translucent) { case BlockModelType::CUSTOM: {
continue; blockCustomModel(
} {x, y, z},
const UVRegion texfaces[6] { def,
cache.getRegion(id, variantId, 0, densePass), vox.state,
cache.getRegion(id, variantId, 1, densePass), !def.shadeless,
cache.getRegion(id, variantId, 2, densePass), def.ambientOcclusion && enableAO
cache.getRegion(id, variantId, 3, densePass), );
cache.getRegion(id, variantId, 4, densePass), break;
cache.getRegion(id, variantId, 5, densePass)
};
int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W);
int z = (i / CHUNK_D) % CHUNK_W;
switch (def.getModel(state.userbits).type) {
case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion && enableAO);
break;
case BlockModelType::XSPRITE: {
if (!denseRender)
blockXSprite(x, y, z, glm::vec3(1.0f),
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
break;
}
case BlockModelType::AABB: {
if (!denseRender)
blockAABB({x, y, z}, texfaces, &def, vox.state.rotation,
!def.shadeless, def.ambientOcclusion && enableAO);
break;
}
case BlockModelType::CUSTOM: {
blockCustomModel(
{x, y, z},
def,
vox.state,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
default:
break;
}
if (overflow) {
return;
} }
default:
break;
}
if (overflow) {
return;
} }
} }
} }
SortingMeshData BlocksRenderer::renderTranslucent( SortingMeshData BlocksRenderer::renderTranslucent(
const voxel* voxels, int beginEnds[256][2] const voxel* voxels, int totalBegin, int totalEnd
) { ) {
SortingMeshData sortingMesh {{}}; SortingMeshData sortingMesh {{}};
@ -568,106 +562,98 @@ SortingMeshData BlocksRenderer::renderTranslucent(
bool densePass = this->densePass; bool densePass = this->densePass;
bool enableAO = settings.graphics.softLighting.get(); bool enableAO = settings.graphics.softLighting.get();
for (const auto drawGroup : *content.drawGroups) { for (int i = totalBegin; i < totalEnd; i++) {
int begin = beginEnds[drawGroup][0]; const voxel& vox = voxels[i];
if (begin == 0) { blockid_t id = vox.id;
blockstate state = vox.state;
const auto& def = *blockDefsCache[id];
uint8_t variantId = def.getVariantIndex(state.userbits);
if (id == 0 || state.segment) {
continue; continue;
} }
int end = beginEnds[drawGroup][1]; if (!def.translucent) {
for (int i = begin-1; i <= end; i++) { continue;
const voxel& vox = voxels[i];
blockid_t id = vox.id;
blockstate state = vox.state;
const auto& def = *blockDefsCache[id];
uint8_t variantId = def.getVariantIndex(state.userbits);
const auto& variant = def.getVariant(variantId);
if (id == 0 || variant.drawGroup != drawGroup || state.segment) {
continue;
}
if (!def.translucent) {
continue;
}
const UVRegion texfaces[6] {
cache.getRegion(id, variantId, 0, densePass),
cache.getRegion(id, variantId, 1, densePass),
cache.getRegion(id, variantId, 2, densePass),
cache.getRegion(id, variantId, 3, densePass),
cache.getRegion(id, variantId, 4, densePass),
cache.getRegion(id, variantId, 5, densePass)
};
int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W);
int z = (i / CHUNK_D) % CHUNK_W;
switch (def.getModel(state.userbits).type) {
case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion && enableAO);
break;
case BlockModelType::XSPRITE: {
blockXSprite(x, y, z, glm::vec3(1.0f),
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
break;
}
case BlockModelType::AABB: {
blockAABB(
{x, y, z},
texfaces,
&def,
vox.state.rotation,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
case BlockModelType::CUSTOM: {
blockCustomModel(
{x, y, z},
def,
vox.state,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
default:
break;
}
if (vertexCount == 0) {
continue;
}
SortingMeshEntry entry {
glm::vec3(
x + chunk->x * CHUNK_W + 0.5f,
y + 0.5f,
z + chunk->z * CHUNK_D + 0.5f
),
util::Buffer<ChunkVertex>(indexCount), 0};
totalSize += entry.vertexData.size();
for (int j = 0; j < indexCount; j++) {
std::memcpy(
entry.vertexData.data() + j,
vertexBuffer.get() + indexBuffer[j],
sizeof(ChunkVertex)
);
ChunkVertex& vertex = entry.vertexData[j];
if (!aabbInit) {
aabbInit = true;
aabb.a = aabb.b = vertex.position;
} else {
aabb.addPoint(vertex.position);
}
vertex.position.x += chunk->x * CHUNK_W + 0.5f;
vertex.position.y += 0.5f;
vertex.position.z += chunk->z * CHUNK_D + 0.5f;
}
sortingMesh.entries.push_back(std::move(entry));
vertexCount = 0;
vertexOffset = indexCount = 0;
} }
const UVRegion texfaces[6] {
cache.getRegion(id, variantId, 0, densePass),
cache.getRegion(id, variantId, 1, densePass),
cache.getRegion(id, variantId, 2, densePass),
cache.getRegion(id, variantId, 3, densePass),
cache.getRegion(id, variantId, 4, densePass),
cache.getRegion(id, variantId, 5, densePass)
};
int x = i % CHUNK_W;
int y = i / (CHUNK_D * CHUNK_W);
int z = (i / CHUNK_D) % CHUNK_W;
switch (def.getModel(state.userbits).type) {
case BlockModelType::BLOCK:
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
def.ambientOcclusion && enableAO);
break;
case BlockModelType::XSPRITE: {
blockXSprite(x, y, z, glm::vec3(1.0f),
texfaces[FACE_MX], texfaces[FACE_MZ], 1.0f);
break;
}
case BlockModelType::AABB: {
blockAABB(
{x, y, z},
texfaces,
&def,
vox.state.rotation,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
case BlockModelType::CUSTOM: {
blockCustomModel(
{x, y, z},
def,
vox.state,
!def.shadeless,
def.ambientOcclusion && enableAO
);
break;
}
default:
break;
}
if (vertexCount == 0) {
continue;
}
SortingMeshEntry entry {
glm::vec3(
x + chunk->x * CHUNK_W + 0.5f,
y + 0.5f,
z + chunk->z * CHUNK_D + 0.5f
),
util::Buffer<ChunkVertex>(indexCount), 0};
totalSize += entry.vertexData.size();
for (int j = 0; j < indexCount; j++) {
std::memcpy(
entry.vertexData.data() + j,
vertexBuffer.get() + indexBuffer[j],
sizeof(ChunkVertex)
);
ChunkVertex& vertex = entry.vertexData[j];
if (!aabbInit) {
aabbInit = true;
aabb.a = aabb.b = vertex.position;
} else {
aabb.addPoint(vertex.position);
}
vertex.position.x += chunk->x * CHUNK_W + 0.5f;
vertex.position.y += 0.5f;
vertex.position.z += chunk->z * CHUNK_D + 0.5f;
}
sortingMesh.entries.push_back(std::move(entry));
vertexCount = 0;
vertexOffset = indexCount = 0;
} }
// additional powerful optimization // additional powerful optimization
@ -709,19 +695,16 @@ void BlocksRenderer::build(
int totalBegin = chunk->bottom * (CHUNK_W * CHUNK_D); int totalBegin = chunk->bottom * (CHUNK_W * CHUNK_D);
int totalEnd = chunk->top * (CHUNK_W * CHUNK_D); int totalEnd = chunk->top * (CHUNK_W * CHUNK_D);
bool hasTranslucent = false; bool hasTranslucent = false;
int beginEnds[256][2] {};
for (int i = totalBegin; i < totalEnd; i++) { for (int i = totalBegin; i < totalEnd; i++) {
const voxel& vox = voxels[i]; const voxel& vox = voxels[i];
blockid_t id = vox.id; blockid_t id = vox.id;
const auto& def = *blockDefsCache[id]; const auto& def = *blockDefsCache[id];
const auto& variant = def.getVariantByBits(vox.state.userbits); if (def.translucent) {
hasTranslucent = def.translucent || hasTranslucent; hasTranslucent = true;
break;
if (beginEnds[variant.drawGroup][0] == 0) {
beginEnds[variant.drawGroup][0] = i + 1;
} }
beginEnds[variant.drawGroup][1] = i;
} }
cancelled = false; cancelled = false;
@ -733,7 +716,7 @@ void BlocksRenderer::build(
densePass = false; densePass = false;
if (hasTranslucent) { if (hasTranslucent) {
sortingMesh = renderTranslucent(voxels, beginEnds); sortingMesh = renderTranslucent(voxels, totalBegin, totalEnd);
} else { } else {
sortingMesh = {}; sortingMesh = {};
} }
@ -746,13 +729,13 @@ void BlocksRenderer::build(
denseRender = false; denseRender = false;
densePass = false; densePass = false;
render(voxels, beginEnds); render(voxels, totalBegin, totalEnd);
size_t endIndex = indexCount; size_t endIndex = indexCount;
denseRender = true; denseRender = true;
densePass = settings.graphics.denseRender.get(); densePass = settings.graphics.denseRender.get();
render(voxels, beginEnds); render(voxels, totalBegin, totalEnd);
denseIndexCount = indexCount; denseIndexCount = indexCount;
for (size_t i = 0; i < denseIndexCount; i++) { for (size_t i = 0; i < denseIndexCount; i++) {
@ -761,7 +744,7 @@ void BlocksRenderer::build(
indexCount = endIndex; indexCount = endIndex;
densePass = false; densePass = false;
render(voxels, beginEnds); render(voxels, totalBegin, totalEnd);
} }
ChunkMeshData BlocksRenderer::createMesh() { ChunkMeshData BlocksRenderer::createMesh() {

View file

@ -25,7 +25,7 @@ class BlocksRenderer final {
public: public:
BlocksRenderer( BlocksRenderer(
size_t capacity, size_t capacity,
const Content& content, const Block* const* blockDefs,
const ContentGfxCache& cache, const ContentGfxCache& cache,
const EngineSettings& settings const EngineSettings& settings
); );
@ -44,7 +44,6 @@ public:
} }
private: private:
static const glm::vec3 SUN_VECTOR; static const glm::vec3 SUN_VECTOR;
const Content& content;
std::unique_ptr<ChunkVertex[]> vertexBuffer; std::unique_ptr<ChunkVertex[]> vertexBuffer;
std::unique_ptr<uint32_t[]> indexBuffer; std::unique_ptr<uint32_t[]> indexBuffer;
std::unique_ptr<uint32_t[]> denseIndexBuffer; std::unique_ptr<uint32_t[]> denseIndexBuffer;
@ -145,13 +144,16 @@ private:
); );
// Does block allow to see other blocks sides (is it transparent) // Does block allow to see other blocks sides (is it transparent)
inline bool isOpen(const glm::ivec3& pos, const Block& def, const Variant& variant) const { bool isOpen(const glm::ivec3& pos, const Block& def, const Variant& variant) const {
const auto& vox = voxelsBuffer->pickBlock( const auto& vox = voxelsBuffer->pickBlock(
chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z
); );
if (vox.id == BLOCK_VOID) { if (vox.id == BLOCK_VOID) {
return false; return false;
} }
if (def.rt.id == vox.id && def.rt.solid && variant.culling == CullingMode::DEFAULT) {
return false;
}
const auto& block = *blockDefsCache[vox.id]; const auto& block = *blockDefsCache[vox.id];
const auto& blockVariant = block.getVariantByBits(vox.state.userbits); const auto& blockVariant = block.getVariantByBits(vox.state.userbits);
uint8_t otherDrawGroup = blockVariant.drawGroup; uint8_t otherDrawGroup = blockVariant.drawGroup;
@ -178,6 +180,6 @@ private:
float x, float y, float z, const glm::ivec3& right, const glm::ivec3& up float x, float y, float z, const glm::ivec3& right, const glm::ivec3& up
) const; ) const;
void render(const voxel* voxels, const int beginEnds[256][2]); void render(const voxel* voxels, int totalBegin, int totalEnd);
SortingMeshData renderTranslucent(const voxel* voxels, int beginEnds[256][2]); SortingMeshData renderTranslucent(const voxel* voxels, int totalBegin, int totalEnd);
}; };

View file

@ -11,9 +11,12 @@
#include "world/Level.hpp" #include "world/Level.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "maths/FrustumCulling.hpp" #include "maths/FrustumCulling.hpp"
#include "maths/util.hpp"
#include "util/listutil.hpp" #include "util/listutil.hpp"
#include "util/ObjectsPool.hpp" #include "util/ObjectsPool.hpp"
#include "util/timeutil.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "content/Content.hpp"
static debug::Logger logger("chunks-render"); static debug::Logger logger("chunks-render");
@ -33,7 +36,7 @@ public:
settings.graphics.denseRender.get() settings.graphics.denseRender.get()
? settings.graphics.chunkMaxVerticesDense.get() ? settings.graphics.chunkMaxVerticesDense.get()
: settings.graphics.chunkMaxVertices.get(), : settings.graphics.chunkMaxVertices.get(),
level.content, level.content.getIndices()->blocks.getDefs(),
cache, cache,
settings settings
) { ) {
@ -92,7 +95,7 @@ ChunksRenderer::ChunksRenderer(
threadPool.setStopOnFail(false); threadPool.setStopOnFail(false);
renderer = std::make_unique<BlocksRenderer>( renderer = std::make_unique<BlocksRenderer>(
settings.graphics.chunkMaxVertices.get(), settings.graphics.chunkMaxVertices.get(),
level.content, cache, settings level.content.getIndices()->blocks.getDefs(), cache, settings
); );
logger.info() << "created " << threadPool.getWorkersCount() << " workers"; logger.info() << "created " << threadPool.getWorkersCount() << " workers";
logger.info() << "memory consumption is " logger.info() << "memory consumption is "
@ -122,30 +125,30 @@ std::shared_ptr<VoxelsRenderVolume> ChunksRenderer::prepareVoxelsVolume(
return voxelsBuffer; return voxelsBuffer;
} }
const ChunkMesh* ChunksRenderer::render( void ChunksRenderer::renderBlocking(const std::shared_ptr<Chunk>& chunk) {
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority glm::ivec2 key(chunk->x, chunk->z);
ChunkMesh mesh {};
auto voxelsBuffer = prepareVoxelsVolume(*chunk);
mesh = renderer->render(chunk.get(), *voxelsBuffer);
meshes[key] = std::move(mesh);
chunk->flags.modified = false;
}
void ChunksRenderer::render(
const std::shared_ptr<Chunk>& chunk, bool lowPriority
) { ) {
glm::ivec2 key(chunk->x, chunk->z); glm::ivec2 key(chunk->x, chunk->z);
if (important) {
ChunkMesh mesh {};
auto voxelsBuffer = prepareVoxelsVolume(*chunk);
mesh = renderer->render(chunk.get(), *voxelsBuffer);
meshes[key] = std::move(mesh);
chunk->flags.modified = false;
return &meshes[key];
}
if (inwork.find(key) != inwork.end() || if (inwork.find(key) != inwork.end() ||
((inwork.size() >= threadPool.getWorkersCount() || ((inwork.size() >= threadPool.getWorkersCount() ||
enqueuedInFrame >= MAX_CHUNKS_ENQUEUED_IN_FRAME) && enqueuedInFrame >= MAX_CHUNKS_ENQUEUED_IN_FRAME) &&
lowPriority)) { lowPriority)) {
return nullptr; return;
} }
chunk->flags.modified = false; chunk->flags.modified = false;
enqueuedInFrame++; enqueuedInFrame++;
auto voxelsBuffer = prepareVoxelsVolume(*chunk); auto voxelsBuffer = prepareVoxelsVolume(*chunk);
threadPool.enqueueJob({chunk, std::move(voxelsBuffer)}); threadPool.enqueueJob({chunk, std::move(voxelsBuffer)});
inwork[key] = true; inwork[key] = true;
return nullptr;
} }
void ChunksRenderer::unload(const Chunk* chunk) { void ChunksRenderer::unload(const Chunk* chunk) {
@ -161,76 +164,69 @@ void ChunksRenderer::clear() {
threadPool.clearQueue(); threadPool.clearQueue();
} }
const ChunkMesh* ChunksRenderer::getOrRender(
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
) {
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
if (found == meshes.end()) {
return render(chunk, important, lowPriority);
}
if (chunk->flags.modified && chunk->flags.lighted) {
render(chunk, important, lowPriority);
}
return &found->second;
}
void ChunksRenderer::update() { void ChunksRenderer::update() {
threadPool.pullResults(); threadPool.pullResults();
enqueuedInFrame = 0; enqueuedInFrame = 0;
}
const Mesh<ChunkVertex>* ChunksRenderer::retrieveChunk( int width = chunks.getWidth();
size_t index, const Camera& camera, bool culling int halfWidth = width / 2;
) { int halfHeight = chunks.getHeight() / 2;
auto chunk = chunks.getChunks()[index]; int centerX = chunks.getOffsetX() + halfWidth;
if (chunk == nullptr) { int centerY = chunks.getOffsetY() + halfHeight;
return nullptr;
} meshBuildQueue.clear();
if (!chunk->flags.lighted) { auto& chunksArray = chunks.getChunks();
const auto& found = meshes.find({chunk->x, chunk->z}); for (int index = 0; index < chunks.getVolume(); index++) {
if (found == meshes.end()) { const auto& chunk = chunks.getChunks()[index];
return nullptr; if (chunk == nullptr || !chunk->flags.lighted) {
} else { continue;
return found->second.mesh.get(); }
int x = chunk->x;
int z = chunk->z;
if (chunk->flags.modified || meshes.find({x, z}) == meshes.end()) {
meshBuildQueue.emplace_back(x - centerX, z - centerY);
} }
} }
float distance = glm::distance( std::sort(
camera.position, meshBuildQueue.begin(),
glm::vec3( meshBuildQueue.end(),
(chunk->x + 0.5f) * CHUNK_W, [](const glm::ivec2& a, const glm::ivec2& b) {
camera.position.y, return util::length2(a) < util::length2(b);
(chunk->z + 0.5f) * CHUNK_D }
)
); );
auto mesh = getOrRender(
chunk,
distance < CHUNK_W * 1.5f,
distance > CHUNK_W * settings.chunks.loadDistance.get() * 0.5
);
if (mesh == nullptr) {
return nullptr;
}
if (chunk->flags.dirtyHeights) {
chunk->updateHeights();
}
if (culling) {
const auto& meshAABB = mesh->meshAABB;
auto aabbMin = meshAABB.min();
auto aabbMax = meshAABB.max();
glm::vec3 min(
chunk->x * CHUNK_W + std::min(0.0f, aabbMin.x),
chunk->bottom,
chunk->z * CHUNK_D + std::min(0.0f, aabbMin.z)
);
glm::vec3 max(
chunk->x * CHUNK_W + aabbMax.x,
chunk->top,
chunk->z * CHUNK_D + aabbMax.z
);
if (!frustum.isBoxVisible(min, max)) return nullptr; int loadDistance = settings.chunks.loadDistance.get();
const int topN = 10;
int top = std::min<int>(meshBuildQueue.size(), topN);
for (int i = 0; i < top; i++) {
glm::ivec2 offset = meshBuildQueue[i];
size_t index =
(offset.y + halfHeight) * width + offset.x + halfWidth;
auto& chunk = chunksArray[index];
assert(chunk != nullptr);
float distance = glm::distance(
glm::vec3 (centerX, 0.0f, centerY),
glm::vec3(
(chunk->x + 0.5f) * CHUNK_W,
0,
(chunk->z + 0.5f) * CHUNK_D
)
);
bool important = distance < CHUNK_W * 1.5f;
bool lowPriority = distance > CHUNK_W * loadDistance * 0.5;
if (chunk->flags.dirtyHeights) {
chunk->updateHeights();
}
if (important) {
renderBlocking(chunk);
} else {
render(chunk, lowPriority);
}
} }
return mesh->mesh.get();
} }
void ChunksRenderer::drawShadowsPass( void ChunksRenderer::drawShadowsPass(
@ -317,16 +313,43 @@ void ChunksRenderer::drawChunks(
// TODO: minimize draw calls number // TODO: minimize draw calls number
for (int i = indices.size()-1; i >= 0; i--) { for (int i = indices.size()-1; i >= 0; i--) {
auto& chunk = chunks.getChunks()[indices[i].index]; auto& chunk = chunks.getChunks()[indices[i].index];
auto mesh = retrieveChunk(indices[i].index, camera, culling); if (chunk == nullptr) {
if (mesh == nullptr) {
continue; continue;
} }
auto found = meshes.find(glm::ivec2(chunk->x, chunk->z));
if (found == meshes.end()) {
continue;
}
auto& mesh = found->second;
if (mesh.mesh == nullptr) {
continue;
}
if (culling) {
const auto& meshAABB = mesh.meshAABB;
auto aabbMin = meshAABB.min();
auto aabbMax = meshAABB.max();
glm::vec3 min(
chunk->x * CHUNK_W + std::min(0.0f, aabbMin.x),
chunk->bottom,
chunk->z * CHUNK_D + std::min(0.0f, aabbMin.z)
);
glm::vec3 max(
chunk->x * CHUNK_W + aabbMax.x,
chunk->top,
chunk->z * CHUNK_D + aabbMax.z
);
if (!frustum.isBoxVisible(min, max)) {
continue;
}
}
glm::vec3 coord( glm::vec3 coord(
chunk->x * CHUNK_W + 0.5f, 0.5f, chunk->z * CHUNK_D + 0.5f chunk->x * CHUNK_W + 0.5f, 0.5f, chunk->z * CHUNK_D + 0.5f
); );
glm::mat4 model = glm::translate(glm::mat4(1.0f), coord); glm::mat4 model = glm::translate(glm::mat4(1.0f), coord);
shader.uniformMatrix("u_model", model); shader.uniformMatrix("u_model", model);
mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1), mesh.mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1),
(coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2); (coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2);
visibleChunks++; visibleChunks++;
} }

View file

@ -1,16 +1,17 @@
#pragma once #pragma once
#define GLM_ENABLE_EXPERIMENTAL
#include "util/ThreadPool.hpp"
#include "commons.hpp"
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/hash.hpp> #include <glm/gtx/hash.hpp>
#include "util/ThreadPool.hpp"
#include "commons.hpp"
template<typename VertexStructure> class Mesh; template<typename VertexStructure> class Mesh;
class Chunk; class Chunk;
class Level; class Level;
@ -44,22 +45,6 @@ struct RendererJob {
}; };
class ChunksRenderer { class ChunksRenderer {
const Chunks& chunks;
const Assets& assets;
const Frustum& frustum;
const EngineSettings& settings;
std::unique_ptr<BlocksRenderer> renderer;
std::unordered_map<glm::ivec2, ChunkMesh> meshes;
std::unordered_map<glm::ivec2, bool> inwork;
std::vector<ChunksSortEntry> indices;
util::ThreadPool<RendererJob, RendererResult> threadPool;
const Mesh<ChunkVertex>* retrieveChunk(
size_t index, const Camera& camera, bool culling
);
std::shared_ptr<VoxelsRenderVolume> prepareVoxelsVolume(const Chunk& chunk);
size_t enqueuedInFrame = 0;
public: public:
ChunksRenderer( ChunksRenderer(
const Level& level, const Level& level,
@ -69,18 +54,11 @@ public:
const ContentGfxCache& cache, const ContentGfxCache& cache,
const EngineSettings& settings const EngineSettings& settings
); );
virtual ~ChunksRenderer(); ~ChunksRenderer();
const ChunkMesh* render(
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
);
void unload(const Chunk* chunk); void unload(const Chunk* chunk);
void clear(); void clear();
const ChunkMesh* getOrRender(
const std::shared_ptr<Chunk>& chunk, bool important, bool lowPriority
);
void drawShadowsPass( void drawShadowsPass(
const Camera& camera, Shader& shader, const Camera& playerCamera const Camera& camera, Shader& shader, const Camera& playerCamera
); );
@ -92,4 +70,25 @@ public:
void update(); void update();
static size_t visibleChunks; static size_t visibleChunks;
private:
const Chunks& chunks;
const Assets& assets;
const Frustum& frustum;
const EngineSettings& settings;
std::unique_ptr<BlocksRenderer> renderer;
std::unordered_map<glm::ivec2, ChunkMesh> meshes;
std::unordered_map<glm::ivec2, bool> inwork;
std::vector<ChunksSortEntry> indices;
util::ThreadPool<RendererJob, RendererResult> threadPool;
std::vector<glm::ivec2> meshBuildQueue;
size_t enqueuedInFrame = 0;
std::shared_ptr<VoxelsRenderVolume> prepareVoxelsVolume(const Chunk& chunk);
void render(const std::shared_ptr<Chunk>& chunk, bool lowPriority);
void renderBlocking(const std::shared_ptr<Chunk>& chunk);
}; };

View file

@ -329,7 +329,7 @@ void CloudsRenderer::draw(
shader.uniform4f("u_tint", glm::vec4(weather.cloudsTint(), 1.0f)); shader.uniform4f("u_tint", glm::vec4(weather.cloudsTint(), 1.0f));
shader.uniform1f("u_fogFactor", fogFactor * 0.03f); shader.uniform1f("u_fogFactor", fogFactor * 0.03f);
shader.uniform1f("u_fogCurve", 0.7f - 0.3f); shader.uniform1f("u_fogCurve", 0.4f);
for (int i = 0; i < std::min<int>(quality, layers.size()); i++) { for (int i = 0; i < std::min<int>(quality, layers.size()); i++) {
draw(layers[i], frustum, shader, camera, timer, i); draw(layers[i], frustum, shader, camera, timer, i);

View file

@ -93,7 +93,7 @@ void DebugLinesRenderer::drawCoordSystem(
} }
void DebugLinesRenderer::render( void DebugLinesRenderer::render(
DrawContext& pctx, const DrawContext& pctx,
const Camera& camera, const Camera& camera,
LineBatch& linesBatch, LineBatch& linesBatch,
Shader& linesShader, Shader& linesShader,

View file

@ -20,7 +20,7 @@ public:
/// @param linesShader Shader used for rendering lines /// @param linesShader Shader used for rendering lines
/// @param showChunkBorders Whether to show chunk borders /// @param showChunkBorders Whether to show chunk borders
void render( void render(
DrawContext& ctx, const DrawContext& ctx,
const Camera& camera, const Camera& camera,
LineBatch& linesBatch, LineBatch& linesBatch,
Shader& linesShader, Shader& linesShader,

View file

@ -25,6 +25,7 @@
#include "voxels/blocks_agent.hpp" #include "voxels/blocks_agent.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "world/Weather.hpp"
#include "WorldRenderer.hpp" #include "WorldRenderer.hpp"
namespace fs = std::filesystem; namespace fs = std::filesystem;

View file

@ -46,7 +46,7 @@ void MainBatch::flush() {
texture = blank.get(); texture = blank.get();
} }
texture->bind(); texture->bind();
mesh->reload(buffer.get(), index); mesh->reload(buffer.get(), index, true);
mesh->draw(); mesh->draw();
index = 0; index = 0;
} }

View file

@ -51,7 +51,9 @@
#include "window/Window.hpp" #include "window/Window.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "world/LevelEvents.hpp" #include "world/LevelEvents.hpp"
#include "world/Weather.hpp"
#include "world/World.hpp" #include "world/World.hpp"
#include "debug/Logger.hpp"
#include <assert.h> #include <assert.h>
#include <algorithm> #include <algorithm>
@ -62,9 +64,20 @@
inline constexpr size_t BATCH3D_CAPACITY = 4096; inline constexpr size_t BATCH3D_CAPACITY = 4096;
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000; inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
static debug::Logger logger("world-renderer");
bool WorldRenderer::showChunkBorders = false; bool WorldRenderer::showChunkBorders = false;
bool WorldRenderer::showEntitiesDebug = false; bool WorldRenderer::showEntitiesDebug = false;
template <typename T>
static ObserverHandler observe_setting(
ObservableSetting<T>& setting, bool& dirtySettings
) {
return setting.observe([&dirtySettings](auto const&) {
dirtySettings = true;
});
}
WorldRenderer::WorldRenderer( WorldRenderer::WorldRenderer(
Engine& engine, LevelFrontend& frontend, Player& player Engine& engine, LevelFrontend& frontend, Player& player
) )
@ -72,6 +85,7 @@ WorldRenderer::WorldRenderer(
level(frontend.getLevel()), level(frontend.getLevel()),
player(player), player(player),
assets(*engine.getAssets()), assets(*engine.getAssets()),
weather(frontend.getWeather()),
frustumCulling(std::make_unique<Frustum>()), frustumCulling(std::make_unique<Frustum>()),
lineBatch(std::make_unique<LineBatch>()), lineBatch(std::make_unique<LineBatch>()),
batch3d(std::make_unique<Batch3D>(BATCH3D_CAPACITY)), batch3d(std::make_unique<Batch3D>(BATCH3D_CAPACITY)),
@ -120,10 +134,41 @@ WorldRenderer::WorldRenderer(
shadowMapping = std::make_unique<Shadows>(level); shadowMapping = std::make_unique<Shadows>(level);
debugLines = std::make_unique<DebugLinesRenderer>(level); debugLines = std::make_unique<DebugLinesRenderer>(level);
cloudsRenderer = std::make_unique<CloudsRenderer>(); cloudsRenderer = std::make_unique<CloudsRenderer>();
keepAlive(observe_setting(settings.graphics.advancedRender, dirtySettings));
keepAlive(observe_setting(settings.graphics.shadowsQuality, dirtySettings));
keepAlive(observe_setting(settings.graphics.ssao, dirtySettings));
} }
WorldRenderer::~WorldRenderer() = default; WorldRenderer::~WorldRenderer() = default;
void WorldRenderer::refreshSettings() {
Shader* affectedShaders[] {
&assets.require<Shader>("main"),
&assets.require<Shader>("entity"),
&assets.require<Shader>("clouds"),
&assets.require<Shader>("translucent"),
&assets.require<PostEffect>("deferred_lighting").getShader(),
nullptr
};
const auto& graphics = engine.getSettings().graphics;
gbufferPipeline = graphics.advancedRender.get();
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
shadowMapping->setQuality(shadowsQuality);
std::vector<std::string> defines;
if (shadowsQuality != 0) defines.emplace_back("ENABLE_SHADOWS");
if (graphics.ssao.get()) defines.emplace_back("ENABLE_SSAO");
if (gbufferPipeline) defines.emplace_back("ADVANCED_RENDER");
logger.info() << "recompiling shaders due to settings change";
for (size_t i = 0; affectedShaders[i]; i++) {
affectedShaders[i]->recompile(defines);
}
}
static void setup_weather(Shader& shader, const Weather& weather) { static void setup_weather(Shader& shader, const Weather& weather) {
shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity()); shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity());
shader.uniform1f("u_weatherFogDencity", weather.fogDencity()); shader.uniform1f("u_weatherFogDencity", weather.fogDencity());
@ -156,7 +201,7 @@ void WorldRenderer::setupWorldShader(
shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get());
shader.uniform1i("u_debugLights", lightsDebug); shader.uniform1i("u_debugLights", lightsDebug);
shader.uniform1i("u_debugNormals", false); shader.uniform1i("u_debugNormals", false);
shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime); shader.uniform1f("u_dayTime", level.getWorld().getInfo().daytime);
shader.uniform2f("u_lightDir", skybox->getLightDir()); shader.uniform2f("u_lightDir", skybox->getLightDir());
shader.uniform1i("u_skybox", advanced_pipeline::TARGET_SKYBOX); shader.uniform1i("u_skybox", advanced_pipeline::TARGET_SKYBOX);
@ -188,8 +233,6 @@ void WorldRenderer::renderOpaque(
const EngineSettings& settings, const EngineSettings& settings,
bool hudVisible bool hudVisible
) { ) {
texts->render(ctx, camera, settings, hudVisible, false);
float fogFactor = calcFogFactor(); float fogFactor = calcFogFactor();
auto& entityShader = assets.require<Shader>("entity"); auto& entityShader = assets.require<Shader>("entity");
@ -212,12 +255,12 @@ void WorldRenderer::renderOpaque(
modelBatch->render(); modelBatch->render();
particles->render(camera); particles->render(camera);
auto& shader = assets.require<Shader>("main"); auto& mainShader = assets.require<Shader>("main");
auto& cloudsShader = assets.require<Shader>("clouds"); auto& cloudsShader = assets.require<Shader>("clouds");
setupWorldShader(shader, camera, settings, fogFactor); setupWorldShader(mainShader, camera, settings, fogFactor);
chunksRenderer->drawChunks(camera, shader); chunksRenderer->drawChunks(camera, mainShader);
blockWraps->draw(ctx); blockWraps->draw(ctx);
if (level.environment.sky.clouds) { if (level.environment.sky.clouds) {
@ -232,7 +275,7 @@ void WorldRenderer::renderOpaque(
if (hudVisible) { if (hudVisible) {
auto& linesShader = assets.require<Shader>("lines"); auto& linesShader = assets.require<Shader>("lines");
renderLines(camera, linesShader, ctx); renderInWorldLines(camera, linesShader, ctx);
} }
} }
@ -265,7 +308,7 @@ void WorldRenderer::renderBlockSelection() {
lineBatch->flush(); lineBatch->flush();
} }
void WorldRenderer::renderLines( void WorldRenderer::renderInWorldLines(
const Camera& camera, Shader& linesShader, const DrawContext& pctx const Camera& camera, Shader& linesShader, const DrawContext& pctx
) { ) {
linesShader.use(); linesShader.use();
@ -282,193 +325,144 @@ void WorldRenderer::renderLines(
} }
} }
void WorldRenderer::refreshSettings(Shader** shaders) {
const auto& graphics = engine.getSettings().graphics;
gbufferPipeline = graphics.advancedRender.get();
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
shadowMapping->setQuality(shadowsQuality);
CompileTimeShaderSettings currentSettings {
gbufferPipeline,
shadowsQuality != 0,
graphics.ssao.get() && gbufferPipeline};
if (prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
prevCTShaderSettings.shadows != currentSettings.shadows ||
prevCTShaderSettings.ssao != currentSettings.ssao) {
std::vector<std::string> defines;
if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
if (currentSettings.advancedRender)
defines.emplace_back("ADVANCED_RENDER");
for (size_t i = 0; shaders[i]; i++) {
shaders[i]->recompile(defines);
}
prevCTShaderSettings = currentSettings;
}
}
void WorldRenderer::update(const Camera& camera, float delta) { void WorldRenderer::update(const Camera& camera, float delta) {
timer += delta; timer += delta;
chunksRenderer->update();
weather.update(delta); weather.update(delta);
precipitation->update(delta); precipitation->update(delta);
particles->update(camera, delta); particles->update(camera, delta);
} }
void WorldRenderer::renderFrameClassic(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
const auto& settings = engine.getSettings();
const auto& worldInfo = level.getWorld().getInfo();
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
ctx.setFramebuffer(postProcessing.getFramebuffer());
skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, weather.clouds()
);
texts->render(ctx, camera, settings, hudVisible, false);
if (debug && hudVisible) {
renderDebugLines(ctx, camera);
}
DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks
{
const auto& settings = engine.getSettings();
auto& translucentShader = assets.require<Shader>("translucent");
auto sctx = wctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, calcFogFactor());
chunksRenderer->drawSortedMeshes(camera, translucentShader);
}
renderWeatherEffects(camera);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void WorldRenderer::renderFrameAdvanced(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
const auto& settings = engine.getSettings();
const auto& worldInfo = level.getWorld().getInfo();
float fogFactor = calcFogFactor();
shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
auto& shader = assets.require<Shader>("shadows");
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
});
auto& deferredShader =
assets.require<PostEffect>("deferred_lighting").getShader();
deferredShader.use();
setupWorldShader(deferredShader, camera, settings, fogFactor);
postProcessing.renderDeferredShading(pctx, assets, timer, camera);
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
postProcessing.bindDepthBuffer();
skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, weather.clouds()
);
texts->render(ctx, camera, settings, hudVisible, false);
if (debug && hudVisible) {
renderDebugLines(ctx, camera);
}
DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks
{
const auto& settings = engine.getSettings();
auto& translucentShader = assets.require<Shader>("translucent");
auto sctx = wctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor);
chunksRenderer->drawSortedMeshes(camera, translucentShader);
}
renderWeatherEffects(camera);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void WorldRenderer::renderFrame( void WorldRenderer::renderFrame(
const DrawContext& pctx, const DrawContext& pctx,
Camera& camera, Camera& camera,
bool hudVisible, bool hudVisible,
PostProcessing& postProcessing PostProcessing& postProcessing
) { ) {
auto projView = camera.getProjView();
auto world = level.getWorld();
const auto& vp = pctx.getViewport(); const auto& vp = pctx.getViewport();
camera.setAspectRatio(vp.x / static_cast<float>(vp.y)); camera.setAspectRatio(vp.x / static_cast<float>(vp.y));
if (dirtySettings) {
refreshSettings();
dirtySettings = false;
}
auto& mainShader = assets.require<Shader>("main"); const auto& worldInfo = level.getWorld().getInfo();
auto& entityShader = assets.require<Shader>("entity");
auto& cloudsShader = assets.require<Shader>("clouds");
auto& translucentShader = assets.require<Shader>("translucent");
auto& deferredShader =
assets.require<PostEffect>("deferred_lighting").getShader();
const auto& settings = engine.getSettings();
Shader* affectedShaders[] {
&mainShader,
&entityShader,
&cloudsShader,
&translucentShader,
&deferredShader,
nullptr};
refreshSettings(affectedShaders);
const auto& worldInfo = world->getInfo();
float clouds = weather.clouds(); float clouds = weather.clouds();
clouds = glm::max(worldInfo.fog, clouds);
float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f;
float random = rand() / static_cast<float>(RAND_MAX); float random = rand() / static_cast<float>(RAND_MAX);
skybox->refresh( skybox->refresh(
level.environment, level.environment,
pctx, pctx,
worldInfo.daytime, worldInfo.daytime,
mie, 1.0f + clouds,
weather.skyTint(), weather.skyTint(),
weather.highlight * random, weather.highlight * random,
4 4
); );
renderOpaquePass(pctx, camera, hudVisible, postProcessing);
chunksRenderer->update();
shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) {
auto& shader = assets.require<Shader>("shadows");
setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f);
chunksRenderer->drawShadowsPass(shadowCamera, shader, camera);
});
{
DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline);
display::clearDepth();
/* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
renderOpaque(ctx, camera, settings, hudVisible);
}
texts->render(pctx, camera, settings, hudVisible, true);
}
float fogFactor = calcFogFactor();
if (gbufferPipeline) { if (gbufferPipeline) {
deferredShader.use(); renderFrameAdvanced(pctx, camera, hudVisible, postProcessing);
setupWorldShader(deferredShader, camera, settings, fogFactor); } else {
postProcessing.renderDeferredShading(pctx, assets, timer, camera); renderFrameClassic(pctx, camera, hudVisible, postProcessing);
}
{
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
if (gbufferPipeline) {
postProcessing.bindDepthBuffer();
} else {
ctx.setFramebuffer(postProcessing.getFramebuffer());
}
// Background sky plane
skybox->draw(
level.environment, ctx, camera, worldInfo.daytime, clouds
);
auto& linesShader = assets.require<Shader>("lines");
linesShader.use();
if (debug && hudVisible) {
debugLines->render(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
}
linesShader.uniformMatrix("u_projview", projView);
lineBatch->flush();
DrawContext wctx = ctx.sub();
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
// Translucent blocks
{
auto sctx = wctx.sub();
sctx.setCullFace(true);
translucentShader.use();
setupWorldShader(translucentShader, camera, settings, fogFactor);
chunksRenderer->drawSortedMeshes(camera, translucentShader);
}
// Weather effects
entityShader.use();
setupWorldShader(entityShader, camera, settings, fogFactor);
std::array<const WeatherPreset*, 2> weatherInstances {
&weather.a, &weather.b};
for (const auto& weather : weatherInstances) {
float maxIntensity = weather->fall.maxIntensity;
float zero = weather->fall.minOpacity;
float one = weather->fall.maxOpacity;
float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
entityShader.uniform1f(
"u_opacity", weather->fall.opaque ? t * t : t
);
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
precipitation->render(camera, *weather);
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }
postProcessing.render(pctx, assets, timer, camera); postProcessing.render(pctx, assets, timer, camera);
if (player.currentCamera == player.fpCamera) { if (player.currentCamera == player.fpCamera) {
DrawContext ctx = pctx.sub(); renderHandsPass(pctx, camera);
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
display::clearDepth();
// prepare modified HUD camera
Camera hudcam = camera;
hudcam.far = 10.0f;
hudcam.setFov(0.9f);
hudcam.position = {};
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
hands->render(camera);
} }
{ {
auto bctx = pctx.sub(); auto bctx = pctx.sub();
@ -478,6 +472,83 @@ void WorldRenderer::renderFrame(
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
} }
void WorldRenderer::renderOpaquePass(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
) {
const auto& settings = engine.getSettings();
DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline);
display::clearDepth();
/* Main opaque pass (GBuffer pass) */ {
DrawContext ctx = wctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
renderOpaque(ctx, camera, settings, hudVisible);
}
texts->render(wctx, camera, settings, hudVisible, true);
}
void WorldRenderer::renderWeatherEffects(Camera& camera) {
const auto& settings = engine.getSettings();
auto& entityShader = assets.require<Shader>("entity");
entityShader.use();
setupWorldShader(entityShader, camera, settings, calcFogFactor());
std::array<const WeatherPreset*, 2> weatherInstances {
&weather.a, &weather.b};
for (const auto& weather : weatherInstances) {
float maxIntensity = weather->fall.maxIntensity;
float zero = weather->fall.minOpacity;
float one = weather->fall.maxOpacity;
float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
entityShader.uniform1f(
"u_opacity", weather->fall.opaque ? t * t : t
);
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
precipitation->render(camera, *weather);
}
}
}
void WorldRenderer::renderDebugLines(const DrawContext& ctx, Camera& camera) {
auto& linesShader = assets.require<Shader>("lines");
linesShader.use();
debugLines->render(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
linesShader.uniformMatrix("u_projview", camera.getProjView());
lineBatch->flush();
}
void WorldRenderer::renderHandsPass(const DrawContext& pctx, Camera& camera) {
auto& entityShader = assets.require<Shader>("entity");
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
display::clearDepth();
// prepare modified HUD camera
Camera hudcam = camera;
hudcam.far = 10.0f;
hudcam.setFov(0.9f);
hudcam.position = {};
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
hands->render(camera);
}
void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
int x = std::floor(player.currentCamera->position.x); int x = std::floor(player.currentCamera->position.x);
int y = std::floor(player.currentCamera->position.y); int y = std::floor(player.currentCamera->position.y);
@ -523,7 +594,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
batch3d->flush(); batch3d->flush();
} }
void WorldRenderer::clear() { void WorldRenderer::resetCache() {
chunksRenderer->clear(); chunksRenderer->clear();
} }

View file

@ -4,12 +4,11 @@
#include "typedefs.hpp" #include "typedefs.hpp"
#include "presets/WeatherPreset.hpp" #include "presets/WeatherPreset.hpp"
#include "world/Weather.hpp"
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "util/ObjectsKeeper.hpp"
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <algorithm>
#include <string> #include <string>
class Assets; class Assets;
@ -36,18 +35,38 @@ class Skybox;
class TextsRenderer; class TextsRenderer;
class CloudsRenderer; class CloudsRenderer;
struct EngineSettings; struct EngineSettings;
struct Weather;
struct CompileTimeShaderSettings { class WorldRenderer final : public util::ObjectsKeeper {
bool advancedRender = false; public:
bool shadows = false; static bool showChunkBorders;
bool ssao = false; static bool showEntitiesDebug;
};
class WorldRenderer { WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player);
~WorldRenderer();
void update(const Camera& camera, float delta);
void renderFrame(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void resetCache();
void setDebug(bool flag);
void toggleLightsDebug();
Weather& getWeather();
private:
Engine& engine; Engine& engine;
const Level& level; const Level& level;
Player& player; Player& player;
const Assets& assets; const Assets& assets;
Weather& weather;
std::unique_ptr<Frustum> frustumCulling; std::unique_ptr<Frustum> frustumCulling;
std::unique_ptr<LineBatch> lineBatch; std::unique_ptr<LineBatch> lineBatch;
std::unique_ptr<Batch3D> batch3d; std::unique_ptr<Batch3D> batch3d;
@ -59,14 +78,12 @@ class WorldRenderer {
std::unique_ptr<DebugLinesRenderer> debugLines; std::unique_ptr<DebugLinesRenderer> debugLines;
std::unique_ptr<PrecipitationRenderer> precipitation; std::unique_ptr<PrecipitationRenderer> precipitation;
std::unique_ptr<CloudsRenderer> cloudsRenderer; std::unique_ptr<CloudsRenderer> cloudsRenderer;
Weather weather {};
float timer = 0.0f; float timer = 0.0f;
bool debug = false; bool debug = false;
bool lightsDebug = false; bool lightsDebug = false;
bool gbufferPipeline = false; bool gbufferPipeline = false;
bool dirtySettings = true;
CompileTimeShaderSettings prevCTShaderSettings {};
/// @brief Render block selection lines /// @brief Render block selection lines
void renderBlockSelection(); void renderBlockSelection();
@ -74,7 +91,7 @@ class WorldRenderer {
/// @brief Render lines (selection and debug) /// @brief Render lines (selection and debug)
/// @param camera active camera /// @param camera active camera
/// @param linesShader shader used /// @param linesShader shader used
void renderLines( void renderInWorldLines(
const Camera& camera, Shader& linesShader, const DrawContext& pctx const Camera& camera, Shader& linesShader, const DrawContext& pctx
); );
@ -98,7 +115,34 @@ class WorldRenderer {
bool hudVisible bool hudVisible
); );
void refreshSettings(Shader** shaders); void renderOpaquePass(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void renderWeatherEffects(Camera& camera);
void renderHandsPass(const DrawContext& pctx, Camera& camera);
void renderDebugLines(const DrawContext& context, Camera& camera);
void renderFrameClassic(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void renderFrameAdvanced(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void refreshSettings();
float calcFogFactor() const; float calcFogFactor() const;
public: public:
@ -106,27 +150,4 @@ public:
std::unique_ptr<TextsRenderer> texts; std::unique_ptr<TextsRenderer> texts;
std::unique_ptr<BlockWrapsRenderer> blockWraps; std::unique_ptr<BlockWrapsRenderer> blockWraps;
std::unique_ptr<NamedSkeletons> skeletons; std::unique_ptr<NamedSkeletons> skeletons;
static bool showChunkBorders;
static bool showEntitiesDebug;
WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player);
~WorldRenderer();
void update(const Camera& camera, float delta);
void renderFrame(
const DrawContext& context,
Camera& camera,
bool hudVisible,
PostProcessing& postProcessing
);
void clear();
void setDebug(bool flag);
void toggleLightsDebug();
Weather& getWeather();
}; };

View file

@ -2,7 +2,7 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <iostream> #include <iosfwd>
#include <filesystem> #include <filesystem>
#include "../path.hpp" #include "../path.hpp"

View file

@ -12,7 +12,7 @@ Inventories::Inventories(Level& level) : level(level) {
Inventories::~Inventories() = default; Inventories::~Inventories() = default;
std::shared_ptr<Inventory> Inventories::create(size_t size) { std::shared_ptr<Inventory> Inventories::create(size_t size) {
int64_t id = level.getWorld()->getNextInventoryId(); int64_t id = level.getWorld().getNextInventoryId();
auto inv = std::make_shared<Inventory>(id, size); auto inv = std::make_shared<Inventory>(id, size);
store(inv); store(inv);
return inv; return inv;
@ -52,7 +52,7 @@ std::shared_ptr<Inventory> Inventories::clone(int64_t id) {
auto original = get(id); auto original = get(id);
if (original == nullptr) return nullptr; if (original == nullptr) return nullptr;
auto clone = std::make_shared<Inventory>(*original); auto clone = std::make_shared<Inventory>(*original);
clone->setId(level.getWorld()->getNextInventoryId()); clone->setId(level.getWorld().getNextInventoryId());
store(clone); store(clone);
return clone; return clone;
} }

View file

@ -27,7 +27,7 @@ ChunksController::ChunksController(Level& level)
generator(std::make_unique<WorldGenerator>( generator(std::make_unique<WorldGenerator>(
level.content.generators.require(level.environment.generator), level.content.generators.require(level.environment.generator),
level.content, level.content,
level.getWorld()->getSeed() level.getWorld().getSeed()
)) {} )) {}
ChunksController::~ChunksController() = default; ChunksController::~ChunksController() = default;

View file

@ -314,8 +314,8 @@ void EngineController::setLocalPlayer(int64_t player) {
localPlayer = player; localPlayer = player;
} }
void EngineController::reopenWorld(World* world) { void EngineController::reopenWorld(World& world) {
std::string name = world->wfile->getFolder().name(); std::string name = world.wfile->getFolder().name();
engine.onWorldClosed(); engine.onWorldClosed();
openWorld(name, true); openWorld(name, true);
} }
@ -384,16 +384,16 @@ void EngineController::reconfigPacks(
} }
} else { } else {
auto level = controller->getLevel(); auto level = controller->getLevel();
auto world = level->getWorld(); auto& world = level->getWorld();
controller->processBeforeQuit(); controller->processBeforeQuit();
controller->saveWorld(); controller->saveWorld();
auto names = PacksManager::getNames(world->getPacks()); auto names = PacksManager::getNames(world.getPacks());
auto& manager = contentControl.scan(); auto& manager = contentControl.scan();
reconfig_packs_inside(manager, names, packsToAdd, packsToRemove); reconfig_packs_inside(manager, names, packsToAdd, packsToRemove);
const auto& settings = engine.getSettings(); const auto& settings = engine.getSettings();
auto& wfile = *world->wfile; auto& wfile = *world.wfile;
if (!settings.debug.generatorTestMode.get()) { if (!settings.debug.generatorTestMode.get()) {
wfile.removeIndices(packsToRemove); wfile.removeIndices(packsToRemove);
} }

View file

@ -41,5 +41,5 @@ public:
void setLocalPlayer(int64_t player); void setLocalPlayer(int64_t player);
void reopenWorld(World* world); void reopenWorld(World& world);
}; };

View file

@ -140,16 +140,16 @@ void LevelController::processBeforeQuit() {
} }
void LevelController::saveWorld() { void LevelController::saveWorld() {
auto world = level->getWorld(); auto& world = level->getWorld();
if (world->isNameless()) { if (world.isNameless()) {
logger.info() << "nameless world will not be saved"; logger.info() << "nameless world will not be saved";
return; return;
} }
logger.info() << "writing world '" << world->getName() << "'"; logger.info() << "writing world '" << world.getName() << "'";
world->wfile->createDirectories(); world.wfile->createDirectories();
scripting::on_world_save(); scripting::on_world_save();
level->onSave(); level->onSave();
level->getWorld()->write(*level); level->getWorld().write(*level);
} }
void LevelController::onWorldQuit() { void LevelController::onWorldQuit() {

View file

@ -2,6 +2,7 @@
#include "world/Level.hpp" #include "world/Level.hpp"
#include "world/World.hpp" #include "world/World.hpp"
#include "world/Weather.hpp"
using namespace scripting; using namespace scripting;
@ -9,6 +10,11 @@ static Weather& require_weather() {
if (level == nullptr) { if (level == nullptr) {
throw std::runtime_error("world is not open"); throw std::runtime_error("world is not open");
} }
if (renderer == nullptr) {
throw std::runtime_error(
"world frontend is not initialized yet (wait for on_hud_open event)"
);
}
return renderer->getWeather(); return renderer->getWeather();
} }

View file

@ -33,7 +33,7 @@ static Level& require_level() {
} }
static WorldInfo& require_world_info() { static WorldInfo& require_world_info() {
return require_level().getWorld()->getInfo(); return require_level().getWorld().getInfo();
} }
static int l_is_open(lua::State* L) { static int l_is_open(lua::State* L) {
@ -139,7 +139,7 @@ static int l_get_chunk_data(lua::State* L) {
auto voxelData = std::make_unique<ubyte[]>(CHUNK_DATA_LEN); auto voxelData = std::make_unique<ubyte[]>(CHUNK_DATA_LEN);
std::vector<ubyte> chunkData; std::vector<ubyte> chunkData;
if (chunk == nullptr) { if (chunk == nullptr) {
auto& regions = level->getWorld()->wfile->getRegions(); auto& regions = level->getWorld().wfile->getRegions();
if (!regions.getVoxels(x, z, voxelData.get())) { if (!regions.getVoxels(x, z, voxelData.get())) {
return 0; return 0;
} }
@ -218,7 +218,7 @@ static int l_save_chunk_data(lua::State* L) {
reinterpret_cast<const ubyte*>(buffer.data()), reinterpret_cast<const ubyte*>(buffer.data()),
reinterpret_cast<const ubyte*>(buffer.data()) + buffer.size() reinterpret_cast<const ubyte*>(buffer.data()) + buffer.size()
), ),
level->getWorld()->wfile->getRegions() level->getWorld().wfile->getRegions()
); );
return 0; return 0;
} }

View file

@ -310,7 +310,7 @@ void scripting::on_world_load(LevelController* controller) {
for (auto& pack : content_control->getAllContentPacks()) { for (auto& pack : content_control->getAllContentPacks()) {
lua::emit_event(L, pack.id + ":.worldopen", [](auto L) { lua::emit_event(L, pack.id + ":.worldopen", [](auto L) {
return lua::pushboolean( return lua::pushboolean(
L, !scripting::level->getWorld()->getInfo().isLoaded L, !scripting::level->getWorld().getInfo().isLoaded
); );
}); });
} }

View file

@ -109,12 +109,16 @@ namespace util {
/// @return integer square of vector length /// @return integer square of vector length
/// @note glm::length2 does not support integer vectors /// @note glm::length2 does not support integer vectors
inline int length2(const glm::ivec3& a) { inline long long int length2(const glm::ivec2& a) {
return a.x * a.x + a.y * a.y;
}
inline long long int length2(const glm::ivec3& a) {
return a.x * a.x + a.y * a.y + a.z * a.z; return a.x * a.x + a.y * a.y + a.z * a.z;
} }
/// @return integer square of vector length /// @return integer square of vector length
inline int length2(int x, int y, int z) { inline long long int length2(int x, int y, int z) {
return x * x + y * y + z * z; return x * x + y * y + z * z;
} }

View file

@ -67,7 +67,7 @@ Player* Players::getNearest(const glm::vec3& position) const {
} }
Player* Players::create(int64_t id) { Player* Players::create(int64_t id) {
int64_t& nextPlayerID = level.getWorld()->getInfo().nextPlayerId; int64_t& nextPlayerID = level.getWorld().getInfo().nextPlayerId;
if (id == NONE) { if (id == NONE) {
id = nextPlayerID++; id = nextPlayerID++;
} else { } else {
@ -146,7 +146,7 @@ void Players::deserialize(const dv::value& src) {
auto& inventory = player->getInventory(); auto& inventory = player->getInventory();
// invalid inventory id pre 0.25 // invalid inventory id pre 0.25
if (inventory->getId() == 0) { if (inventory->getId() == 0) {
inventory->setId(level.getWorld()->getNextInventoryId()); inventory->setId(level.getWorld().getNextInventoryId());
} }
level.inventories->store(inventory); level.inventories->store(inventory);
} }

View file

@ -5,7 +5,6 @@
#include <optional> #include <optional>
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
#include <iostream>
#include <queue> #include <queue>
#include <thread> #include <thread>
#include <utility> #include <utility>

View file

@ -29,16 +29,16 @@ public:
voxel voxels[CHUNK_VOL] {}; voxel voxels[CHUNK_VOL] {};
std::shared_ptr<Lightmap> lightmap; std::shared_ptr<Lightmap> lightmap;
struct { struct {
bool modified : 1; bool modified : 1; // is chunk mesh should be updated
bool ready : 1; bool ready : 1; // is chunk ready for modifications (loaded / generated)
bool loaded : 1; bool loaded : 1; // was chunk loaded from region
bool lighted : 1; bool lighted : 1; // is lights built (chunk ready to be visualized)
bool unsaved : 1; bool unsaved : 1; // does chunk contain unsaved changes
bool loadedLights : 1; bool loadedLights : 1; // was lights loaded from cache
bool entities : 1; bool entities : 1; // does chunk contain entities list changes
bool blocksData : 1; bool blocksData : 1; // does chunk contain block fields changes
bool dirtyHeights : 1; bool dirtyHeights : 1; // is chunk bottom, top should be recalculated
bool inventoriesRemoved : 1; bool inventoriesRemoved : 1; // was block inventories removed since the last save
} flags {}; } flags {};
uint64_t lastRandomTickId = -1; uint64_t lastRandomTickId = -1;

View file

@ -107,7 +107,7 @@ std::shared_ptr<Chunk> GlobalChunks::create(int x, int z, bool lighting) {
chunks_pool.create(x, z, lighting ? lightmaps_pool.create() : nullptr); chunks_pool.create(x, z, lighting ? lightmaps_pool.create() : nullptr);
chunksMap[keyfrom(x, z)] = chunk; chunksMap[keyfrom(x, z)] = chunk;
World& world = *level.getWorld(); World& world = level.getWorld();
auto& regions = world.wfile.get()->getRegions(); auto& regions = world.wfile.get()->getRegions();
if (regions.getVoxels(chunk->x, chunk->z, voxelDataBuffer.get())) { if (regions.getVoxels(chunk->x, chunk->z, voxelDataBuffer.get())) {
@ -197,7 +197,7 @@ void GlobalChunks::save(Chunk* chunk) {
if (!entities.empty()) { if (!entities.empty()) {
chunk->flags.entities = true; chunk->flags.entities = true;
} }
level.getWorld()->wfile->getRegions().put( level.getWorld().wfile->getRegions().put(
chunk, chunk,
chunk->flags.entities ? json::to_binary(root, true) chunk->flags.entities ? json::to_binary(root, true)
: std::vector<ubyte>() : std::vector<ubyte>()

View file

@ -66,7 +66,7 @@ public:
return lights.get(); return lights.get();
} }
inline blockid_t pickBlockId(int bx, int by, int bz) const { blockid_t pickBlockId(int bx, int by, int bz) const {
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h || if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
bz >= z + d) { bz >= z + d) {
return BLOCK_VOID; return BLOCK_VOID;
@ -75,7 +75,7 @@ public:
} }
inline voxel pickBlock(int bx, int by, int bz) const { voxel pickBlock(int bx, int by, int bz) const {
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h || if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
bz >= z + d) { bz >= z + d) {
return {BLOCK_VOID, {}}; return {BLOCK_VOID, {}};
@ -83,7 +83,7 @@ public:
return voxels[vox_index(bx - x, by - y, bz - z, w, d)]; return voxels[vox_index(bx - x, by - y, bz - z, w, d)];
} }
inline light_t pickLight(int bx, int by, int bz) const { light_t pickLight(int bx, int by, int bz) const {
if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h || if (bx < x || by < y || bz < z || bx >= x + w || by >= y + h ||
bz >= z + d) { bz >= z + d) {
return 0; return 0;
@ -103,10 +103,10 @@ private:
template <int w, int h, int d> template <int w, int h, int d>
class StaticVoxelsVolume { class StaticVoxelsVolume {
public: public:
static inline constexpr size_t size = w * h * d; static constexpr size_t size = w * h * d;
static inline constexpr int width = w; static constexpr int width = w;
static inline constexpr int height = h; static constexpr int height = h;
static inline constexpr int depth = d; static constexpr int depth = d;
StaticVoxelsVolume() StaticVoxelsVolume()
: StaticVoxelsVolume(0, 0, 0) {} : StaticVoxelsVolume(0, 0, 0) {}
@ -142,7 +142,7 @@ public:
return lights; return lights;
} }
inline blockid_t pickBlockId(uint bx, uint by, uint bz) const { blockid_t pickBlockId(uint bx, uint by, uint bz) const {
bx -= x; bx -= x;
by -= y; by -= y;
bz -= z; bz -= z;
@ -153,7 +153,7 @@ public:
} }
inline const voxel& pickBlock(uint bx, uint by, uint bz) const { const voxel& pickBlock(uint bx, uint by, uint bz) const {
bx -= x; bx -= x;
by -= y; by -= y;
bz -= z; bz -= z;
@ -164,7 +164,7 @@ public:
return voxels[vox_index(bx, by, bz, w, d)]; return voxels[vox_index(bx, by, bz, w, d)];
} }
inline light_t pickLight(uint bx, uint by, uint bz) const { light_t pickLight(uint bx, uint by, uint bz) const {
bx -= x; bx -= x;
by -= y; by -= y;
bz -= z; bz -= z;

View file

@ -71,12 +71,12 @@ Level::Level(
Level::~Level() = default; Level::~Level() = default;
World* Level::getWorld() { World& Level::getWorld() {
return world.get(); return *world;
} }
const World* Level::getWorld() const { const World& Level::getWorld() const {
return world.get(); return *world;
} }
void Level::onSave() { void Level::onSave() {

View file

@ -46,9 +46,9 @@ public:
); );
~Level(); ~Level();
World* getWorld(); World& getWorld();
const World* getWorld() const; const World& getWorld() const;
void onSave(); void onSave();

View file

@ -147,7 +147,7 @@ std::unique_ptr<Level> World::load(
if (!playerRoot["players"].empty()) { if (!playerRoot["players"].empty()) {
if (!playerRoot["players"][0].has("id")) { if (!playerRoot["players"][0].has("id")) {
level->getWorld()->getInfo().nextPlayerId++; level->getWorld().getInfo().nextPlayerId++;
} }
} }
} }
@ -216,9 +216,6 @@ void WorldInfo::deserialize(const dv::value& root) {
daytimeSpeed = timeobj["day-time-speed"].asNumber(); daytimeSpeed = timeobj["day-time-speed"].asNumber();
totalTime = timeobj["total-time"].asNumber(); totalTime = timeobj["total-time"].asNumber();
} }
if (root.has("weather")) {
fog = root["weather"]["fog"].asNumber();
}
nextInventoryId = root["next-inventory-id"].asInteger(2); nextInventoryId = root["next-inventory-id"].asInteger(2);
nextEntityId = root["next-entity-id"].asInteger(1); nextEntityId = root["next-entity-id"].asInteger(1);
root.at("next-player-id").get(nextPlayerId); root.at("next-player-id").get(nextPlayerId);
@ -244,9 +241,6 @@ dv::value WorldInfo::serialize() const {
timeobj["day-time-speed"] = daytimeSpeed; timeobj["day-time-speed"] = daytimeSpeed;
timeobj["total-time"] = totalTime; timeobj["total-time"] = totalTime;
root["weather"] = dv::object();
root["weather"]["fog"] = fog;
root["next-inventory-id"] = nextInventoryId; root["next-inventory-id"] = nextInventoryId;
root["next-entity-id"] = nextEntityId; root["next-entity-id"] = nextEntityId;
root["next-player-id"] = nextPlayerId; root["next-player-id"] = nextPlayerId;

View file

@ -39,9 +39,6 @@ struct WorldInfo : public Serializable {
/// @brief total time passed in the world (not depending on daytimeSpeed) /// @brief total time passed in the world (not depending on daytimeSpeed)
double totalTime = 0.0; double totalTime = 0.0;
/// @brief will be replaced with weather in future
float fog = 0.0f;
entityid_t nextEntityId = 0; entityid_t nextEntityId = 0;
int major = 0, minor = -1; int major = 0, minor = -1;
@ -70,6 +67,8 @@ public:
const std::vector<ContentPack>& packs const std::vector<ContentPack>& packs
); );
World(const World&) = delete;
~World(); ~World();
/// @brief Update world day-time and total time /// @brief Update world day-time and total time