Files
VoxelEngine/src/graphics/core/PostProcessing.cpp
T

251 lines
6.7 KiB
C++

#include "PostProcessing.hpp"
#include "Mesh.hpp"
#include "Shader.hpp"
#include "GBuffer.hpp"
#include "Texture.hpp"
#include "Framebuffer.hpp"
#include "DrawContext.hpp"
#include "PostEffect.hpp"
#include "assets/Assets.hpp"
#include "window/Camera.hpp"
#include <stdexcept>
#include <random>
// TODO: REFACTOR WHOLE RENDER ENGINE
using namespace advanced_pipeline;
PostProcessing::PostProcessing(size_t effectSlotsCount)
: effectSlots(effectSlotsCount) {
// Fullscreen quad mesh bulding
PostProcessingVertex meshData[] {
{{-1.0f, -1.0f}},
{{-1.0f, 1.0f}},
{{1.0f, 1.0f}},
{{-1.0f, -1.0f}},
{{1.0f, 1.0f}},
{{1.0f, -1.0f}},
};
quadMesh = std::make_unique<Mesh<PostProcessingVertex>>(meshData, 6);
std::vector<glm::vec3> ssaoNoise;
for (unsigned int i = 0; i < 16; i++)
{
glm::vec3 noise(
(rand() / static_cast<float>(RAND_MAX)) * 2.0 - 1.0,
(rand() / static_cast<float>(RAND_MAX)) * 2.0 - 1.0,
0.0f);
ssaoNoise.push_back(noise);
}
glGenTextures(1, &noiseTexture);
glBindTexture(GL_TEXTURE_2D, noiseTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 4, 4, 0, GL_RGB, GL_FLOAT, ssaoNoise.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(GL_TEXTURE_2D, 0);
}
PostProcessing::~PostProcessing() = default;
void PostProcessing::use(DrawContext& context, bool gbufferPipeline) {
const auto& vp = context.getViewport();
if (gbufferPipeline) {
if (gbuffer == nullptr) {
gbuffer = std::make_unique<GBuffer>(vp.x, vp.y);
} else {
gbuffer->resize(vp.x, vp.y);
}
context.setFramebuffer(gbuffer.get());
} else {
gbuffer.reset();
refreshFbos(vp.x, vp.y);
context.setFramebuffer(fbo.get());
}
}
void PostProcessing::refreshFbos(uint width, uint height) {
if (fbo) {
fbo->resize(width, height);
fboSecond->resize(width, height);
} else {
fbo = std::make_unique<Framebuffer>(width, height);
fboSecond = std::make_unique<Framebuffer>(width, height);
}
}
void PostProcessing::bindDepthBuffer() {
if (gbuffer) {
gbuffer->bindDepthBuffer(fbo->getFBO());
}
}
void PostProcessing::configureEffect(
const DrawContext& context,
PostEffect& effect,
Shader& shader,
float timer,
const Camera& camera
) {
const auto& viewport = context.getViewport();
shader.uniform1i("u_screen", TARGET_COLOR);
shader.uniform1i("u_skybox", TARGET_SKYBOX);
if (gbuffer) {
shader.uniform1i("u_position", TARGET_POSITIONS);
shader.uniform1i("u_normal", TARGET_NORMALS);
shader.uniform1i("u_emission", TARGET_EMISSION);
}
shader.uniform1i("u_noise", TARGET_SSAO); // used in SSAO pass
shader.uniform1i("u_ssao", TARGET_SSAO);
shader.uniform2i("u_screenSize", viewport);
shader.uniform3f("u_cameraPos", camera.position);
shader.uniform1f("u_timer", timer);
shader.uniformMatrix("u_projection", camera.getProjection());
shader.uniformMatrix("u_view", camera.getView());
shader.uniformMatrix("u_inverseView", glm::inverse(camera.getView()));
}
void PostProcessing::renderDeferredShading(
const DrawContext& context,
const Assets& assets,
float timer,
const Camera& camera
) {
if (gbuffer == nullptr) {
throw std::runtime_error("gbuffer is not initialized");
}
// Generating ssao
gbuffer->bindBuffers();
glActiveTexture(GL_TEXTURE0 + TARGET_SSAO);
glBindTexture(GL_TEXTURE_2D, noiseTexture);
glActiveTexture(GL_TEXTURE0);
auto& ssaoEffect = assets.require<PostEffect>("ssao");
auto& shader = ssaoEffect.use();
configureEffect(
context,
ssaoEffect,
shader,
timer,
camera
);
gbuffer->bindSSAO();
quadMesh->draw();
gbuffer->unbind();
{
auto viewport = context.getViewport();
refreshFbos(viewport.x, viewport.y);
auto ctx = context.sub();
ctx.setFramebuffer(fbo.get());
glActiveTexture(GL_TEXTURE0 + TARGET_SSAO);
gbuffer->bindSSAOBuffer();
glActiveTexture(GL_TEXTURE0);
gbuffer->bindBuffers();
auto& effect = assets.require<PostEffect>("deferred_lighting");
auto& shader = effect.use();
configureEffect(
context,
effect,
shader,
timer,
camera
);
quadMesh->draw();
}
}
void PostProcessing::render(
const DrawContext& context,
const Assets& assets,
float timer,
const Camera& camera
) {
if (fbo == nullptr) {
throw std::runtime_error("'use(...)' was never called");
}
int totalPasses = 0;
for (const auto& effect : effectSlots) {
totalPasses +=
(effect != nullptr && effect->isActive() &&
!(effect->isAdvanced() && gbuffer == nullptr));
}
const auto& vp = context.getViewport();
refreshFbos(vp.x, vp.y);
glActiveTexture(GL_TEXTURE0);
fbo->getTexture()->bind();
if (totalPasses == 0) {
// replace 'default' blit shader with glBlitFramebuffer?
auto& effect = assets.require<PostEffect>("default");
auto& shader = effect.use();
configureEffect(
context, effect, shader, timer, camera
);
quadMesh->draw();
return;
}
int currentPass = 1;
for (const auto& effect : effectSlots) {
if (effect == nullptr || !effect->isActive()) {
continue;
}
if (effect->isAdvanced() && gbuffer == nullptr) {
continue;
}
auto& shader = effect->use();
configureEffect(
context,
*effect,
shader,
timer,
camera
);
if (currentPass > 1) {
fbo->getTexture()->bind();
}
if (currentPass < totalPasses) {
fboSecond->bind();
}
quadMesh->draw();
if (currentPass < totalPasses) {
fboSecond->unbind();
std::swap(fbo, fboSecond);
}
currentPass++;
}
}
void PostProcessing::setEffect(size_t slot, std::shared_ptr<PostEffect> effect) {
effectSlots.at(slot) = std::move(effect);
}
PostEffect* PostProcessing::getEffect(size_t slot) {
return effectSlots.at(slot).get();
}
std::unique_ptr<ImageData> PostProcessing::toImage() {
return fbo->getTexture()->readData();
}
Framebuffer* PostProcessing::getFramebuffer() const {
return fbo.get();
}