#include "WorldRenderer.hpp" #include "ChunksRenderer.hpp" #include "ModelBatch.hpp" #include "Skybox.hpp" #include "../../assets/Assets.hpp" #include "../../content/Content.hpp" #include "../../engine.hpp" #include "../../frontend/LevelFrontend.hpp" #include "../../items/Inventory.hpp" #include "../../items/ItemDef.hpp" #include "../../items/ItemStack.hpp" #include "../../logic/PlayerController.hpp" #include "../../maths/FrustumCulling.hpp" #include "../../maths/voxmaths.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 "../core/Atlas.hpp" #include "../core/Batch3D.hpp" #include "../core/DrawContext.hpp" #include "../core/LineBatch.hpp" #include "../core/Mesh.hpp" #include "../core/PostProcessing.hpp" #include "../core/Shader.hpp" #include "../core/Texture.hpp" #include "../core/Model.hpp" #include #include #include #include #include #include bool WorldRenderer::showChunkBorders = false; WorldRenderer::WorldRenderer(Engine* engine, LevelFrontend* frontend, Player* player) : engine(engine), level(frontend->getLevel()), player(player), frustumCulling(std::make_unique()), lineBatch(std::make_unique()), modelBatch(std::make_unique(1000, engine->getAssets(), level->chunks.get())) { renderer = std::make_unique( level, frontend->getContentGfxCache(), &engine->getSettings() ); batch3d = std::make_unique(4096); auto& settings = engine->getSettings(); level->events->listen(EVT_CHUNK_HIDDEN, [this](lvl_event_type, Chunk* chunk) { renderer->unload(chunk); } ); auto assets = engine->getAssets(); skybox = std::make_unique( settings.graphics.skyboxResolution.get(), assets->get("skybox_gen") ); } WorldRenderer::~WorldRenderer() { } bool WorldRenderer::drawChunk( size_t index, Camera* camera, Shader* shader, bool culling ){ auto chunk = level->chunks->chunks[index]; if (!chunk->flags.lighted) { return false; } float distance = glm::distance( camera->position, glm::vec3((chunk->x + 0.5f) * CHUNK_W, camera->position.y, (chunk->z + 0.5f) * CHUNK_D) ); auto mesh = renderer->getOrRender(chunk, distance < CHUNK_W*1.5f); if (mesh == nullptr) { return false; } if (culling){ glm::vec3 min( chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D ); glm::vec3 max( chunk->x * CHUNK_W + CHUNK_W, chunk->top, chunk->z * CHUNK_D + CHUNK_D ); if (!frustumCulling->IsBoxVisible(min, max)) return false; } glm::vec3 coord(chunk->x*CHUNK_W+0.5f, 0.5f, chunk->z*CHUNK_D+0.5f); glm::mat4 model = glm::translate(glm::mat4(1.0f), coord); shader->uniformMatrix("u_model", model); mesh->draw(); return true; } void WorldRenderer::drawChunks(Chunks* chunks, Camera* camera, Shader* shader) { renderer->update(); // [warning] this whole method is not thread-safe for chunks std::vector indices; for (size_t i = 0; i < chunks->volume; i++){ if (chunks->chunks[i] == nullptr) continue; indices.emplace_back(i); } float px = camera->position.x / (float)CHUNK_W - 0.5f; float pz = camera->position.z / (float)CHUNK_D - 0.5f; std::sort(indices.begin(), indices.end(), [chunks, px, pz](auto i, auto j) { const auto a = chunks->chunks[i].get(); const auto b = chunks->chunks[j].get(); auto adx = (a->x - px); auto adz = (a->z - pz); auto bdx = (b->x - px); auto bdz = (b->z - pz); return (adx*adx + adz*adz > bdx*bdx + bdz*bdz); }); bool culling = engine->getSettings().graphics.frustumCulling.get(); if (culling) { frustumCulling->update(camera->getProjView()); } chunks->visible = 0; for (size_t i = 0; i < indices.size(); i++){ chunks->visible += drawChunk(indices[i], camera, shader, culling); } } void WorldRenderer::renderLevel( const DrawContext&, Camera* camera, const EngineSettings& settings ) { auto assets = engine->getAssets(); auto atlas = assets->get("blocks"); auto shader = assets->get("main"); auto indices = level->content->getIndices(); float fogFactor = 15.0f / ((float)settings.chunks.loadDistance.get()-2); // Setting up main shader shader->use(); shader->uniformMatrix("u_proj", camera->getProjection()); shader->uniformMatrix("u_view", camera->getView()); shader->uniform1f("u_timer", Window::time()); shader->uniform1f("u_gamma", settings.graphics.gamma.get()); shader->uniform1f("u_fogFactor", fogFactor); shader->uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); shader->uniform1f("u_dayTime", level->getWorld()->daytime); shader->uniform3f("u_cameraPos", camera->position); shader->uniform1i("u_cubemap", 1); // Light emission when an emissive item is chosen { auto inventory = player->getInventory(); ItemStack& stack = inventory->getSlot(player->getChosenSlot()); auto item = indices->getItemDef(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); } // Binding main shader textures skybox->bind(); atlas->getTexture()->bind(); drawChunks(level->chunks.get(), camera, shader); shader->uniformMatrix("u_model", glm::mat4(1.0f)); /// draw models here skybox->unbind(); } void WorldRenderer::renderBlockSelection(Camera* camera, Shader* linesShader) { const auto& selection = player->selection; auto indices = level->content->getIndices(); blockid_t id = selection.vox.id; auto block = indices->getBlockDef(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; linesShader->use(); linesShader->uniformMatrix("u_projview", camera->getProjView()); 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.02), glm::vec4(0.f, 0.f, 0.f, 0.5f)); if (player->debug) { lineBatch->line(point, point+norm*0.5f, glm::vec4(1.0f, 0.0f, 1.0f, 1.0f)); } } lineBatch->render(); } void WorldRenderer::renderDebugLines( const DrawContext& pctx, Camera* camera, Shader* linesShader ) { DrawContext ctx = pctx.sub(); const auto& viewport = ctx.getViewport(); uint displayWidth = viewport.getWidth(); uint displayHeight = viewport.getHeight(); ctx.setDepthTest(true); linesShader->use(); if (showChunkBorders){ linesShader->uniformMatrix("u_projview", camera->getProjView()); glm::vec3 coord = player->camera->position; if (coord.x < 0) coord.x--; if (coord.z < 0) coord.z--; int cx = floordiv((int)coord.x, CHUNK_W); int cz = floordiv((int)coord.z, CHUNK_D); drawBorders( cx * CHUNK_W, 0, cz * CHUNK_D, (cx + 1) * CHUNK_W, CHUNK_H, (cz + 1) * CHUNK_D ); } float length = 40.f; glm::vec3 tsl(displayWidth/2, displayHeight/2, 0.f); glm::mat4 model(glm::translate(glm::mat4(1.f), tsl)); linesShader->uniformMatrix("u_projview", glm::ortho( 0.f, (float)displayWidth, 0.f, (float)displayHeight, -length, length) * model * glm::inverse(camera->rotation) ); ctx.setDepthTest(false); lineBatch->lineWidth(4.0f); lineBatch->line(0.f, 0.f, 0.f, length, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f); lineBatch->line(0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 0.f, 0.f, 1.f); lineBatch->line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 0.f, 1.f); lineBatch->render(); ctx.setDepthTest(true); lineBatch->lineWidth(2.0f); lineBatch->line(0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 0.f, 0.f, 1.f); lineBatch->line(0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 0.f, 1.f); lineBatch->line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f); lineBatch->render(); } void WorldRenderer::draw( const DrawContext& pctx, Camera* camera, bool hudVisible, PostProcessing* postProcessing ){ auto world = level->getWorld(); const Viewport& vp = pctx.getViewport(); camera->aspect = vp.getWidth() / static_cast(vp.getHeight()); const EngineSettings& settings = engine->getSettings(); skybox->refresh(pctx, world->daytime, 1.0f+world->fog*2.0f, 4); auto assets = engine->getAssets(); auto linesShader = assets->get("lines"); // World render scope with diegetic HUD included { DrawContext wctx = pctx.sub(); postProcessing->use(wctx); Window::clearDepth(); // Drawing background sky plane skybox->draw(pctx, camera, assets, world->daytime, world->fog); // Actually world render with depth buffer on { DrawContext ctx = wctx.sub(); ctx.setDepthTest(true); ctx.setCullFace(true); renderLevel(ctx, camera, settings); // Selected block if (player->selection.vox.id != BLOCK_VOID && hudVisible){ renderBlockSelection(camera, linesShader); } } if (hudVisible && player->debug) { renderDebugLines(wctx, camera, linesShader); } } // Rendering fullscreen quad with auto screenShader = assets->get("screen"); screenShader->use(); screenShader->uniform1f("u_timer", Window::time()); screenShader->uniform1f("u_dayTime", level->getWorld()->daytime); postProcessing->render(pctx, screenShader); } void WorldRenderer::drawBorders(int sx, int sy, int sz, int ex, int ey, int ez) { int ww = ex-sx; int dd = ez-sz; /*corner*/ { lineBatch->line(sx, sy, sz, sx, ey, sz, 0.8f, 0, 0.8f, 1); lineBatch->line(sx, sy, ez, sx, ey, ez, 0.8f, 0, 0.8f, 1); lineBatch->line(ex, sy, sz, ex, ey, sz, 0.8f, 0, 0.8f, 1); lineBatch->line(ex, sy, ez, ex, ey, ez, 0.8f, 0, 0.8f, 1); } for (int i = 2; i < ww; i+=2) { lineBatch->line(sx + i, sy, sz, sx + i, ey, sz, 0, 0, 0.8f, 1); lineBatch->line(sx + i, sy, ez, sx + i, ey, ez, 0, 0, 0.8f, 1); } for (int i = 2; i < dd; i+=2) { lineBatch->line(sx, sy, sz + i, sx, ey, sz + i, 0.8f, 0, 0, 1); lineBatch->line(ex, sy, sz + i, ex, ey, sz + i, 0.8f, 0, 0, 1); } for (int i = sy; i < ey; i+=2){ lineBatch->line(sx, i, sz, sx, i, ez, 0, 0.8f, 0, 1); lineBatch->line(sx, i, ez, ex, i, ez, 0, 0.8f, 0, 1); lineBatch->line(ex, i, ez, ex, i, sz, 0, 0.8f, 0, 1); lineBatch->line(ex, i, sz, sx, i, sz, 0, 0.8f, 0, 1); } lineBatch->render(); }