#include "Shadows.hpp" #include #define GLM_ENABLE_EXPERIMENTAL #include #include #include "glm/gtc/matrix_transform.hpp" #include "assets/Assets.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/commons.hpp" #include "world/Level.hpp" #include "world/Weather.hpp" #include "world/World.hpp" using namespace advanced_pipeline; inline constexpr int MIN_SHADOW_MAP_RES = 512; inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0; class ShadowMap { public: ShadowMap(int resolution) : resolution(resolution) { glGenTextures(1, &depthMap); glBindTexture(GL_TEXTURE_2D, depthMap); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE ); float border[4] {1.0f, 1.0f, 1.0f, 1.0f}; glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border); glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0 ); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); glBindFramebuffer(GL_FRAMEBUFFER, 0); } ~ShadowMap() { glDeleteFramebuffers(1, &fbo); glDeleteTextures(1, &depthMap); } void bind(){ glBindFramebuffer(GL_FRAMEBUFFER, fbo); glClear(GL_DEPTH_BUFFER_BIT); } void unbind() { glBindFramebuffer(GL_FRAMEBUFFER, 0); } uint getDepthMap() const { return depthMap; } int getResolution() const { return resolution; } private: uint fbo; uint depthMap; int resolution; }; Shadows::Shadows(const Level& level) : level(level) {} Shadows::~Shadows() = default; void Shadows::setQuality(int quality) { int resolution = MIN_SHADOW_MAP_RES << quality; if (quality > 0 && !shadows) { shadowMap = std::make_unique(resolution); wideShadowMap = std::make_unique(resolution); shadows = true; } else if (quality == 0 && shadows) { shadowMap.reset(); wideShadowMap.reset(); shadows = false; } if (shadows && shadowMap->getResolution() != resolution) { shadowMap = std::make_unique(resolution); wideShadowMap = std::make_unique(resolution); } this->quality = quality; } void Shadows::setup(Shader& shader, const Weather& weather) { if (shadows) { const auto& worldInfo = level.getWorld()->getInfo(); float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds()); float shadowsOpacity = 1.0f - cloudsIntensity; shadowsOpacity *= glm::sqrt(glm::abs( glm::mod((worldInfo.daytime + 0.5f) * 2.0f, 1.0f) * 2.0f - 1.0f )); 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", shadowsOpacity); // 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); } } void Shadows::refresh(const Camera& camera, const DrawContext& pctx, std::function renderShadowPass) { static int frameid = 0; if (shadows) { if (frameid % 2 == 0) { generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f, renderShadowPass); } else { generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f, renderShadowPass); } } frameid++; } void Shadows::generateShadowsMap( const Camera& camera, const DrawContext& pctx, ShadowMap& shadowMap, Camera& shadowCamera, float scale, std::function renderShadowPass ) { auto world = level.getWorld(); const auto& worldInfo = world->getInfo(); int resolution = shadowMap.getResolution(); 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(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f ); float sunAltitude = glm::pi() * 0.25f; shadowCamera.rotate( -glm::cos(sunAngle + glm::pi() * 0.5f) * sunAltitude, sunAngle - glm::pi() * 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)); { auto sctx = pctx.sub(); sctx.setDepthTest(true); sctx.setCullFace(true); sctx.setViewport({resolution, resolution}); shadowMap.bind(); if (renderShadowPass) { renderShadowPass(shadowCamera); } shadowMap.unbind(); } }