206 lines
6.9 KiB
C++
206 lines
6.9 KiB
C++
#include "Shadows.hpp"
|
|
|
|
#include <GL/glew.h>
|
|
#define GLM_ENABLE_EXPERIMENTAL
|
|
#include <glm/gtx/norm.hpp>
|
|
#include <glm/gtc/constants.hpp>
|
|
#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<ShadowMap>(resolution);
|
|
wideShadowMap = std::make_unique<ShadowMap>(resolution);
|
|
shadows = true;
|
|
} else if (quality == 0 && shadows) {
|
|
shadowMap.reset();
|
|
wideShadowMap.reset();
|
|
shadows = false;
|
|
}
|
|
if (shadows && shadowMap->getResolution() != resolution) {
|
|
shadowMap = std::make_unique<ShadowMap>(resolution);
|
|
wideShadowMap = std::make_unique<ShadowMap>(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<void(Camera&)> 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<void(Camera&)> 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<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));
|
|
|
|
{
|
|
auto sctx = pctx.sub();
|
|
sctx.setDepthTest(true);
|
|
sctx.setCullFace(true);
|
|
sctx.setViewport({resolution, resolution});
|
|
shadowMap.bind();
|
|
if (renderShadowPass) {
|
|
renderShadowPass(shadowCamera);
|
|
}
|
|
shadowMap.unbind();
|
|
}
|
|
}
|