530 lines
17 KiB
C++
530 lines
17 KiB
C++
#include "WorldRenderer.hpp"
|
|
|
|
#include <GL/glew.h>
|
|
#include <assert.h>
|
|
|
|
#include <algorithm>
|
|
#include <glm/ext.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <memory>
|
|
|
|
#include "assets/Assets.hpp"
|
|
#include "assets/assets_util.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 "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/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/Model.hpp"
|
|
#include "graphics/core/PostProcessing.hpp"
|
|
#include "graphics/core/Shader.hpp"
|
|
#include "graphics/core/Texture.hpp"
|
|
#include "ChunksRenderer.hpp"
|
|
#include "ModelBatch.hpp"
|
|
#include "Skybox.hpp"
|
|
|
|
bool WorldRenderer::showChunkBorders = false;
|
|
bool WorldRenderer::showEntitiesDebug = false;
|
|
|
|
WorldRenderer::WorldRenderer(
|
|
Engine* engine, LevelFrontend* frontend, Player* player
|
|
)
|
|
: engine(engine),
|
|
level(frontend->getLevel()),
|
|
player(player),
|
|
frustumCulling(std::make_unique<Frustum>()),
|
|
lineBatch(std::make_unique<LineBatch>()),
|
|
modelBatch(std::make_unique<ModelBatch>(
|
|
20'000, engine->getAssets(), level->chunks.get(),
|
|
&engine->getSettings()
|
|
)) {
|
|
renderer = std::make_unique<ChunksRenderer>(
|
|
level, frontend->getContentGfxCache(), &engine->getSettings()
|
|
);
|
|
batch3d = std::make_unique<Batch3D>(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<Skybox>(
|
|
settings.graphics.skyboxResolution.get(),
|
|
assets->get<Shader>("skybox_gen")
|
|
);
|
|
}
|
|
|
|
WorldRenderer::~WorldRenderer() = default;
|
|
|
|
bool WorldRenderer::drawChunk(
|
|
size_t index, const Camera& camera, Shader* shader, bool culling
|
|
) {
|
|
auto chunk = level->chunks->getChunks()[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, const Camera& camera, Shader* shader
|
|
) {
|
|
auto assets = engine->getAssets();
|
|
auto atlas = assets->get<Atlas>("blocks");
|
|
|
|
atlas->getTexture()->bind();
|
|
renderer->update();
|
|
|
|
// [warning] this whole method is not thread-safe for chunks
|
|
|
|
std::vector<size_t> indices;
|
|
for (size_t i = 0; i < chunks->getVolume(); i++) {
|
|
if (chunks->getChunks()[i] == nullptr) continue;
|
|
indices.emplace_back(i);
|
|
}
|
|
float px = camera.position.x / static_cast<float>(CHUNK_W) - 0.5f;
|
|
float pz = camera.position.z / static_cast<float>(CHUNK_D) - 0.5f;
|
|
std::sort(indices.begin(), indices.end(), [chunks, px, pz](auto i, auto j) {
|
|
const auto& chunksBuffer = chunks->getChunks();
|
|
const auto a = chunksBuffer[i].get();
|
|
const auto b = chunksBuffer[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::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_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&,
|
|
const Camera& camera,
|
|
const EngineSettings& settings,
|
|
float delta,
|
|
bool pause
|
|
) {
|
|
auto assets = engine->getAssets();
|
|
|
|
bool culling = engine->getSettings().graphics.frustumCulling.get();
|
|
float fogFactor = 15.0f / ((float)settings.chunks.loadDistance.get() - 2);
|
|
|
|
auto entityShader = assets->get<Shader>("entity");
|
|
setupWorldShader(entityShader, camera, settings, fogFactor);
|
|
skybox->bind();
|
|
|
|
level->entities->render(
|
|
assets,
|
|
*modelBatch,
|
|
culling ? frustumCulling.get() : nullptr,
|
|
delta,
|
|
pause
|
|
);
|
|
modelBatch->render();
|
|
|
|
auto shader = assets->get<Shader>("main");
|
|
setupWorldShader(shader, camera, settings, fogFactor);
|
|
|
|
drawChunks(level->chunks.get(), camera, shader);
|
|
|
|
if (!pause) {
|
|
scripting::on_frontend_render();
|
|
}
|
|
|
|
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<AABB>& 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.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->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 (player->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::renderDebugLines(
|
|
const DrawContext& pctx, const Camera& camera, Shader* linesShader
|
|
) {
|
|
DrawContext ctx = pctx.sub(lineBatch.get());
|
|
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->fpCamera->position;
|
|
if (coord.x < 0) coord.x--;
|
|
if (coord.z < 0) coord.z--;
|
|
int cx = floordiv(static_cast<int>(coord.x), CHUNK_W);
|
|
int cz = floordiv(static_cast<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,
|
|
static_cast<float>(displayWidth),
|
|
0.f,
|
|
static_cast<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->flush();
|
|
|
|
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);
|
|
}
|
|
|
|
void WorldRenderer::renderHands(const Camera& camera, const Assets& assets) {
|
|
auto entityShader = assets.get<Shader>("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 = 100.0f;
|
|
hudcam.setFov(1.2f);
|
|
hudcam.position = {};
|
|
|
|
// configure model matrix
|
|
const glm::vec3 itemOffset(0.08f, 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<float>(engine->getDelta() * speed));
|
|
rotation = glm::mat4_cast(finalRot);
|
|
matrix = rotation * matrix *
|
|
glm::rotate(
|
|
glm::mat4(1.0f), -glm::pi<float>() * 0.5f, glm::vec3(0, 1, 0)
|
|
);
|
|
prevRotation = rotation;
|
|
auto offset = -(camera.position - player->getPosition());
|
|
float angle = glm::radians(player->cam.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<model::Model>(def.modelName),
|
|
nullptr
|
|
);
|
|
Window::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 delta,
|
|
PostProcessing* postProcessing
|
|
) {
|
|
timer += delta * !pause;
|
|
auto world = level->getWorld();
|
|
const Viewport& vp = pctx.getViewport();
|
|
camera.aspect = vp.getWidth() / static_cast<float>(vp.getHeight());
|
|
|
|
const auto& settings = engine->getSettings();
|
|
const auto& worldInfo = world->getInfo();
|
|
|
|
skybox->refresh(pctx, worldInfo.daytime, 1.0f + worldInfo.fog * 2.0f, 4);
|
|
|
|
const auto& assets = *engine->getAssets();
|
|
auto linesShader = assets.get<Shader>("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, worldInfo.daytime, worldInfo.fog);
|
|
|
|
// Actually world render with depth buffer on
|
|
{
|
|
DrawContext ctx = wctx.sub();
|
|
ctx.setDepthTest(true);
|
|
ctx.setCullFace(true);
|
|
renderLevel(ctx, camera, settings, delta, pause);
|
|
// Debug lines
|
|
if (hudVisible) {
|
|
renderLines(camera, linesShader, ctx);
|
|
if (player->currentCamera == player->fpCamera) {
|
|
renderHands(camera, assets);
|
|
}
|
|
}
|
|
}
|
|
if (hudVisible && player->debug) {
|
|
renderDebugLines(wctx, camera, linesShader);
|
|
}
|
|
renderBlockOverlay(wctx, assets);
|
|
}
|
|
|
|
// Rendering fullscreen quad with
|
|
auto screenShader = assets.get<Shader>("screen");
|
|
screenShader->use();
|
|
screenShader->uniform1f("u_timer", timer);
|
|
screenShader->uniform1f("u_dayTime", worldInfo.daytime);
|
|
postProcessing->render(pctx, screenShader);
|
|
}
|
|
|
|
void WorldRenderer::renderBlockOverlay(const DrawContext& wctx, const Assets& assets) {
|
|
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 = level->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<Shader>("ui3d");
|
|
shader.use();
|
|
batch3d->begin();
|
|
shader.uniformMatrix("u_projview", glm::mat4(1.0f));
|
|
shader.uniformMatrix("u_apply", glm::mat4(1.0f));
|
|
auto light = level->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::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->flush();
|
|
}
|
|
|
|
void WorldRenderer::clear() {
|
|
renderer->clear();
|
|
}
|