From 361cdaf87f5a1158fb3e006bcb0c27e79b76cf55 Mon Sep 17 00:00:00 2001
From: Mathias Paulin <mathias.paulin@irit.fr>
Date: Wed, 14 Apr 2021 12:08:19 +0200
Subject: [PATCH] Prototype an image processing (2D) pass

---
 src/libRender/CMakeLists.txt                  |   2 +
 .../RadiumNBR/Passes/ImageProcessPass.cpp     | 140 ++++++++++++++++++
 .../RadiumNBR/Passes/ImageProcessPass.hpp     |  59 ++++++++
 .../RadiumNBR/Renderer/Visualization.cpp      |  25 +++-
 .../RadiumNBR/Renderer/Visualization.hpp      |   6 +
 .../ImageProcess.frag.glsl                    |  77 ++++++++++
 .../ImageProcess.vert.glsl                    |   7 +
 7 files changed, 315 insertions(+), 1 deletion(-)
 create mode 100644 src/libRender/RadiumNBR/Passes/ImageProcessPass.cpp
 create mode 100644 src/libRender/RadiumNBR/Passes/ImageProcessPass.hpp
 create mode 100644 src/libRender/RadiumNBR/Shaders/ImageProcessingPass/ImageProcess.frag.glsl
 create mode 100644 src/libRender/RadiumNBR/Shaders/ImageProcessingPass/ImageProcess.vert.glsl

