VoxelEngine/src/graphics/render/WorldRenderer.cpp
2025-07-18 20:27:38 +03:00

628 lines
21 KiB
C++

#include "WorldRenderer.hpp"
#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/Engine.hpp"
#include "coders/GLSLExtension.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/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/Framebuffer.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/core/Font.hpp"
#include "graphics/core/ShadowMap.hpp"
#include "graphics/core/GBuffer.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"
using namespace advanced_pipeline;
inline constexpr size_t BATCH3D_CAPACITY = 4096;
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0;
inline constexpr int MIN_SHADOW_MAP_RES = 512;
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<Frustum>()),
lineBatch(std::make_unique<LineBatch>()),
batch3d(std::make_unique<Batch3D>(BATCH3D_CAPACITY)),
modelBatch(std::make_unique<ModelBatch>(
MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings()
)),
guides(std::make_unique<GuidesRenderer>()),
chunks(std::make_unique<ChunksRenderer>(
&level,
*player.chunks,
assets,
*frustumCulling,
frontend.getContentGfxCache(),
engine.getSettings()
)),
particles(std::make_unique<ParticlesRenderer>(
assets, level, *player.chunks, &engine.getSettings().graphics
)),
texts(std::make_unique<TextsRenderer>(*batch3d, assets, *frustumCulling)),
blockWraps(
std::make_unique<BlockWrapsRenderer>(assets, level, *player.chunks)
),
precipitation(std::make_unique<PrecipitationRenderer>(
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<Skybox>(
settings.graphics.skyboxResolution.get(),
assets->require<Shader>("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.uniform1i("u_debugLights", lightsDebug);
shader.uniform1i("u_debugNormals", false);
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_skybox", 1);
shader.uniform1i("u_enableShadows", shadows);
if (shadows) {
const auto& worldInfo = level.getWorld()->getInfo();
float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds());
shader.uniform1i("u_screen", 0);
shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView());
shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView());
shader.uniform3f("u_sunDir", shadowCamera.front);
shader.uniform1i("u_shadowsRes", shadowMap->getResolution());
shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable
shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0);
shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0);
glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap());
glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1);
shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1);
glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap());
glActiveTexture(TEXTURE_MAIN);
}
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.75f;
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", 8.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<float>(settings.chunks.loadDistance.get() - 2);
auto& entityShader = assets.require<Shader>("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<Shader>("main");
auto& linesShader = assets.require<Shader>("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();
}
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.01), glm::vec4(0.f, 0.f, 0.f, 1.0f)
);
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<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 = 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<float>(delta * 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;
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<model::Model>(def.modelName),
nullptr
);
display::clearDepth();
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
skybox->bind();
modelBatch->render();
modelBatch->setLightsOffset(glm::vec3());
skybox->unbind();
}
void WorldRenderer::generateShadowsMap(
const Camera& camera,
const DrawContext& pctx,
ShadowMap& shadowMap,
Camera& shadowCamera,
float scale
) {
auto& shadowsShader = assets.require<Shader>("shadows");
auto world = level.getWorld();
const auto& worldInfo = world->getInfo();
const auto& settings = engine.getSettings();
int resolution = shadowMap.getResolution();
int quality = settings.graphics.shadowsQuality.get();
float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale;
float shadowMapSize = resolution * shadowMapScale;
glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f;
glm::vec3 prevPos = shadowCamera.position;
shadowCamera = Camera(
glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos,
shadowMapSize
);
shadowCamera.near = 0.1f;
shadowCamera.far = 1000.0f;
shadowCamera.perspective = false;
shadowCamera.setAspectRatio(1.0f);
float t = worldInfo.daytime - 0.25f;
if (t < 0.0f) {
t += 1.0f;
}
t = fmod(t, 0.5f);
float sunCycleStep = 1.0f / 500.0f;
float sunAngle = glm::radians(
90.0f -
((static_cast<int>(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f
);
float sunAltitude = glm::pi<float>() * 0.25f;
shadowCamera.rotate(
-glm::cos(sunAngle + glm::pi<float>() * 0.5f) * sunAltitude,
sunAngle - glm::pi<float>() * 0.5f,
glm::radians(0.0f)
);
shadowCamera.position -= shadowCamera.front * 500.0f;
shadowCamera.position += shadowCamera.up * 0.0f;
shadowCamera.position += camera.front * 0.0f;
auto view = shadowCamera.getView();
auto currentPos = shadowCamera.position;
auto topRight = shadowCamera.right + shadowCamera.up;
auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f);
auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f);
shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f));
{
frustumCulling->update(shadowCamera.getProjView());
auto sctx = pctx.sub();
sctx.setDepthTest(true);
sctx.setCullFace(true);
sctx.setViewport({resolution, resolution});
shadowMap.bind();
setupWorldShader(shadowsShader, shadowCamera, settings, 0.0f);
chunks->drawChunksShadowsPass(shadowCamera, shadowsShader);
shadowMap.unbind();
}
}
void WorldRenderer::draw(
const DrawContext& pctx,
Camera& camera,
bool hudVisible,
bool pause,
float uiDelta,
PostProcessing& postProcessing
) {
// TODO: REFACTOR WHOLE RENDER ENGINE
float delta = uiDelta * !pause;
timer += delta;
weather.update(delta);
auto world = level.getWorld();
const auto& vp = pctx.getViewport();
camera.setAspectRatio(vp.x / static_cast<float>(vp.y));
auto& mainShader = assets.require<Shader>("main");
auto& entityShader = assets.require<Shader>("entity");
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader();
const auto& settings = engine.getSettings();
gbufferPipeline = settings.graphics.advancedRender.get();
int shadowsQuality = settings.graphics.shadowsQuality.get() * gbufferPipeline;
int resolution = MIN_SHADOW_MAP_RES << shadowsQuality;
if (shadowsQuality > 0 && !shadows) {
shadowMap = std::make_unique<ShadowMap>(resolution);
wideShadowMap = std::make_unique<ShadowMap>(resolution);
shadows = true;
} else if (shadowsQuality == 0 && shadows) {
shadowMap.reset();
wideShadowMap.reset();
shadows = false;
}
CompileTimeShaderSettings currentSettings {
gbufferPipeline,
shadows,
settings.graphics.ssao.get() && gbufferPipeline
};
if (
prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
prevCTShaderSettings.shadows != currentSettings.shadows ||
prevCTShaderSettings.ssao != currentSettings.ssao
) {
Shader::preprocessor->setDefined("ENABLE_SHADOWS", currentSettings.shadows);
Shader::preprocessor->setDefined("ENABLE_SSAO", currentSettings.ssao);
Shader::preprocessor->setDefined("ADVANCED_RENDER", currentSettings.advancedRender);
mainShader.recompile();
entityShader.recompile();
deferredShader.recompile();
prevCTShaderSettings = currentSettings;
}
if (shadows && shadowMap->getResolution() != resolution) {
shadowMap = std::make_unique<ShadowMap>(resolution);
wideShadowMap = std::make_unique<ShadowMap>(resolution);
}
const auto& worldInfo = world->getInfo();
float clouds = weather.clouds();
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);
chunks->update();
static int frameid = 0;
if (shadows) {
if (frameid % 2 == 0) {
generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f);
} else {
generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f);
}
}
frameid++;
auto& linesShader = assets.require<Shader>("lines");
/* World render scope with diegetic HUD included */ {
DrawContext wctx = pctx.sub();
postProcessing.use(wctx, gbufferPipeline);
display::clearDepth();
/* 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
);
}
}
}
texts->render(pctx, camera, settings, hudVisible, true);
}
skybox->bind();
float fogFactor =
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
if (gbufferPipeline) {
deferredShader.use();
setupWorldShader(deferredShader, camera, settings, fogFactor);
postProcessing.renderDeferredShading(pctx, assets, timer, camera);
}
{
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
if (gbufferPipeline) {
postProcessing.bindDepthBuffer();
} else {
postProcessing.getFramebuffer()->bind();
}
// Drawing background sky plane
skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds);
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, pause ? 0.0f : delta, *weather);
}
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
postProcessing.render(pctx, assets, timer, camera);
skybox->unbind();
if (player.currentCamera == player.fpCamera) {
DrawContext ctx = pctx.sub();
ctx.setDepthTest(true);
ctx.setCullFace(true);
renderHands(camera, delta);
}
renderBlockOverlay(pctx);
glActiveTexture(GL_TEXTURE0);
}
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<Shader>("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;
}
void WorldRenderer::toggleLightsDebug() {
lightsDebug = !lightsDebug;
}
Weather& WorldRenderer::getWeather() {
return weather;
}