#include "WorldRenderer.hpp" #include #include #include #include #include #include #include "assets/Assets.hpp" #include "assets/assets_util.hpp" #include "content/Content.hpp" #include "engine/Engine.hpp" #include "frontend/LevelFrontend.hpp" #include "frontend/ContentGfxCache.hpp" #include "items/Inventory.hpp" #include "items/ItemDef.hpp" #include "items/ItemStack.hpp" #include "logic/PlayerController.hpp" #include "logic/scripting/scripting_hud.hpp" #include "maths/FrustumCulling.hpp" #include "maths/voxmaths.hpp" #include "objects/Entities.hpp" #include "objects/Player.hpp" #include "settings.hpp" #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" #include "voxels/Chunks.hpp" #include "window/Camera.hpp" #include "window/Window.hpp" #include "world/Level.hpp" #include "world/LevelEvents.hpp" #include "world/World.hpp" #include "graphics/commons/Model.hpp" #include "graphics/core/Atlas.hpp" #include "graphics/core/Batch3D.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/LineBatch.hpp" #include "graphics/core/Mesh.hpp" #include "graphics/core/PostProcessing.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/Texture.hpp" #include "graphics/core/Font.hpp" #include "BlockWrapsRenderer.hpp" #include "ParticlesRenderer.hpp" #include "PrecipitationRenderer.hpp" #include "TextsRenderer.hpp" #include "ChunksRenderer.hpp" #include "GuidesRenderer.hpp" #include "ModelBatch.hpp" #include "Skybox.hpp" #include "Emitter.hpp" #include "TextNote.hpp" inline constexpr size_t BATCH3D_CAPACITY = 4096; inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000; bool WorldRenderer::showChunkBorders = false; bool WorldRenderer::showEntitiesDebug = false; WorldRenderer::WorldRenderer( Engine& engine, LevelFrontend& frontend, Player& player ) : engine(engine), level(frontend.getLevel()), player(player), assets(*engine.getAssets()), frustumCulling(std::make_unique()), lineBatch(std::make_unique()), batch3d(std::make_unique(BATCH3D_CAPACITY)), modelBatch(std::make_unique( MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings() )), guides(std::make_unique()), chunks(std::make_unique( &level, *player.chunks, assets, *frustumCulling, frontend.getContentGfxCache(), engine.getSettings() )), particles(std::make_unique( assets, level, *player.chunks, &engine.getSettings().graphics )), texts(std::make_unique(*batch3d, assets, *frustumCulling)), blockWraps( std::make_unique(assets, level, *player.chunks) ), precipitation(std::make_unique( assets, level, *player.chunks, &engine.getSettings().graphics )) { auto& settings = engine.getSettings(); level.events->listen( LevelEventType::CHUNK_HIDDEN, [this](LevelEventType, Chunk* chunk) { chunks->unload(chunk); } ); auto assets = engine.getAssets(); skybox = std::make_unique( settings.graphics.skyboxResolution.get(), assets->require("skybox_gen") ); } WorldRenderer::~WorldRenderer() = default; void WorldRenderer::setupWorldShader( Shader& shader, const Camera& camera, const EngineSettings& settings, float fogFactor ) { shader.use(); shader.uniformMatrix("u_model", glm::mat4(1.0f)); shader.uniformMatrix("u_proj", camera.getProjection()); shader.uniformMatrix("u_view", camera.getView()); shader.uniform1f("u_timer", timer); shader.uniform1f("u_gamma", settings.graphics.gamma.get()); shader.uniform1f("u_fogFactor", fogFactor); shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity()); shader.uniform1f("u_weatherFogDencity", weather.fogDencity()); shader.uniform1f("u_weatherFogCurve", weather.fogCurve()); shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime); shader.uniform2f("u_lightDir", skybox->getLightDir()); shader.uniform3f("u_cameraPos", camera.position); shader.uniform1i("u_cubemap", 1); auto indices = level.content.getIndices(); // Light emission when an emissive item is chosen { auto inventory = player.getInventory(); ItemStack& stack = inventory->getSlot(player.getChosenSlot()); auto& item = indices->items.require(stack.getItemId()); float multiplier = 0.5f; shader.uniform3f( "u_torchlightColor", item.emission[0] / 15.0f * multiplier, item.emission[1] / 15.0f * multiplier, item.emission[2] / 15.0f * multiplier ); shader.uniform1f("u_torchlightDistance", 6.0f); } } void WorldRenderer::renderLevel( const DrawContext& ctx, const Camera& camera, const EngineSettings& settings, float delta, bool pause, bool hudVisible ) { texts->render(ctx, camera, settings, hudVisible, false); bool culling = engine.getSettings().graphics.frustumCulling.get(); float fogFactor = 15.0f / static_cast(settings.chunks.loadDistance.get() - 2); auto& entityShader = assets.require("entity"); setupWorldShader(entityShader, camera, settings, fogFactor); skybox->bind(); if (culling) { frustumCulling->update(camera.getProjView()); } entityShader.uniform1i("u_alphaClip", true); entityShader.uniform1f("u_opacity", 1.0f); level.entities->render( assets, *modelBatch, culling ? frustumCulling.get() : nullptr, delta, pause ); modelBatch->render(); particles->render(camera, delta * !pause); auto& shader = assets.require("main"); auto& linesShader = assets.require("lines"); setupWorldShader(shader, camera, settings, fogFactor); chunks->drawChunks(camera, shader); blockWraps->draw(ctx, player); if (hudVisible) { renderLines(camera, linesShader, ctx); } shader.use(); chunks->drawSortedMeshes(camera, shader); if (!pause) { scripting::on_frontend_render(); } setupWorldShader(entityShader, camera, settings, fogFactor); std::array 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, pause ? 0.0f : delta, *weather); } } skybox->unbind(); } void WorldRenderer::renderBlockSelection() { const auto& selection = player.selection; auto indices = level.content.getIndices(); blockid_t id = selection.vox.id; auto& block = indices->blocks.require(id); const glm::ivec3 pos = player.selection.position; const glm::vec3 point = selection.hitPosition; const glm::vec3 norm = selection.normal; const std::vector& hitboxes = block.rotatable ? block.rt.hitboxes[selection.vox.state.rotation] : block.hitboxes; lineBatch->lineWidth(2.0f); for (auto& hitbox : hitboxes) { const glm::vec3 center = glm::vec3(pos) + hitbox.center(); const glm::vec3 size = hitbox.size(); lineBatch->box( center, size + glm::vec3(0.01), glm::vec4(0.f, 0.f, 0.f, 0.5f) ); if (debug) { lineBatch->line( point, point + norm * 0.5f, glm::vec4(1.0f, 0.0f, 1.0f, 1.0f) ); } } lineBatch->flush(); } void WorldRenderer::renderLines( const Camera& camera, Shader& linesShader, const DrawContext& pctx ) { linesShader.use(); linesShader.uniformMatrix("u_projview", camera.getProjView()); if (player.selection.vox.id != BLOCK_VOID) { renderBlockSelection(); } if (debug && showEntitiesDebug) { auto ctx = pctx.sub(lineBatch.get()); bool culling = engine.getSettings().graphics.frustumCulling.get(); level.entities->renderDebug( *lineBatch, culling ? frustumCulling.get() : nullptr, ctx ); } } void WorldRenderer::renderHands( const Camera& camera, float delta ) { auto& entityShader = assets.require("entity"); auto indices = level.content.getIndices(); // get current chosen item const auto& inventory = player.getInventory(); int slot = player.getChosenSlot(); const ItemStack& stack = inventory->getSlot(slot); const auto& def = indices->items.require(stack.getItemId()); // prepare modified HUD camera Camera hudcam = camera; hudcam.far = 10.0f; hudcam.setFov(0.9f); hudcam.position = {}; // configure model matrix const glm::vec3 itemOffset(0.06f, 0.035f, -0.1); static glm::mat4 prevRotation(1.0f); const float speed = 24.0f; glm::mat4 matrix = glm::translate(glm::mat4(1.0f), itemOffset); matrix = glm::scale(matrix, glm::vec3(0.1f)); glm::mat4 rotation = camera.rotation; glm::quat rot0 = glm::quat_cast(prevRotation); glm::quat rot1 = glm::quat_cast(rotation); glm::quat finalRot = glm::slerp(rot0, rot1, static_cast(delta * speed)); rotation = glm::mat4_cast(finalRot); matrix = rotation * matrix * glm::rotate( glm::mat4(1.0f), -glm::pi() * 0.5f, glm::vec3(0, 1, 0) ); prevRotation = rotation; glm::vec3 cameraRotation = player.getRotation(); auto offset = -(camera.position - player.getPosition()); float angle = glm::radians(cameraRotation.x - 90); float cos = glm::cos(angle); float sin = glm::sin(angle); float newX = offset.x * cos - offset.z * sin; float newZ = offset.x * sin + offset.z * cos; offset = glm::vec3(newX, offset.y, newZ); matrix = matrix * glm::translate(glm::mat4(1.0f), offset); // render modelBatch->setLightsOffset(camera.position); modelBatch->draw( matrix, glm::vec3(1.0f), assets.get(def.modelName), nullptr ); display::clearDepth(); setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f); skybox->bind(); modelBatch->render(); modelBatch->setLightsOffset(glm::vec3()); skybox->unbind(); } void WorldRenderer::draw( const DrawContext& pctx, Camera& camera, bool hudVisible, bool pause, float uiDelta, PostProcessing& postProcessing ) { float delta = uiDelta * !pause; timer += delta; weather.update(delta); auto world = level.getWorld(); const auto& vp = pctx.getViewport(); camera.setAspectRatio(vp.x / static_cast(vp.y)); const auto& settings = engine.getSettings(); const auto& worldInfo = world->getInfo(); float sqrtT = glm::sqrt(weather.t); float clouds = weather.b.clouds * sqrtT + weather.a.clouds * (1.0f - sqrtT); 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); const auto& assets = *engine.getAssets(); auto& linesShader = assets.require("lines"); /* World render scope with diegetic HUD included */ { DrawContext wctx = pctx.sub(); postProcessing.use(wctx); display::clearDepth(); // Drawing background sky plane skybox->draw(pctx, camera, assets, worldInfo.daytime, clouds); /* Actually world render with depth buffer on */ { DrawContext ctx = wctx.sub(); ctx.setDepthTest(true); ctx.setCullFace(true); renderLevel(ctx, camera, settings, uiDelta, pause, hudVisible); // Debug lines if (hudVisible) { if (debug) { guides->renderDebugLines( ctx, camera, *lineBatch, linesShader, showChunkBorders ); } if (player.currentCamera == player.fpCamera) { renderHands(camera, delta); } } } { DrawContext ctx = wctx.sub(); texts->render(ctx, camera, settings, hudVisible, true); } renderBlockOverlay(wctx); } postProcessing.render(pctx, assets, timer); } void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { int x = std::floor(player.currentCamera->position.x); 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(); } } void WorldRenderer::clear() { chunks->clear(); } void WorldRenderer::setDebug(bool flag) { debug = flag; } Weather& WorldRenderer::getWeather() { return weather; }