diff --git a/src/libRender/CMakeLists.txt b/src/libRender/CMakeLists.txt
index fd86e22..4bb346a 100644
--- a/src/libRender/CMakeLists.txt
+++ b/src/libRender/CMakeLists.txt
@@ -41,6 +41,7 @@ set(sources
     RadiumNBR/Passes/AccessibilityBufferPass.cpp
     RadiumNBR/Passes/EnvLightPass.cpp
     RadiumNBR/Passes/EmissivityPass.cpp
+    RadiumNBR/Passes/ImageProcessPass.cpp
     RadiumNBR/Passes/LocalLightPass.cpp
     RadiumNBR/Passes/TransparencyPass.cpp
     RadiumNBR/Passes/UiPass.cpp
@@ -62,6 +63,7 @@ set(public_headers
     RadiumNBR/Passes/AccessibilityBufferPass.hpp
     RadiumNBR/Passes/EmissivityPass.hpp
     RadiumNBR/Passes/EnvLightPass.hpp
+    RadiumNBR/Passes/ImageProcessPass.hpp
     RadiumNBR/Passes/LocalLightPass.hpp
     RadiumNBR/Passes/TransparencyPass.hpp
     RadiumNBR/Passes/UiPass.hpp
diff --git a/src/libRender/RadiumNBR/Passes/ImageProcessPass.cpp b/src/libRender/RadiumNBR/Passes/ImageProcessPass.cpp
new file mode 100644
index 0000000..690b507
--- /dev/null
+++ b/src/libRender/RadiumNBR/Passes/ImageProcessPass.cpp
@@ -0,0 +1,140 @@
+#include <RadiumNBR/Passes/ImageProcessPass.hpp>
+
+#ifdef PASSES_LOG
+#    include <Core/Utils/Log.hpp>
+using namespace Ra::Core::Utils; // log
+#endif
+
+#include <Core/Geometry/MeshPrimitives.hpp>
+
+#include <Engine/Data/Mesh.hpp>
+#include <Engine/Data/ShaderConfigFactory.hpp>
+#include <Engine/Data/ShaderProgramManager.hpp>
+#include <Engine/Data/Texture.hpp>
+#include <Engine/Data/ViewingParameters.hpp>
+#include <Engine/RadiumEngine.hpp>
+
+#include <globjects/Framebuffer.h>
+
+namespace RadiumNBR {
+using namespace gl;
+
+static const GLenum buffer = { GL_COLOR_ATTACHMENT0 };
+
+ImageProcessPass::ImageProcessPass( const Ra::Core::Utils::Index& idx ) :
+    RenderPass( "Image processing pass", idx, nullptr ) {}
+
+ImageProcessPass::~ImageProcessPass() = default;
+
+
+bool ImageProcessPass::initializePass( size_t width,
+                                       size_t height,
+                                       Ra::Engine::Data::ShaderProgramManager* shaderMngr ) {
+    // Initialize the fbo
+    m_fbo     = std::make_unique<globjects::Framebuffer>();
+
+    Ra::Engine::Data::TextureParameters texparams;
+    texparams.width     = width;
+    texparams.height    = height;
+    texparams.target    = GL_TEXTURE_2D;
+    texparams.minFilter = GL_LINEAR;
+    texparams.magFilter = GL_LINEAR;
+    texparams.internalFormat = GL_RGBA32F;
+    texparams.format         = GL_RGBA;
+    texparams.type = GL_FLOAT;
+    texparams.name = "ImageProcess::Result";
+    m_sharedTextures.insert(
+        {texparams.name, std::make_shared<Ra::Engine::Data::Texture>( texparams )} );
+
+    // The shader caller
+    Ra::Core::Geometry::TriangleMesh mesh =
+        Ra::Core::Geometry::makeZNormalQuad( Ra::Core::Vector2( -1.f, 1.f ) );
+    auto qm = std::make_unique<Ra::Engine::Data::Mesh>( "caller" );
+    qm->loadGeometry( std::move( mesh ) );
+    m_quadMesh = std::move( qm );
+    m_quadMesh->updateGL();
+
+    // The shader
+    // TODO, based on the same approach than in CustomAttribToColorPass,
+    //  in the future, this shader might be user-defined
+    std::string resourcesRootDir = getResourcesDir();
+    auto added                   = shaderMngr->addShaderProgram(
+        {{"ImageProcess(LIC)"},
+         resourcesRootDir + "Shaders/ImageProcessingPass/ImageProcess.vert.glsl",
+         resourcesRootDir + "Shaders/ImageProcessingPass/ImageProcess.frag.glsl"} );
+    if ( added ) { m_shader = added.value(); }
+    else
+    { return false; }
+
+    return true;
+}
+
+
+bool ImageProcessPass::update() {
+    // nothing to do
+    return true;
+}
+
+void ImageProcessPass::resize( size_t width, size_t height ) {
+    for ( auto& t : m_sharedTextures )
+    {
+        t.second->resize( width, height );
+    }
+
+    m_fbo->bind();
+    m_fbo->attachTexture( GL_COLOR_ATTACHMENT0, m_sharedTextures["ImageProcess::Result"]->texture() );
+#ifdef PASSES_LOG
+    if ( m_fbo->checkStatus() != GL_FRAMEBUFFER_COMPLETE )
+    { LOG( logERROR ) << "FBO Error (GeomPrePass::resize): " << m_fbo->checkStatus(); }
+#endif
+
+    globjects::Framebuffer::unbind();
+}
+
+void ImageProcessPass::execute(
+    const Ra::Engine::Data::ViewingParameters& viewParams ) const {
+    m_fbo->bind();
+    GL_ASSERT( glDrawBuffers( 1, &buffer ) );
+    GL_ASSERT( glClearBufferfv( GL_COLOR, 0, Ra::Core::Utils::Color::White().data() ) );
+    GL_ASSERT( glDisable( GL_DEPTH_TEST ) );
+    GL_ASSERT( glDepthMask( GL_FALSE ) );
+
+    m_shader->bind();
+    Ra::Core::Matrix4 viewproj = viewParams.projMatrix * viewParams.viewMatrix;
+
+    m_shader->setUniform( "transform.mvp", viewproj );
+    m_shader->setUniform( "transform.proj", viewParams.projMatrix );
+    m_shader->setUniform( "transform.view", viewParams.viewMatrix );
+
+    const auto td = m_importedTextures.find( "ImageProcess::DepthSampler" );
+    m_shader->setUniform( "depth_sampler", td->second.get(), 0 );
+    const auto tc = m_importedTextures.find( "ImageProcess::ColorSampler" );
+    m_shader->setUniform( "image_sampler", tc->second.get(), 1 );
+
+    m_quadMesh->render( m_shader );
+    GL_ASSERT( glEnable( GL_DEPTH_TEST ) );
+    GL_ASSERT( glDepthMask( GL_TRUE ) );
+    m_fbo->unbind();
+
+}
+
+/// These inputs must be computed before executing this pass
+/// positions and normals must be computed in world space
+void ImageProcessPass::setInputs( const SharedTextures& depthBuffer,
+                                  const SharedTextures& colorBuffer ) {
+    m_importedTextures["ImageProcess::DepthSampler"] = depthBuffer.second;
+    m_importedTextures["ImageProcess::ColorSampler"]   = colorBuffer.second;
+}
+
+void ImageProcessPass::setOutput( const SharedTextures& colorBuffer ) {
+    m_outputTexture = colorBuffer;
+}
+
+// Nothing to do, this pass is screen space
+bool ImageProcessPass::buildRenderTechnique(
+    const Ra::Engine::Rendering::RenderObject* ro,
+    Ra::Engine::Rendering::RenderTechnique& rt ) const {
+    return true;
+};
+
+}
diff --git a/src/libRender/RadiumNBR/Passes/ImageProcessPass.hpp b/src/libRender/RadiumNBR/Passes/ImageProcessPass.hpp
new file mode 100644
index 0000000..169ddbe
--- /dev/null
+++ b/src/libRender/RadiumNBR/Passes/ImageProcessPass.hpp
@@ -0,0 +1,59 @@
+#pragma once
+#include <RadiumNBR/RenderPass.hpp>
+
+#include <Engine/Data/DisplayableObject.hpp>
+
+namespace Ra::Engine::Data {
+class ShaderProgram;
+} // namespace Ra::Engine::Data
+
+namespace globjects {
+class Framebuffer;
+}
+
+namespace RadiumNBR {
+/// Render pass do 2D processing on input textures to generate a resulting color texture.
+class ImageProcessPass : public RenderPass
+{
+  public:
+    explicit ImageProcessPass( const Ra::Core::Utils::Index& idx );
+    ~ImageProcessPass() override;
+
+    bool buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                               Ra::Engine::Rendering::RenderTechnique& rt ) const override;
+    bool initializePass( size_t width,
+                         size_t height,
+                         Ra::Engine::Data::ShaderProgramManager* shaderMngr ) override;
+    bool update() override;
+    void resize( size_t width, size_t height ) override;
+    void execute( const Ra::Engine::Data::ViewingParameters& viewParams ) const override;
+
+    /// Add the output colorBuffer.
+    /// \warning the output color buffer must be different from the input one
+    /// \todo allow to have same input/output color buffer
+    void setOutput( const SharedTextures& colorBuffer );
+
+    /// These inputs must be computed before executing this pass : depth buffer
+    void setInputs( const SharedTextures& depthBuffer, const SharedTextures& colorBuffer );
+
+    void rebuildShaders();
+
+  private:
+    /// The framebuffer used to render this pass
+    std::unique_ptr<globjects::Framebuffer> m_fbo{nullptr};
+
+    /// The Shader manager to use when building shaders
+    Ra::Engine::Data::ShaderProgramManager* m_shaderMngr{nullptr};
+
+    /// The color texture for output.Stored here for easy access.
+    SharedTextures m_outputTexture;
+
+    /// The quad to be drawn for shader invocation
+    std::unique_ptr<Ra::Engine::Data::Displayable> m_quadMesh{nullptr};
+
+    /// The shader that process the image (the Radium shader manager has ownership)
+    const Ra::Engine::Data::ShaderProgram* m_shader{nullptr};
+
+
+};
+} // namespace RadiumNBR
diff --git a/src/libRender/RadiumNBR/Renderer/Visualization.cpp b/src/libRender/RadiumNBR/Renderer/Visualization.cpp
index c415fc7..333d32d 100644
--- a/src/libRender/RadiumNBR/Renderer/Visualization.cpp
+++ b/src/libRender/RadiumNBR/Renderer/Visualization.cpp
@@ -3,6 +3,7 @@
 #include <RadiumNBR/Passes/ClearPass.hpp>
 #include <RadiumNBR/Passes/CustomAttribToColorPass.hpp>
 #include <RadiumNBR/Passes/GeomPrepass.hpp>
+#include <RadiumNBR/Passes/ImageProcessPass.hpp>
 #include <RadiumNBR/Renderer/Visualization.hpp>
 
 #include <Core/Utils/Log.hpp>
@@ -36,7 +37,7 @@ void VisualizationController::configure( RadiumNBR::NodeBasedRenderer* renderer,
         // set the output of the pass : clear the renderer Linear RGB output texture
         m_clearPass->setOutput( *colortexture );
         // set the clear color (pass internal state)
-        m_clearPass->setBackground( {0.5, 0.1, 0.1} /*renderer->getBackgroundColor()*/ );
+        m_clearPass->setBackground( renderer->getBackgroundColor() );
         m_clearPass->initializePass( w, h, shaderManager );
         // add the pass to the renderer and activate it
         renderer->addPass( m_clearPass, m_clearPass->index() );
@@ -70,6 +71,28 @@ void VisualizationController::configure( RadiumNBR::NodeBasedRenderer* renderer,
         m_customPass->activate();
     }
     //! [Adding a CustomAttribToColorPass pass]
+
+    //! [Adding an image processing pass]
+    {
+        // pass that draw no object and is positioned at rank 0
+        m_imageProcessPass = std::make_shared<RadiumNBR::ImageProcessPass>(3 );
+        // set the output of the pass : clear the renderer Linear RGB output texture
+        auto vectorFieldTex = renderer->sharedTextures().find( "CustomAtt2Clr::VectorField" );
+        m_imageProcessPass->setInputs( *depthtexture, *vectorFieldTex );
+        m_imageProcessPass->setOutput( *colortexture );
+        // configure access to shader/resources files and initialize the pass
+        m_imageProcessPass->setResourcesDir( resourcesPath );
+        m_imageProcessPass->initializePass( w, h, shaderManager );
+        // add the pass to the renderer and activate it
+        renderer->addPass( m_imageProcessPass, m_imageProcessPass->index() );
+        m_imageProcessPass->activate();
+        // Add the exported (shared) texture to the set of available textures
+        auto &sharedTextures = renderer->sharedTextures();
+        for (auto t : m_imageProcessPass->getOutputImages() ) {
+            sharedTextures.insert( {t.first, t.second} );
+        }
+    }
+    //! [Adding a clear-screen pass]
 }
 
 void VisualizationController::update( const Ra::Engine::Data::ViewingParameters& ) {
diff --git a/src/libRender/RadiumNBR/Renderer/Visualization.hpp b/src/libRender/RadiumNBR/Renderer/Visualization.hpp
index 9f18f05..8296e46 100644
--- a/src/libRender/RadiumNBR/Renderer/Visualization.hpp
+++ b/src/libRender/RadiumNBR/Renderer/Visualization.hpp
@@ -5,6 +5,7 @@
 namespace RadiumNBR {
 class CustomAttribToColorPass;
 class ClearPass;
+class ImageProcessPass;
 
 /**
  * This class parameterize the renderer just after the OpenGL system was initialized.
@@ -53,6 +54,11 @@ class NodeBasedRenderer_LIBRARY_API VisualizationController
 
     /// The clear pass for the background
     std::shared_ptr<RadiumNBR::ClearPass> m_clearPass;
+
+    /// The image processing pass to render vector field
+    std::shared_ptr<RadiumNBR::ImageProcessPass> m_imageProcessPass;
+
+
     /// TODO : this default are the same as in the CustomAttribToColorPass. Use them instead of redefining here
     std::string m_vertexFunction{"void outputCustomAttribs() {\n}\n"};
     std::string m_geometryFunction{"void propagateAttributes(){}\n"};
diff --git a/src/libRender/RadiumNBR/Shaders/ImageProcessingPass/ImageProcess.frag.glsl b/src/libRender/RadiumNBR/Shaders/ImageProcessingPass/ImageProcess.frag.glsl
new file mode 100644
index 0000000..721692b
--- /dev/null
+++ b/src/libRender/RadiumNBR/Shaders/ImageProcessingPass/ImageProcess.frag.glsl
@@ -0,0 +1,77 @@
+layout (location = 0) out vec4 out_color;
+
+uniform sampler2D depth_sampler;
+uniform sampler2D image_sampler;
+in vec2 varTexcoord;
+
+
+// replace the nois texture : TODO verify the properties of the generated noise
+float whiteNoise(in vec2 at, in vec2 scale) {
+    return (noise1 (at * scale)  + 0.5) * 0.5;
+}
+
+/*
+// LIC function to be adapted ...
+//desc : texture de direction (vecteurs)
+// invertdir pour faire "perpendiculaire" a desc,
+// noise une texture de bruit "poivre et sel"
+// sw, sh : screensize
+vec4 lic(in sampler2D desc,in bool invertdir) {
+    const int halfsize = 10;
+    vec2 coord;
+    vec2 dir = texture2D(desc,gl_TexCoord[0].st).xy;
+    //vec2 dir = texelFetch(desc,ivec2(gl_TexCoord[0].st*1.0/vec2(sw,sh)),0).xy;
+    if(invertdir)
+    dir = vec2(dir.y,-dir.x);
+    vec4 res = texture2D(noise,gl_TexCoord[0].st);
+    vec2 currentdir;
+    vec2 tmpdir;
+    coord = gl_TexCoord[0].st;
+    currentdir = dir;
+    for(int i=1;i<=halfsize;i++) {
+        coord  = coord+vec2(currentdir.x*sw,currentdir.y*sh);
+        res   += texture2D(noise,coord);
+        tmpdir = texture2D(desc,coord).xy;
+        //tmpdir = texelFetch(desc,ivec2(coord*1.0/vec2(sw,sh)),0).xy;
+        if(invertdir)
+        tmpdir = vec2(tmpdir.y,-tmpdir.x);
+        currentdir = tmpdir;
+    }
+    coord = gl_TexCoord[0].st;
+    currentdir = dir;
+    for(int i=1;i<=halfsize;i++) {
+        coord  = coord-vec2(currentdir.x*sw,currentdir.y*sh);
+        res   += texture2D(noise,coord);
+        tmpdir = texture2D(desc,coord).xy;
+        //tmpdir = texelFetch(desc,ivec2(coord*1.0/vec2(sw,sh)),0).xy;
+        if(invertdir)
+        tmpdir = vec2(tmpdir.y,-tmpdir.x);
+        currentdir = tmpdir;
+    }
+    res = res/(2.0f*float(halfsize)+1.0f);
+    return vec4(res.x);
+}
+
+*/
+
+void main() {
+    /*
+    // 4x4 filtering for test
+    const int half_width = 2;
+    vec2 texelSize = 1.0 / vec2(textureSize(image_sampler, 0));
+    vec3 result = vec3(0.0);
+    for (int x = -half_width; x < half_width; ++x)
+    {
+        for (int y = -half_width; y < half_width; ++y)
+        {
+            vec2 offset = vec2(float(x), float(y)) * texelSize;
+            result += texture(image_sampler, varTexcoord + offset).rgb;
+        }
+    }
+    out_color = vec4(result / (4 * half_width * half_width), 1);
+    */
+
+    vec2 imgSize = vec2(textureSize(image_sampler, 0));
+    out_color = vec4( vec3( whiteNoise( varTexcoord, imgSize) ), 1);
+}
+
diff --git a/src/libRender/RadiumNBR/Shaders/ImageProcessingPass/ImageProcess.vert.glsl b/src/libRender/RadiumNBR/Shaders/ImageProcessingPass/ImageProcess.vert.glsl
new file mode 100644
index 0000000..c540dd1
--- /dev/null
+++ b/src/libRender/RadiumNBR/Shaders/ImageProcessingPass/ImageProcess.vert.glsl
@@ -0,0 +1,7 @@
+layout (location = 0) in vec3 in_position;
+out vec2 varTexcoord;
+void main(void)
+{
+    gl_Position = vec4(in_position.xyz, 1.0);
+    varTexcoord = (in_position.xy + 1.0) / 2.0;
+}
-- 
GitLab