diff --git a/res/preload.json b/res/preload.json index 1cfd0747..cac23425 100644 --- a/res/preload.json +++ b/res/preload.json @@ -5,10 +5,12 @@ "main", "lines", "entity", - "screen", "background", "skybox_gen" ], + "post-effects": [ + "default" + ], "textures": [ "gui/menubg", "gui/delete_icon", diff --git a/res/shaders/effect.glslf b/res/shaders/effect.glslf new file mode 100644 index 00000000..a6325cca --- /dev/null +++ b/res/shaders/effect.glslf @@ -0,0 +1,13 @@ +in vec2 v_uv; +out vec4 f_color; + +uniform sampler2D u_screen; +uniform ivec2 u_screenSize; +uniform float u_intensity; + +#include <__effect__> + +void main() { + f_color = effect(); +} + diff --git a/res/shaders/screen.glslv b/res/shaders/effect.glslv similarity index 56% rename from res/shaders/screen.glslv rename to res/shaders/effect.glslv index 593e5f19..97ac08a7 100644 --- a/res/shaders/screen.glslv +++ b/res/shaders/effect.glslv @@ -1,13 +1,10 @@ layout (location = 0) in vec2 v_position; -out vec2 v_coord; +out vec2 v_uv; uniform ivec2 u_screenSize; -uniform float u_timer; -uniform float u_dayTime; void main(){ - v_coord = v_position * 0.5 + 0.5; + v_uv = v_position * 0.5 + 0.5; gl_Position = vec4(v_position, 0.0, 1.0); } - diff --git a/res/shaders/effects/default.glsl b/res/shaders/effects/default.glsl new file mode 100644 index 00000000..c2f9effa --- /dev/null +++ b/res/shaders/effects/default.glsl @@ -0,0 +1,3 @@ +vec4 effect() { + return texture(u_screen, v_uv); +} diff --git a/res/shaders/screen.glslf b/res/shaders/screen.glslf deleted file mode 100644 index e7523d10..00000000 --- a/res/shaders/screen.glslf +++ /dev/null @@ -1,25 +0,0 @@ -in vec2 v_coord; -out vec4 f_color; - -uniform sampler2D u_texture0; -uniform ivec2 u_screenSize; - -// Vignette -vec4 apply_vignette(vec4 color) { - vec2 position = (gl_FragCoord.xy / u_screenSize) - vec2(0.5); - float dist = length(position); - - float radius = 1.3; - float softness = 1.0; - float vignette = smoothstep(radius, radius - softness, dist); - - color.rgb = color.rgb * vignette; - - return color; -} - -void main() { - f_color = texture(u_texture0, v_coord); - f_color = apply_vignette(f_color); -} - diff --git a/src/assets/Assets.hpp b/src/assets/Assets.hpp index c126079d..c0853b0b 100644 --- a/src/assets/Assets.hpp +++ b/src/assets/Assets.hpp @@ -16,7 +16,16 @@ class Assets; -enum class AssetType { TEXTURE, SHADER, FONT, ATLAS, LAYOUT, SOUND, MODEL }; +enum class AssetType { + TEXTURE, + SHADER, + FONT, + ATLAS, + LAYOUT, + SOUND, + MODEL, + POST_EFFECT +}; namespace assetload { /// @brief final work to do in the main thread @@ -91,6 +100,20 @@ public: return static_cast(found->second.get()); } + template + std::shared_ptr getShared(const std::string& name) const { + const auto& mapIter = assets.find(typeid(T)); + if (mapIter == assets.end()) { + return nullptr; + } + const auto& map = mapIter->second; + const auto& found = map.find(name); + if (found == map.end()) { + return nullptr; + } + return std::static_pointer_cast(found->second); + } + template T& require(const std::string& name) const { T* asset = get(name); diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 10f3f7ea..cdbb28e2 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -34,6 +34,7 @@ AssetsLoader::AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths addLoader(AssetType::LAYOUT, assetload::layout); addLoader(AssetType::SOUND, assetload::sound); addLoader(AssetType::MODEL, assetload::model); + addLoader(AssetType::POST_EFFECT, assetload::posteffect); } void AssetsLoader::addLoader(AssetType tag, aloader_func func) { @@ -131,6 +132,8 @@ static std::string assets_def_folder(AssetType tag) { return SOUNDS_FOLDER; case AssetType::MODEL: return MODELS_FOLDER; + case AssetType::POST_EFFECT: + return POST_EFFECTS_FOLDER; } return ""; } @@ -196,6 +199,7 @@ void AssetsLoader::processPreloadConfig(const io::path& file) { processPreloadList(AssetType::TEXTURE, root["textures"]); processPreloadList(AssetType::SOUND, root["sounds"]); processPreloadList(AssetType::MODEL, root["models"]); + processPreloadList(AssetType::POST_EFFECT, root["post-effects"]); // layouts are loaded automatically } diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 7558bbdc..2afae28c 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -60,13 +60,7 @@ assetload::postfunc assetload::texture( } } -assetload::postfunc assetload::shader( - AssetsLoader*, - const ResPaths& paths, - const std::string& filename, - const std::string& name, - const std::shared_ptr& -) { +static auto process_program(const ResPaths& paths, const std::string& filename) { io::path vertexFile = paths.find(filename + ".glslv"); io::path fragmentFile = paths.find(filename + ".glslf"); @@ -75,8 +69,25 @@ assetload::postfunc assetload::shader( auto& preprocessor = *Shader::preprocessor; - vertexSource = preprocessor.process(vertexFile, vertexSource).code; - fragmentSource = preprocessor.process(fragmentFile, fragmentSource).code; + auto vertex = preprocessor.process(vertexFile, vertexSource); + auto fragment = preprocessor.process(fragmentFile, fragmentSource); + return std::make_pair(vertex, fragment); +} + +assetload::postfunc assetload::shader( + AssetsLoader*, + const ResPaths& paths, + const std::string& filename, + const std::string& name, + const std::shared_ptr& +) { + auto [vertex, fragment] = process_program(paths, filename); + + io::path vertexFile = paths.find(filename + ".glslv"); + io::path fragmentFile = paths.find(filename + ".glslf"); + + std::string vertexSource = std::move(vertex.code); + std::string fragmentSource = std::move(fragment.code); return [=](auto assets) { assets->store( @@ -91,6 +102,40 @@ assetload::postfunc assetload::shader( }; } +assetload::postfunc assetload::posteffect( + AssetsLoader*, + const ResPaths& paths, + const std::string& file, + const std::string& name, + const std::shared_ptr& settings +) { + io::path effectFile = paths.find(file + ".glsl"); + std::string effectSource = io::read_string(effectFile); + + auto& preprocessor = *Shader::preprocessor; + preprocessor.addHeader( + "__effect__", preprocessor.process(effectFile, effectSource, true) + ); + + auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect"); + auto params = std::move(fragment.params); + + std::string vertexSource = std::move(vertex.code); + std::string fragmentSource = std::move(fragment.code); + + return [=](auto assets) { + auto program = Shader::create( + effectFile.string(), + effectFile.string(), + vertexSource, + fragmentSource + ); + assets->store( + std::make_shared(std::move(program), params), name + ); + }; +} + static bool append_atlas(AtlasBuilder& atlas, const io::path& file) { std::string name = file.stem(); // skip duplicates diff --git a/src/assets/assetload_funcs.hpp b/src/assets/assetload_funcs.hpp index eb26dc62..97ed60ea 100644 --- a/src/assets/assetload_funcs.hpp +++ b/src/assets/assetload_funcs.hpp @@ -62,4 +62,11 @@ namespace assetload { const std::string& name, const std::shared_ptr& settings ); + postfunc posteffect( + AssetsLoader*, + const ResPaths& paths, + const std::string& file, + const std::string& name, + const std::shared_ptr& settings + ); } diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index f205b9b4..8af419e0 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -27,21 +27,21 @@ void GLSLExtension::loadHeader(const std::string& name) { } io::path file = paths->find("shaders/lib/" + name + ".glsl"); std::string source = io::read_string(file); - addHeader(name, ""); - - auto [code, _] = process(file, source, true); - addHeader(name, std::move(code)); + addHeader(name, {}); + addHeader(name, process(file, source, true)); } -void GLSLExtension::addHeader(const std::string& name, std::string source) { - headers[name] = std::move(source); +void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) { + headers[name] = std::move(header); } void GLSLExtension::define(const std::string& name, std::string value) { defines[name] = std::move(value); } -const std::string& GLSLExtension::getHeader(const std::string& name) const { +const GLSLExtension::ProcessingResult& GLSLExtension::getHeader( + const std::string& name +) const { auto found = headers.find(name); if (found == headers.end()) { throw std::runtime_error("no header '" + name + "' loaded"); @@ -161,7 +161,11 @@ public: if (!glsl.hasHeader(headerName)) { glsl.loadHeader(headerName); } - ss << glsl.getHeader(headerName) << '\n'; + const auto& header = glsl.getHeader(headerName); + for (const auto& [name, param] : header.params) { + params[name] = param; + } + ss << header.code << '\n'; source_line(ss, line); return false; } diff --git a/src/coders/GLSLExtension.hpp b/src/coders/GLSLExtension.hpp index e78f06d7..2d795d2c 100644 --- a/src/coders/GLSLExtension.hpp +++ b/src/coders/GLSLExtension.hpp @@ -11,13 +11,20 @@ class ResPaths; class GLSLExtension { public: + using ParamsMap = std::unordered_map; + + struct ProcessingResult { + std::string code; + ParamsMap params; + }; + void setPaths(const ResPaths* paths); void define(const std::string& name, std::string value); void undefine(const std::string& name); - void addHeader(const std::string& name, std::string source); + void addHeader(const std::string& name, ProcessingResult header); - const std::string& getHeader(const std::string& name) const; + const ProcessingResult& getHeader(const std::string& name) const; const std::string& getDefine(const std::string& name) const; const std::unordered_map& getDefines() const; @@ -25,12 +32,7 @@ public: bool hasHeader(const std::string& name) const; bool hasDefine(const std::string& name) const; void loadHeader(const std::string& name); - - struct ProcessingResult { - std::string code; - std::unordered_map params; - }; - + ProcessingResult process( const io::path& file, const std::string& source, @@ -39,7 +41,7 @@ public: static inline std::string VERSION = "330 core"; private: - std::unordered_map headers; + std::unordered_map headers; std::unordered_map defines; const ResPaths* paths = nullptr; diff --git a/src/constants.hpp b/src/constants.hpp index 32acc83e..de954717 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -68,5 +68,6 @@ inline const std::string LAYOUTS_FOLDER = "layouts"; inline const std::string SOUNDS_FOLDER = "sounds"; inline const std::string MODELS_FOLDER = "models"; inline const std::string SKELETONS_FOLDER = "skeletons"; +inline const std::string POST_EFFECTS_FOLDER = "shaders/effects"; inline const std::string FONT_DEFAULT = "normal"; diff --git a/src/graphics/core/PostEffect.cpp b/src/graphics/core/PostEffect.cpp index 6af987c5..7fcc5162 100644 --- a/src/graphics/core/PostEffect.cpp +++ b/src/graphics/core/PostEffect.cpp @@ -8,10 +8,19 @@ PostEffect::Param::Param(Type type, Value defValue) : type(type), defValue(std::move(defValue)) { } -PostEffect::PostEffect(std::unique_ptr shader) - : shader(std::move(shader)) { +PostEffect::PostEffect( + std::unique_ptr shader, + std::unordered_map params +) + : shader(std::move(shader)), params(std::move(params)) { } -void PostEffect::use() { +Shader& PostEffect::use() { shader->use(); + shader->uniform1f("u_intensity", intensity); + return *shader; +} + +void PostEffect::setIntensity(float value) { + intensity = value; } diff --git a/src/graphics/core/PostEffect.hpp b/src/graphics/core/PostEffect.hpp index f77e121f..ed8f2753 100644 --- a/src/graphics/core/PostEffect.hpp +++ b/src/graphics/core/PostEffect.hpp @@ -21,10 +21,20 @@ public: Param(Type type, Value defValue); }; - PostEffect(std::unique_ptr shader); + PostEffect( + std::unique_ptr shader, + std::unordered_map params + ); - void use(); + Shader& use(); + + void setIntensity(float value); + + bool isActive() { + return intensity > 1e-4f; + } private: std::unique_ptr shader; std::unordered_map params; + float intensity = 0.0f; }; diff --git a/src/graphics/core/PostProcessing.cpp b/src/graphics/core/PostProcessing.cpp index 9cfe6414..f41ad661 100644 --- a/src/graphics/core/PostProcessing.cpp +++ b/src/graphics/core/PostProcessing.cpp @@ -4,13 +4,15 @@ #include "Texture.hpp" #include "Framebuffer.hpp" #include "DrawContext.hpp" +#include "PostEffect.hpp" +#include "assets/Assets.hpp" #include PostProcessing::PostProcessing() { // Fullscreen quad mesh bulding float vertices[] { - -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f }; VertexAttribute attrs[] {{2}, {0}}; @@ -23,22 +25,56 @@ void PostProcessing::use(DrawContext& context) { const auto& vp = context.getViewport(); if (fbo) { fbo->resize(vp.x, vp.y); + fboSecond->resize(vp.x, vp.y); } else { fbo = std::make_unique(vp.x, vp.y); + fboSecond = std::make_unique(vp.x, vp.y); } context.setFramebuffer(fbo.get()); } -void PostProcessing::render(const DrawContext& context, Shader* screenShader) { +void PostProcessing::render( + const DrawContext& context, const Assets& assets, float timer +) { if (fbo == nullptr) { throw std::runtime_error("'use(...)' was never called"); } + int totalPasses = 0; + for (const auto& effect : effectSlots) { + totalPasses += (effect != nullptr && effect->isActive()); + } - const auto& viewport = context.getViewport(); - screenShader->use(); - screenShader->uniform2i("u_screenSize", viewport); - fbo->getTexture()->bind(); - quadMesh->draw(); + if (totalPasses == 0) { + auto& effect = assets.require("default"); + effect.use(); + fbo->getTexture()->bind(); + quadMesh->draw(); + return; + } + + int currentPass = 1; + for (const auto& effect : effectSlots) { + if (effect == nullptr || !effect->isActive()) { + continue; + } + auto& shader = effect->use(); + + const auto& viewport = context.getViewport(); + shader.uniform1i("u_screen", 0); + shader.uniform2i("u_screenSize", viewport); + shader.uniform1f("u_timer", timer); + + fbo->getTexture()->bind(); + if (currentPass < totalPasses) { + fboSecond->bind(); + } + quadMesh->draw(); + if (currentPass < totalPasses) { + fboSecond->unbind(); + std::swap(fbo, fboSecond); + } + currentPass++; + } } std::unique_ptr PostProcessing::toImage() { diff --git a/src/graphics/core/PostProcessing.hpp b/src/graphics/core/PostProcessing.hpp index b31a1369..888c6694 100644 --- a/src/graphics/core/PostProcessing.hpp +++ b/src/graphics/core/PostProcessing.hpp @@ -1,12 +1,14 @@ #pragma once +#include #include class Mesh; -class Shader; +class Assets; class Framebuffer; class DrawContext; class ImageData; +class PostEffect; /// @brief Framebuffer with blitting with shaders. /// @attention Current implementation does not support multiple render passes @@ -14,8 +16,10 @@ class ImageData; class PostProcessing { /// @brief Main framebuffer (lasy field) std::unique_ptr fbo; + std::unique_ptr fboSecond; /// @brief Fullscreen quad mesh as the post-processing canvas std::unique_ptr quadMesh; + std::vector> effectSlots; public: PostProcessing(); ~PostProcessing(); @@ -27,9 +31,8 @@ public: /// @brief Render fullscreen quad using the passed shader /// with framebuffer texture bound /// @param context graphics context - /// @param screenShader shader used for fullscreen quad /// @throws std::runtime_error if use(...) wasn't called before - void render(const DrawContext& context, Shader* screenShader); + void render(const DrawContext& context, const Assets& assets, float timer); /// @brief Make an image from the last rendered frame std::unique_ptr toImage(); diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 73ab27a2..04a0e427 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -388,12 +388,7 @@ void WorldRenderer::draw( renderBlockOverlay(wctx); } - // Rendering fullscreen quad - auto screenShader = assets.get("screen"); - screenShader->use(); - screenShader->uniform1f("u_timer", timer); - screenShader->uniform1f("u_dayTime", worldInfo.daytime); - postProcessing.render(pctx, screenShader); + postProcessing.render(pctx, assets, timer); } void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { diff --git a/src/typedefs.hpp b/src/typedefs.hpp index e598b9da..f075e998 100644 --- a/src/typedefs.hpp +++ b/src/typedefs.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include using scriptenv = std::shared_ptr; diff --git a/test/coders/GLSLExtension.cpp b/test/coders/GLSLExtension.cpp index 87690ad6..bcdd5316 100644 --- a/test/coders/GLSLExtension.cpp +++ b/test/coders/GLSLExtension.cpp @@ -6,10 +6,13 @@ TEST(GLSLExtension, processing) { GLSLExtension glsl; glsl.addHeader("sum", - "// sum function for glsl\n" - "float sum(float a, float b) {\n" - " return a + b;\n" - "}\n" + glsl.process("sum.glsl", + "// sum function for glsl\n" + "float sum(float a, float b) {\n" + " return a + b;\n" + "}\n", + true + ) ); try { auto processed = glsl.process("test.glsl",