diff --git a/framework/opengl_renderer/CMakeLists.txt b/framework/opengl_renderer/CMakeLists.txt index 26c76b0..d1b683c 100644 --- a/framework/opengl_renderer/CMakeLists.txt +++ b/framework/opengl_renderer/CMakeLists.txt @@ -179,6 +179,49 @@ target_link_libraries(blur_render_task engine ) +############# bloom_extraction render task ########### + +add_library(bloom_extraction_render_task + src/mm/opengl/render_tasks/bloom_extraction.hpp + src/mm/opengl/render_tasks/bloom_extraction.cpp +) + +target_include_directories(bloom_extraction_render_task PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") + +target_link_libraries(bloom_extraction_render_task + opengl_renderer_s + engine +) + +############# bloom_combine render task ########### + +add_library(bloom_combine_render_task + src/mm/opengl/render_tasks/bloom_combine.hpp + src/mm/opengl/render_tasks/bloom_combine.cpp +) + +target_include_directories(bloom_combine_render_task PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") + +target_link_libraries(bloom_combine_render_task + opengl_renderer_s + engine +) + +############# composition render task ########### +# intendet for bloom compositing and tonemapping + +add_library(composition_render_task + src/mm/opengl/render_tasks/composition.hpp + src/mm/opengl/render_tasks/composition.cpp +) + +target_include_directories(composition_render_task PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") + +target_link_libraries(composition_render_task + opengl_renderer_s + engine +) + ############# tilemap renderer ########### add_library(tilemap_render_task @@ -210,6 +253,21 @@ target_link_libraries(fast_sky_render_task engine ) +############# bloom ########### + +add_library(bloom + src/mm/opengl/bloom.hpp + src/mm/opengl/bloom.cpp +) + +target_include_directories(bloom PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") + +target_link_libraries(bloom + bloom_extraction_render_task + blur_render_task + bloom_combine_render_task +) + ######################## if (BUILD_TESTING) diff --git a/framework/opengl_renderer/src/mm/opengl/bloom.cpp b/framework/opengl_renderer/src/mm/opengl/bloom.cpp new file mode 100644 index 0000000..bf65ef9 --- /dev/null +++ b/framework/opengl_renderer/src/mm/opengl/bloom.cpp @@ -0,0 +1,158 @@ +#include "./bloom.hpp" + +#include +#include +#include + +#include +#include + +namespace MM::OpenGL { + +using namespace entt::literals; + +void setup_bloom( + MM::Engine& engine, + const std::string color_src_tex, + const size_t bloom_phases, + const float bloom_in_scale, + const float bloom_phase_scale +) { + assert(bloom_phases > 1); + auto& rs = engine.getService(); + auto& rm_t = MM::ResourceManager::ref(); + auto [w, h] = engine.getService().getWindowSize(); + +#ifdef MM_OPENGL_3_GLES + #if 0 // NOPE!! + // TODO: caps at 1, invest in half float? + const auto bloom_internal_format = GL_RGB565; // prolly fine + const auto bloom_format_type = GL_UNSIGNED_BYTE; + #else + const auto bloom_internal_format = GL_RGB16F; + const auto bloom_format_type = GL_FLOAT; + #endif +#else + const auto bloom_internal_format = GL_RGB16F; + const auto bloom_format_type = GL_FLOAT; +#endif + + { // bloom in (bloom extraction) + rm_t.reload( + "bloom_in", + bloom_internal_format, + w * bloom_in_scale, h * bloom_in_scale, + GL_RGB, bloom_format_type + ); + { // filter + rm_t.get("bloom_in"_hs)->bind(0); + 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_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + rs.targets["bloom_extraction"] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get("bloom_in"_hs), GL_COLOR_ATTACHMENT0) + .setResizeFactors(bloom_in_scale, bloom_in_scale) + .setResize(true) + .finish(); + assert(rs.targets["bloom_extraction"]); + } + + // blur textures and fbos + for (size_t i = 1; i <= bloom_phases; i++) { + // TODO: further dedup + std::string tex_out_name {"blur_out" + std::to_string(i)}; + auto tex_out_id = entt::hashed_string::value(tex_out_name.c_str()); + rm_t.reload( + tex_out_id, + bloom_internal_format, + w * bloom_in_scale * glm::pow(bloom_phase_scale, i), h * bloom_in_scale * glm::pow(bloom_phase_scale, i), + GL_RGB, bloom_format_type + ); + { // filter + rm_t.get(tex_out_id)->bind(0); + 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_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + std::string tex_tmp_name {"blur_tmp" + std::to_string(i)}; + auto tex_tmp_id = entt::hashed_string::value(tex_tmp_name.c_str()); + rm_t.reload( + tex_tmp_id, + bloom_internal_format, + w * bloom_in_scale * glm::pow(bloom_phase_scale, i), h * bloom_in_scale * glm::pow(bloom_phase_scale, i), + GL_RGB, bloom_format_type + ); + { // filter + rm_t.get(tex_tmp_id)->bind(0); + 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_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + rs.targets[tex_out_name] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get(tex_out_id), GL_COLOR_ATTACHMENT0) + .setResizeFactors(bloom_in_scale * glm::pow(bloom_phase_scale, i), bloom_in_scale * glm::pow(bloom_phase_scale, i)) + .setResize(true) + .finish(); + assert(rs.targets[tex_out_name]); + + rs.targets[tex_tmp_name] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get(tex_tmp_id), GL_COLOR_ATTACHMENT0) + .setResizeFactors(bloom_in_scale * glm::pow(bloom_phase_scale, i), bloom_in_scale * glm::pow(bloom_phase_scale, i)) + .setResize(true) + .finish(); + assert(rs.targets[tex_tmp_name]); + } + + { // render tasks + auto& extraction = rs.addRenderTask(engine); + extraction.src_tex = color_src_tex; + extraction.target_fbo = "bloom_extraction"; + + const glm::vec2 blur_factor {1.f, 1.f}; + + { // blur rt + std::string prev_out_tex = "bloom_in"; + for (size_t i = 1; i <= bloom_phases; i++) { + auto& blur = rs.addRenderTask(engine); + // h + blur.in_tex = prev_out_tex; + blur.temp_fbo = "blur_tmp" + std::to_string(i); + // v + blur.temp_tex = "blur_tmp" + std::to_string(i); + blur.out_fbo = "blur_out" + std::to_string(i); + blur.out_tex = "blur_out" + std::to_string(i); + blur.tex_offset_factor = blur_factor * glm::vec2{2.f, 1.f}; // the input texture is double the size + + // old blur: + //blur.tex_offset = {1.f/(w * bloom_in_scale * bloom_in_scale), 1.f/(h * bloom_in_scale * bloom_in_scale)}; + + prev_out_tex = blur.out_tex; + } + } + + // combine passes + for (size_t i = bloom_phases; i > 1; i--) { + auto& combine = rs.addRenderTask(engine); + if (i == bloom_phases) { + combine.tex0 = "blur_out" + std::to_string(i); + } else { + combine.tex0 = "blur_tmp" + std::to_string(i); + } + combine.tex1 = "blur_out" + std::to_string(i-1); + combine.target_fbo = "blur_tmp" + std::to_string(i-1); // -> blur_tmpi-1 + } + } +} + +} // MM::OpenGL + diff --git a/framework/opengl_renderer/src/mm/opengl/bloom.hpp b/framework/opengl_renderer/src/mm/opengl/bloom.hpp new file mode 100644 index 0000000..467df57 --- /dev/null +++ b/framework/opengl_renderer/src/mm/opengl/bloom.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "./render_task.hpp" + +namespace MM::OpenGL { + + // helper function to setup a bloom blur and recombine chain + // creates (texture) rendertarget, fbos and rendertasks + // outputs blur to "blur_tmp1" texture + // + // you still need to add the Composition rendertask (or eqv) your self, eg: + // auto& comp = rs.addRenderTask(engine); + // comp.color_tex = "hdr_color"; + // comp.bloom_tex = "blur_tmp1"; + // comp.target_fbo = "display"; + void setup_bloom( + MM::Engine& engine, + const std::string color_src_tex = "hdr_color", // texture to extract color from + const size_t bloom_phases = 5, // number of downsampled blurs (4 prob fine for 720, 5 for 1080) + const float bloom_in_scale = 0.5f, // scale of bloom extraction layer (1 - 0.5 best, lower for more perf) + const float bloom_phase_scale = 0.5f // ammount of scaling per downsampling + ); + +} // MM::OpenGL + diff --git a/framework/opengl_renderer/src/mm/opengl/render_task.hpp b/framework/opengl_renderer/src/mm/opengl/render_task.hpp index c589314..93dcb2e 100644 --- a/framework/opengl_renderer/src/mm/opengl/render_task.hpp +++ b/framework/opengl_renderer/src/mm/opengl/render_task.hpp @@ -30,5 +30,5 @@ namespace MM::OpenGL { //virtual void reload(void) {} // TODO: remove //virtual std::vector getShaderPaths(void) {return {};} // TODO: remove }; -} +} // MM:OpenGL diff --git a/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_combine.cpp b/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_combine.cpp new file mode 100644 index 0000000..c20bb1f --- /dev/null +++ b/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_combine.cpp @@ -0,0 +1,161 @@ +#include "./bloom_combine.hpp" + +#include +#include +#include + +#include + +namespace MM::OpenGL::RenderTasks { + +BloomCombine::BloomCombine(Engine& engine) { + float vertices[] = { + -1.f, 1.f, + -1.f, -1.f, + 1.f, -1.f, + 1.f, -1.f, + 1.f, 1.f, + -1.f, 1.f, + }; + + _vertexBuffer = std::make_unique(vertices, 2 * 6 * sizeof(float), GL_STATIC_DRAW); + _vao = std::make_unique(); + _vao->bind(); + _vertexBuffer->bind(GL_ARRAY_BUFFER); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); + + _vertexBuffer->unbind(GL_ARRAY_BUFFER); + _vao->unbind(); + + setupShaderFiles(); + _shader = Shader::createF(engine, vertexPath, fragmentPath); + assert(_shader != nullptr); +} + +BloomCombine::~BloomCombine(void) { +} + +void BloomCombine::render(Services::OpenGLRenderer& rs, Engine& engine) { + ZoneScopedN("RenderTasks::BloomCombine::render"); + + rs.targets[target_fbo]->bind(FrameBufferObject::W); + { // TODO: move to fbo + GLsizei width {0}; + GLsizei height {0}; + { // get size of fb <.< + auto& fbo_tex_arr = rs.targets[target_fbo]->_texAttachments; + if (fbo_tex_arr.empty()) { + //GLint o_type {0}; + //glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &o_type); + //if (o_type == GL_NONE) { + //// default framebuffer or error + //SPDLOG_INFO("gl none"); + //} + + // nah, just assume screen res + std::tie(width, height) = engine.getService().getWindowSize(); + } else { + auto& target_fbo_tex = rs.targets[target_fbo]->_texAttachments.front(); + width = target_fbo_tex->width; + height = target_fbo_tex->height; + } + } + glViewport(0, 0, width, height); + } + + + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + _shader->bind(); + _vertexBuffer->bind(GL_ARRAY_BUFFER); + _vao->bind(); + + auto& rm = MM::ResourceManager::ref(); + + auto tex_0 = rm.get(entt::hashed_string::value(tex0.c_str())); + tex_0->bind(0); + + auto tex_1 = rm.get(entt::hashed_string::value(tex1.c_str())); + tex_1->bind(1); + + // assign image units + _shader->setUniform1i("_tex0", 0); + _shader->setUniform1i("_tex1", 1); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + _vao->unbind(); + _vertexBuffer->unbind(GL_ARRAY_BUFFER); + _shader->unbind(); +} + +void BloomCombine::reloadShaders(Engine& engine) { + auto tmp_shader = Shader::createF(engine, vertexPath, fragmentPath); + if (tmp_shader) { + _shader = tmp_shader; + } +} + +void BloomCombine::setupShaderFiles(void) { + FS_CONST_MOUNT_FILE(vertexPath, +GLSL_VERSION_STRING +R"( +#ifdef GL_ES + precision mediump float; +#endif + +in vec2 _vertexPosition; +out vec2 _uv; + +void main() { + gl_Position = vec4(_vertexPosition, 0, 1); + _uv = vec2(_vertexPosition.x * 0.5 + 0.5, _vertexPosition.y * 0.5 + 0.5); +})") + + FS_CONST_MOUNT_FILE(fragmentPath, +GLSL_VERSION_STRING +R"( +#ifdef GL_ES + precision mediump float; +#endif + +// tent sampling +#include "/shaders/builtin/sampling.glsl" + +uniform sampler2D _tex0; +uniform sampler2D _tex1; + +in vec2 _uv; + +out vec3 _out_color; + +vec3 tentSampling() { + // i hope the pipeline caches this + ivec2 tex0_size = textureSize(_tex0, 0); // TODO: lod + vec2 tex0_texel_step = vec2(1.0) / vec2(tex0_size); + + return sampling2D_tent3x3_vec3( + _tex0, + _uv, + tex0_texel_step + ); +} + +vec3 simpleSampling() { + return texture(_tex0, _uv).rgb; +} + +void main() { +#ifdef SIMPLE_SAMPLING + _out_color = texture(_tex1, _uv).rgb + simpleSampling(); +#else + _out_color = texture(_tex1, _uv).rgb + tentSampling(); +#endif +})") +} + +} // MM::OpenGL::RenderTasks + diff --git a/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_combine.hpp b/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_combine.hpp new file mode 100644 index 0000000..dafee91 --- /dev/null +++ b/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_combine.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +namespace MM::OpenGL::RenderTasks { + + class BloomCombine : public RenderTask { + private: + std::shared_ptr _shader; + std::unique_ptr _vertexBuffer; + std::unique_ptr _vao; + + public: + BloomCombine(Engine& engine); + ~BloomCombine(void); + + const char* name(void) override { return "BloomCombine"; } + + void render(Services::OpenGLRenderer& rs, Engine& engine) override; + + public: + const char* vertexPath {"shader/combine_render_task/vert.glsl"}; + const char* fragmentPath {"shader/combine_render_task/frag.glsl"}; + + std::string target_fbo {"display"}; + + std::string tex0 {"tex0"}; // lower res + std::string tex1 {"tex1"}; + + void reloadShaders(Engine& engine); + + private: + void setupShaderFiles(void); + }; + +} // MM::OpenGL::RenderTasks + diff --git a/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_extraction.cpp b/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_extraction.cpp new file mode 100644 index 0000000..3975a62 --- /dev/null +++ b/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_extraction.cpp @@ -0,0 +1,114 @@ +#include "./bloom_extraction.hpp" + +#include +#include +#include + +#include + +namespace MM::OpenGL::RenderTasks { + +BloomExtraction::BloomExtraction(Engine& engine) { + float vertices[] = { + -1.f, 1.f, + -1.f, -1.f, + 1.f, -1.f, + 1.f, -1.f, + 1.f, 1.f, + -1.f, 1.f, + }; + + _vertexBuffer = std::make_unique(vertices, 2 * 6 * sizeof(float), GL_STATIC_DRAW); + _vao = std::make_unique(); + _vao->bind(); + _vertexBuffer->bind(GL_ARRAY_BUFFER); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); + + _vertexBuffer->unbind(GL_ARRAY_BUFFER); + _vao->unbind(); + + setupShaderFiles(); + _shader = Shader::createF(engine, vertexPath, fragmentPath); + assert(_shader != nullptr); +} + +BloomExtraction::~BloomExtraction(void) { +} + +void BloomExtraction::render(Services::OpenGLRenderer& rs, Engine&) { + ZoneScopedN("RenderTasks::BloomExtraction::render"); + + auto& target_fbo_ = rs.targets[target_fbo]; + target_fbo_->bind(FrameBufferObject::W); + auto& target_fbo_tex = target_fbo_->_texAttachments.front(); + glViewport(0, 0, target_fbo_tex->width, target_fbo_tex->height); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + _shader->bind(); + _vertexBuffer->bind(GL_ARRAY_BUFFER); + _vao->bind(); + + auto& rm = MM::ResourceManager::ref(); + + auto tex = rm.get(entt::hashed_string::value(src_tex.c_str())); + tex->bind(0); + + _shader->setUniform1i("color_tex", 0); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + _vao->unbind(); + _vertexBuffer->unbind(GL_ARRAY_BUFFER); + _shader->unbind(); +} + +void BloomExtraction::reloadShaders(Engine& engine) { + auto tmp_shader = Shader::createF(engine, vertexPath, fragmentPath); + if (tmp_shader) { + _shader = tmp_shader; + } +} + +void BloomExtraction::setupShaderFiles(void) { + FS_CONST_MOUNT_FILE(vertexPath, +GLSL_VERSION_STRING +R"( +#ifdef GL_ES + precision mediump float; +#endif + +in vec2 _vertexPosition; +out vec2 _uv; + +void main() { + gl_Position = vec4(_vertexPosition, 0, 1); + _uv = vec2(_vertexPosition.x * 0.5 + 0.5, _vertexPosition.y * 0.5 + 0.5); +})") + + FS_CONST_MOUNT_FILE(fragmentPath, +GLSL_VERSION_STRING +R"( +#ifdef GL_ES + precision mediump float; +#endif + +uniform sampler2D color_tex; + +in vec2 _uv; + +out vec3 _out_color; + +void main() { + vec3 color = texture(color_tex, _uv).rgb; + + // TODO: expose threshold + _out_color = max(vec3(0.0), color - vec3(1.0)); +})") +} + +} // MM::OpenGL::RenderTasks + diff --git a/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_extraction.hpp b/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_extraction.hpp new file mode 100644 index 0000000..44e8dbf --- /dev/null +++ b/framework/opengl_renderer/src/mm/opengl/render_tasks/bloom_extraction.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include + +namespace MM::OpenGL::RenderTasks { + + class BloomExtraction : public RenderTask { + private: + std::shared_ptr _shader; + std::unique_ptr _vertexBuffer; + std::unique_ptr _vao; + + public: + BloomExtraction(Engine& engine); + ~BloomExtraction(void); + + const char* name(void) override { return "BloomExtraction"; } + + void render(Services::OpenGLRenderer& rs, Engine& engine) override; + + public: + const char* vertexPath {"shader/bloom_extraction_render_task/vert.glsl"}; + const char* fragmentPath {"shader/bloom_extraction_render_task/frag.glsl"}; + + std::string target_fbo {"display"}; + + std::string src_tex {"hdr_color"}; + + void reloadShaders(Engine& engine); + + private: + void setupShaderFiles(void); + }; + +} // MM::OpenGL::RenderTasks + diff --git a/framework/opengl_renderer/src/mm/opengl/render_tasks/blur.cpp b/framework/opengl_renderer/src/mm/opengl/render_tasks/blur.cpp index 468f1e2..795a44e 100644 --- a/framework/opengl_renderer/src/mm/opengl/render_tasks/blur.cpp +++ b/framework/opengl_renderer/src/mm/opengl/render_tasks/blur.cpp @@ -36,10 +36,8 @@ Blur::Blur(Engine& engine) { _vao->unbind(); setupShaderFiles(); - _hShader = Shader::createF(engine, vertexPath, fragmentHPath); - assert(_hShader != nullptr); - _vShader = Shader::createF(engine, vertexPath, fragmentVPath); - assert(_vShader != nullptr); + _shader = Shader::createF(engine, vertexPath, fragmentPath); + assert(_shader != nullptr); } Blur::~Blur(void) { @@ -57,17 +55,19 @@ void Blur::render(Services::OpenGLRenderer& rs, Engine&) { auto tex_out = rm_t.get(entt::hashed_string::value(out_tex.c_str())); // TODO: perf problems auto tex_temp = rm_t.get(entt::hashed_string::value(temp_tex.c_str())); // TODO: perf problems + _shader->bind(); + _vertexBuffer->bind(GL_ARRAY_BUFFER); + _vao->bind(); + + _shader->setUniform2f("tex_offset_factor", tex_offset_factor); + { // horizontal rs.targets[temp_fbo]->bind(FrameBufferObject::W); - _hShader->bind(); - _vertexBuffer->bind(GL_ARRAY_BUFFER); - _vao->bind(); - glViewport(0, 0, tex_temp->width, tex_temp->height); tex_in->bind(0); // read - _hShader->setUniform2f("tex_offset", tex_offset); + _shader->setUniform1i("horizontal", 1); glDrawArrays(GL_TRIANGLES, 0, 6); } @@ -75,21 +75,15 @@ void Blur::render(Services::OpenGLRenderer& rs, Engine&) { { // vertical rs.targets[out_fbo]->bind(FrameBufferObject::W); - _vShader->bind(); - _vertexBuffer->bind(GL_ARRAY_BUFFER); - _vao->bind(); - glViewport(0, 0, tex_out->width, tex_out->height); tex_temp->bind(0); // read - _vShader->setUniform2f("tex_offset", tex_offset); + _shader->setUniform1i("horizontal", 0); glDrawArrays(GL_TRIANGLES, 0, 6); } _vao->unbind(); - _vertexBuffer->unbind(GL_ARRAY_BUFFER); - _vShader->unbind(); } void Blur::setupShaderFiles(void) { @@ -108,9 +102,7 @@ void main() { _tex_uv = _vertexPosition * 0.5 + 0.5; })") - // TODO: deduplicate - - FS_CONST_MOUNT_FILE(fragmentHPath, + FS_CONST_MOUNT_FILE(fragmentPath, GLSL_VERSION_STRING R"( #ifdef GL_ES @@ -120,54 +112,19 @@ R"( uniform sampler2D tex0; in vec2 _tex_uv; -//uniform bool horizontal; -const bool horizontal = true; -uniform vec2 tex_offset; +uniform bool horizontal; +//const bool horizontal = true; +uniform vec2 tex_offset_factor; const float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); out vec4 _out_color; void main() { - //vec2 tex_offset = vec2(1.0) / vec2(textureSize(tex0, 0)); // gets size of single texel vec3 result = texture(tex0, _tex_uv).rgb * weight[0]; // current fragment's contribution - if (horizontal) { - for (int i = 1; i < 5; i++) { - result += texture(tex0, _tex_uv + vec2(tex_offset.x * float(i), 0.0)).rgb * weight[i]; - result += texture(tex0, _tex_uv - vec2(tex_offset.x * float(i), 0.0)).rgb * weight[i]; - } - } else { - for (int i = 1; i < 5; i++) { - result += texture(tex0, _tex_uv + vec2(0.0, tex_offset.y * float(i))).rgb * weight[i]; - result += texture(tex0, _tex_uv - vec2(0.0, tex_offset.y * float(i))).rgb * weight[i]; - } - } - - _out_color = vec4(result, 1.0); -})") - - FS_CONST_MOUNT_FILE(fragmentVPath, -GLSL_VERSION_STRING -R"( -#ifdef GL_ES - precision mediump float; -#endif - -uniform sampler2D tex0; -in vec2 _tex_uv; - -//uniform bool horizontal; -const bool horizontal = false; -uniform vec2 tex_offset; - -const float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); - -out vec4 _out_color; - -void main() { - //vec2 tex_offset = vec2(1.0) / vec2(textureSize(tex0, 0)); // gets size of single texel - vec3 result = texture(tex0, _tex_uv).rgb * weight[0]; // current fragment's contribution + vec2 tex_offset = vec2(1.0) / vec2(textureSize(tex0, 0).xy); // gets size of single texel + tex_offset *= tex_offset_factor; if (horizontal) { for (int i = 1; i < 5; i++) { diff --git a/framework/opengl_renderer/src/mm/opengl/render_tasks/blur.hpp b/framework/opengl_renderer/src/mm/opengl/render_tasks/blur.hpp index fa85cb0..c25877c 100644 --- a/framework/opengl_renderer/src/mm/opengl/render_tasks/blur.hpp +++ b/framework/opengl_renderer/src/mm/opengl/render_tasks/blur.hpp @@ -17,8 +17,7 @@ namespace MM::OpenGL::RenderTasks { // this task expects to read and write to textures class Blur : public RenderTask { private: - std::shared_ptr _hShader; - std::shared_ptr _vShader; + std::shared_ptr _shader; std::unique_ptr _vertexBuffer; std::unique_ptr _vao; @@ -31,20 +30,19 @@ namespace MM::OpenGL::RenderTasks { void render(Services::OpenGLRenderer& rs, Engine& engine) override; public: - const char* vertexPath = "shader/blur_render_task/vert.glsl"; - const char* fragmentHPath = "shader/blur_render_task/frag_h.glsl"; - const char* fragmentVPath = "shader/blur_render_task/frag_v.glsl"; + const char* vertexPath {"shader/blur_render_task/vert.glsl"}; + const char* fragmentPath {"shader/blur_render_task/frag_h.glsl"}; - std::string out_fbo = "blur_io"; - std::string temp_fbo = "blur_temp"; + std::string out_fbo {"blur_io"}; + std::string temp_fbo {"blur_temp"}; // bc of it beeing a 2 pass, we need to flipflop - std::string in_tex = "blur_io"; - std::string out_tex = "blur_io"; - std::string temp_tex = "blur_temp"; + std::string in_tex {"blur_io"}; + std::string out_tex {"blur_io"}; + std::string temp_tex {"blur_temp"}; - // determines the kernel lookup offset. "ideal" is 1/tex_size - glm::vec2 tex_offset {0.001f, 0.001f}; + // kernel lookup offset factor + glm::vec2 tex_offset_factor {1.f, 1.f}; private: void setupShaderFiles(void); diff --git a/framework/opengl_renderer/src/mm/opengl/render_tasks/composition.cpp b/framework/opengl_renderer/src/mm/opengl/render_tasks/composition.cpp new file mode 100644 index 0000000..d3863e0 --- /dev/null +++ b/framework/opengl_renderer/src/mm/opengl/render_tasks/composition.cpp @@ -0,0 +1,164 @@ +#include "./composition.hpp" + +#include +#include +#include + +#include + +namespace MM::OpenGL::RenderTasks { + +Composition::Composition(MM::Engine& engine) { + float vertices[] = { + -1.f, 1.f, + -1.f, -1.f, + 1.f, -1.f, + 1.f, -1.f, + 1.f, 1.f, + -1.f, 1.f, + }; + + _vertexBuffer = std::make_unique(vertices, 2 * 6 * sizeof(float), GL_STATIC_DRAW); + _vao = std::make_unique(); + _vao->bind(); + _vertexBuffer->bind(GL_ARRAY_BUFFER); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); + + _vertexBuffer->unbind(GL_ARRAY_BUFFER); + _vao->unbind(); + + setupShaderFiles(); + _shader = MM::OpenGL::Shader::createF(engine, vertexPath, fragmentPath); + assert(_shader != nullptr); +} + +Composition::~Composition(void) { +} + +void Composition::render(MM::Services::OpenGLRenderer& rs, MM::Engine& engine) { + ZoneScopedN("RenderTasks::Composition::render"); + + rs.targets[target_fbo]->bind(MM::OpenGL::FrameBufferObject::W); + { // TODO: move to fbo + GLsizei width {0}; + GLsizei height {0}; + { // get size of fb <.< + auto& fbo_tex_arr = rs.targets[target_fbo]->_texAttachments; + if (fbo_tex_arr.empty()) { + //GLint o_type {0}; + //glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &o_type); + //if (o_type == GL_NONE) { + //// default framebuffer or error + //SPDLOG_INFO("gl none"); + //} + + // nah, just assume screen res + std::tie(width, height) = engine.getService().getWindowSize(); + } else { + auto& target_fbo_tex = rs.targets[target_fbo]->_texAttachments.front(); + width = target_fbo_tex->width; + height = target_fbo_tex->height; + } + } + glViewport(0, 0, width, height); + } + + + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + _shader->bind(); + _vertexBuffer->bind(GL_ARRAY_BUFFER); + _vao->bind(); + + auto& rm = MM::ResourceManager::ref(); + + auto tex_a = rm.get(entt::hashed_string::value(color_tex.c_str())); + tex_a->bind(0); + + auto tex_n = rm.get(entt::hashed_string::value(bloom_tex.c_str())); + tex_n->bind(1); + + // assign image units + _shader->setUniform1i("color_tex", 0); + _shader->setUniform1i("bloom_tex", 1); + + _shader->setUniform1f("bloom_factor", bloom_factor); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + _vao->unbind(); + _vertexBuffer->unbind(GL_ARRAY_BUFFER); + _shader->unbind(); +} + +void Composition::reloadShaders(MM::Engine& engine) { + auto tmp_shader = MM::OpenGL::Shader::createF(engine, vertexPath, fragmentPath); + if (tmp_shader) { + _shader = tmp_shader; + } +} + +void Composition::setupShaderFiles(void) { + FS_CONST_MOUNT_FILE(vertexPath, +GLSL_VERSION_STRING +R"( +#ifdef GL_ES + precision mediump float; +#endif + +in vec2 _vertexPosition; +out vec2 _uv; + +void main() { + gl_Position = vec4(_vertexPosition, 0, 1); + _uv = vec2(_vertexPosition.x * 0.5 + 0.5, _vertexPosition.y * 0.5 + 0.5); +})") + + FS_CONST_MOUNT_FILE(fragmentPath, +GLSL_VERSION_STRING +R"( +#ifdef GL_ES + precision mediump float; +#endif + +#include "/shaders/builtin/tonemapping.glsl" + +uniform sampler2D color_tex; +uniform sampler2D bloom_tex; + +// high bc 32bit on cpu +uniform highp float bloom_factor; + +in vec2 _uv; + +out vec3 _out_color; + +void main() { + vec3 color = texture(color_tex, _uv).rgb; + vec3 bloom = texture(bloom_tex, _uv).rgb; + + vec3 comp = color + bloom * vec3(bloom_factor); + +#if 0 + const vec3 tint = vec3(1.5, 0.8, 1.1); + comp *= tint; +#endif + + //// reinhart + gamma + //_out_color = pow(comp / (comp + vec3(1.0)), vec3(1.0 / 2.2)); + + // makes more sense pre bloom + //comp *= vec3(pow(2.0, -1.0)); // pre expose, -1 fstops + + //_out_color = tonemapReinhard(comp); + _out_color = tonemapACESFilm(comp); // looks right + //_out_color = pow(tonemapACESFilm(pow(comp, vec3(2.2))), vec3(1.0/2.2)); // insane saturation o.O + //_out_color = pow(tonemapACESFilm(comp), vec3(1.0/2.2)); // looks just wrong +})") +} + +} // MM::OpenGL::RenderTasks + diff --git a/framework/opengl_renderer/src/mm/opengl/render_tasks/composition.hpp b/framework/opengl_renderer/src/mm/opengl/render_tasks/composition.hpp new file mode 100644 index 0000000..685141d --- /dev/null +++ b/framework/opengl_renderer/src/mm/opengl/render_tasks/composition.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include + +namespace MM::OpenGL::RenderTasks { + + class Composition : public MM::OpenGL::RenderTask { + private: + std::shared_ptr _shader; + std::unique_ptr _vertexBuffer; + std::unique_ptr _vao; + + public: + Composition(MM::Engine& engine); + ~Composition(void); + + const char* name(void) override { return "Composition"; } + + void render(MM::Services::OpenGLRenderer& rs, MM::Engine& engine) override; + + public: + const char* vertexPath {"shader/composition_render_task/vert.glsl"}; + const char* fragmentPath {"shader/composition_render_task/frag.glsl"}; + + std::string target_fbo {"display"}; + + std::string color_tex {"hdr_color"}; + std::string bloom_tex {"bloom"}; + + float bloom_factor {1.f}; + + void reloadShaders(MM::Engine& engine); + + private: + void setupShaderFiles(void); + }; + +} // MM::OpenGL::RenderTasks + diff --git a/framework/opengl_renderer/test/CMakeLists.txt b/framework/opengl_renderer/test/CMakeLists.txt index b1463cf..9648f84 100644 --- a/framework/opengl_renderer/test/CMakeLists.txt +++ b/framework/opengl_renderer/test/CMakeLists.txt @@ -160,3 +160,30 @@ target_link_libraries(fast_sky_render_task_test add_test(NAME fast_sky_render_task_test COMMAND fast_sky_render_task_test) +################# hdr bloom example + +add_executable(hdr_bloom_pipeline_example ./hdr_bloom_pipeline_example.cpp) + +target_link_libraries(hdr_bloom_pipeline_example + opengl_renderer_s + organizer_scene + clear_render_task + blit_fb_render_task + simple_rect_render_task + + #bloom_extraction_render_task + #blur_render_task + #bloom_combine_render_task + bloom + composition_render_task + + simple_velocity_system + transform_system + + random + + gtest_main +) + +add_test(NAME hdr_bloom_pipeline_example COMMAND hdr_bloom_pipeline_example) + diff --git a/framework/opengl_renderer/test/hdr_bloom_pipeline_example.cpp b/framework/opengl_renderer/test/hdr_bloom_pipeline_example.cpp new file mode 100644 index 0000000..28a110f --- /dev/null +++ b/framework/opengl_renderer/test/hdr_bloom_pipeline_example.cpp @@ -0,0 +1,291 @@ +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +//#include +//#include +//#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +using namespace entt::literals; + +const char* argv0; + +static void setup_textures(MM::Engine& engine) { + auto& rm_t = MM::ResourceManager::ref(); + auto [w, h] = engine.getService().getWindowSize(); + + // we dont have a gbuffer in this example + { // gbuffer + // depth +#ifdef MM_OPENGL_3_GLES + rm_t.reload( + "depth", + GL_DEPTH_COMPONENT24, // d16 is the onlyone for gles 2 (TODO: test for 3) + w, h, + GL_DEPTH_COMPONENT, GL_UNSIGNED_INT + ); +#else + rm_t.reload( + "depth", + GL_DEPTH_COMPONENT32F, // TODO: stencil? + w, h, + GL_DEPTH_COMPONENT, GL_FLOAT + ); +#endif + +#if 0 + // albedo + rm_t.reload( + "albedo", +#ifdef MM_OPENGL_3_GLES + GL_RGB565, +#else + GL_RGBA8, // waste of A +#endif + w, h, + GL_RGB, GL_UNSIGNED_BYTE + ); +#endif + +#if 0 + // normal + rm_t.reload( + "normal", + // prolly fine, but need to remapp to -1,1 +#ifdef MM_OPENGL_3_GLES + GL_RGB565, +#else + GL_RGBA8, // waste of A +#endif + w, h, + GL_RGB, GL_UNSIGNED_BYTE + ); +#endif + } + + const float render_scale = 1.f; + + // hdr color gles3 / webgl2 + rm_t.reload( + "hdr_color", + GL_RGBA16F, + w * render_scale, h * render_scale, + GL_RGBA, + GL_HALF_FLOAT + ); + { // filter + rm_t.get("hdr_color"_hs)->bind(0); + 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_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } +} + +static void setup_fbos(MM::Engine& engine) { + auto& rs = engine.getService(); + auto& rm_t = MM::ResourceManager::ref(); + + const float render_scale = 1.f; + +#if 0 + rs.targets["clear_opaque"] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get("albedo"_hs), GL_COLOR_ATTACHMENT0) + .attachTexture(rm_t.get("opaque_depth"_hs), GL_DEPTH_ATTACHMENT) + .setResize(true) + .finish(); + assert(rs.targets["clear_opaque"]); + + rs.targets["clear_opaque_normal"] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get("normal"_hs), GL_COLOR_ATTACHMENT0) + .setResize(true) + .finish(); + assert(rs.targets["clear_opaque"]); + + rs.targets["opaque"] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get("albedo"_hs), GL_COLOR_ATTACHMENT0) + .attachTexture(rm_t.get("normal"_hs), GL_COLOR_ATTACHMENT1) + .attachTexture(rm_t.get("opaque_depth"_hs), GL_DEPTH_ATTACHMENT) + .setResize(true) + .finish(); + assert(rs.targets["opaque"]); + + rs.targets["tmp_read"] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get("normal"_hs), GL_COLOR_ATTACHMENT0) + .setResize(false) + .finish(); + assert(rs.targets["tmp_read"]); + + rs.targets["depth_read"] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get("opaque_depth"_hs), GL_DEPTH_ATTACHMENT) + .setResize(false) + .finish(); + assert(rs.targets["depth_read"]); + + rs.targets["deferred_shading"] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get("hdr_color"_hs), GL_COLOR_ATTACHMENT0) + .setResize(true) + .finish(); + assert(rs.targets["deferred_shading"]); +#endif + rs.targets["game_view"] = MM::OpenGL::FBOBuilder::start() + .attachTexture(rm_t.get("hdr_color"_hs), GL_COLOR_ATTACHMENT0) + .attachTexture(rm_t.get("depth"_hs), GL_DEPTH_ATTACHMENT) + .setResizeFactors(render_scale, render_scale) + .setResize(true) + .finish(); + assert(rs.targets["game_view"]); +} + +TEST(hdr_bloom_pipeline, it) { + MM::Engine engine; + + auto& sdl_ss = engine.addService(); + ASSERT_TRUE(engine.enableService()); + + sdl_ss.createGLWindow("hdr_bloom_pipeline_example", 1280, 720); + + engine.addService(); + ASSERT_TRUE(engine.enableService()); + + bool provide_ret = engine.provide(); + ASSERT_TRUE(provide_ret); + auto& scene = engine.tryService()->getScene(); + + engine.addService(argv0, "hdr_bloom_pipeline_example"); + ASSERT_TRUE(engine.enableService()); + + auto& rs = engine.addService(); + ASSERT_TRUE(engine.enableService()); + + { // setup rendering + // TODO: split vertically + setup_textures(engine); + setup_fbos(engine); + + // clear + auto& clear_opaque = rs.addRenderTask(engine); + clear_opaque.target_fbo = "game_view"; + // clears all color attachments + clear_opaque.r = 0.f; + clear_opaque.g = 0.f; + clear_opaque.b = 0.f; + clear_opaque.a = 1.f; + clear_opaque.mask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT; + + { // render, whatever + MM::OpenGL::RenderTasks::SimpleRect& bsrr_rend = rs.addRenderTask(engine); + bsrr_rend.target_fbo = "game_view"; + } + + // rn does rt too + MM::OpenGL::setup_bloom(engine); + + // not part of setup_bloom + auto& comp = rs.addRenderTask(engine); + comp.color_tex = "hdr_color"; + comp.bloom_tex = "blur_tmp1"; + comp.target_fbo = "display"; + } + + scene.on_construct().connect<&entt::registry::emplace_or_replace>(); + scene.on_construct().connect<&entt::registry::emplace_or_replace>(); + scene.on_construct().connect<&entt::registry::emplace_or_replace>(); + scene.on_construct().connect<&entt::registry::emplace_or_replace>(); + + scene.on_update().connect<&entt::registry::emplace_or_replace>(); + scene.on_update().connect<&entt::registry::emplace_or_replace>(); + scene.on_update().connect<&entt::registry::emplace_or_replace>(); + scene.on_update().connect<&entt::registry::emplace_or_replace>(); + scene.on_update().connect<&entt::registry::emplace_or_replace>(); // in this example only rotation is touched + + + // setup v system + auto& org = scene.set(); + org.emplace("simple_rotational_velocity_patching"); + org.emplace("position3d_from_2d"); + org.emplace("transform3d_translate"); + org.emplace("transform3d_rotate2d"); + org.emplace("transform3d_scale2d"); + org.emplace("transform_clear_dirty"); + + + // HACK: instead you would switch to this scene + engine.getService().updateOrganizerVertices(scene); + + MM::Random::SRNG rng{42}; + + for (int i = 0; i < 5; i++) { + auto e = scene.create(); + auto& p = scene.emplace(e); + p.pos.x = 0.f; + p.pos.y = 25.f - i * 10.f; + + auto& s = scene.emplace(e); + s.scale = {50.f, i*0.2f + 0.1f}; + + auto& col = scene.emplace(e); + col.color = {3.f, 3.f, 3.f, 1.f}; + } + + for (int i = 0; i < 10; i++) { + auto e = scene.create(); + auto& p = scene.emplace(e); + p.pos.x = i * 9.f - 40; + + // zoffset is created by event + + auto& s = scene.emplace(e); + s.scale = {5,5}; + + scene.emplace(e); + + auto& v = scene.emplace(e); + v.rot_vel = i * 0.3f; + + auto& col = scene.emplace(e); + col.color = {rng.zeroToOne()*2.f, rng.zeroToOne()*2.f, rng.zeroToOne()*2.f, 1.f}; + } + + engine.run(); +} + +int main(int argc, char** argv) { + argv0 = argv[0]; + + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} +