From fbc1df781256085f7a705f7cfa26239292edb27e Mon Sep 17 00:00:00 2001
From: Mathias Paulin <mathias.paulin@irit.fr>
Date: Wed, 24 Mar 2021 18:57:27 +0100
Subject: [PATCH] Add a lighting pass with customization of final color
 computation.

---
 src/DemoApp/main.cpp                          | 302 ++++++++++++++++--
 src/libRender/CMakeLists.txt                  |   2 +
 src/libRender/RadiumNBR/NodeBasedRenderer.hpp |   3 +
 .../Passes/CustomAttribToColorPass.cpp        | 247 ++++++++++++++
 .../Passes/CustomAttribToColorPass.hpp        |  66 ++++
 5 files changed, 590 insertions(+), 30 deletions(-)
 create mode 100644 src/libRender/RadiumNBR/Passes/CustomAttribToColorPass.cpp
 create mode 100644 src/libRender/RadiumNBR/Passes/CustomAttribToColorPass.hpp

diff --git a/src/DemoApp/main.cpp b/src/DemoApp/main.cpp
index 3326b89..8082e91 100644
--- a/src/DemoApp/main.cpp
+++ b/src/DemoApp/main.cpp
@@ -2,50 +2,73 @@
 #include <Gui/BaseApplication.hpp>
 #include <Gui/RadiumWindow/SimpleWindowFactory.hpp>
 
+// To manage the scene
+#include <Core/Geometry/MeshPrimitives.hpp>
+#include <Engine/Scene/EntityManager.hpp>
+#include <Engine/Scene/GeometryComponent.hpp>
+#include <Engine/Scene/GeometrySystem.hpp>
+
 // Include the customizable renderer system (only the used part here)
 #include <RadiumNBR/NodeBasedRenderer.hpp>
 #include <RadiumNBR/Passes/ClearPass.hpp>
+#include <RadiumNBR/Passes/CustomAttribToColorPass.hpp>
 #include <RadiumNBR/Passes/GeomPrepass.hpp>
-#include <RadiumNBR/Passes/WireframePass.hpp>
 
+// To add attribs to loaded objects
+#include <Engine/Data/Mesh.hpp>
+#include <Engine/Rendering/RenderObject.hpp>
+#include <Engine/Rendering/RenderObjectManager.hpp>
+#include <Engine/Scene/EntityManager.hpp>
+#include <Engine/Scene/SystemDisplay.hpp>
+
+#include <Core/Utils/Log.hpp>
+using namespace Ra::Core::Utils; // log
 
 /**
  * This class parameterize the renderer just after the OpenGL system was initialized.
- * In the functor, The OpenGL context of the drawing window is activated.
+ * when a method of this controler is called, the OpenGL context of the drawing window is activated.
  */
-class RendererController : public RadiumNBR::RenderControlFunctor {
+class RendererController : public RadiumNBR::RenderControlFunctor
+{
   public:
-
-    void configure(RadiumNBR::NodeBasedRenderer *renderer, int w, int h) override {
-        LOG( Ra::Core::Utils::logINFO ) << "Customizing the renderer " << renderer->getRendererName();
+    /*
+     * Called once : configure the renderer by adding passes and allocating controler resources
+     */
+    void configure( RadiumNBR::NodeBasedRenderer* renderer, int w, int h ) override {
+        LOG( Ra::Core::Utils::logINFO )
+            << "Customizing the renderer " << renderer->getRendererName();
 
         //! [Caching some helpers and data from the Engine and the renderer]
         auto resourcesCheck = Ra::Core::Resources::getResourcesPath(
-            reinterpret_cast<void*>( &RadiumNBR::NodeBasedRendererMagic ), { "Resources/RadiumNBR" } );
+            reinterpret_cast<void*>( &RadiumNBR::NodeBasedRendererMagic ),
+            {"Resources/RadiumNBR"} );
         if ( !resourcesCheck )
         {
             LOG( Ra::Core::Utils::logERROR ) << "Unable to find resources for NodeBasedRenderer!";
             return;
-        } else {
-            LOG( Ra::Core::Utils::logINFO ) << "NodeBasedRenderer Resources are at " << *resourcesCheck;
         }
-        auto resourcesPath{ *resourcesCheck };
+        else
+        {
+            LOG( Ra::Core::Utils::logINFO )
+                << "NodeBasedRenderer Resources are at " << *resourcesCheck;
+        }
+        auto resourcesPath{*resourcesCheck};
         auto shaderManager = Ra::Engine::RadiumEngine::getInstance()->getShaderProgramManager();
-        auto colortexture = renderer->sharedTextures().find( "Linear RGB (RadiumNBR)");
-        auto depthtexture = renderer->sharedTextures().find( "Depth (RadiumNBR)" );
+        auto colortexture  = renderer->sharedTextures().find( "Linear RGB (RadiumNBR)" );
+        auto depthtexture  = renderer->sharedTextures().find( "Depth (RadiumNBR)" );
         //! [Caching some helpers and data from the Engine and the renderer]
 
         //! [Adding a clear-screen pass]
         {
-            // pass that draw no object and is positionned at rank 0
+            // pass that draw no object and is positioned at rank 0
             auto pass = std::make_shared<RadiumNBR::ClearPass>( nullptr, 0 );
             // set the output of the pass : clear the renderer Linear RGB output texture
             pass->setOutput( *colortexture );
             // set the clear color (pass internal state)
-            pass->setBackground( renderer->getBackgroundColor());
+            pass->setBackground( renderer->getBackgroundColor() );
             pass->initializePass( w, h, shaderManager );
             // add the pass to the renderer and activate it
-            renderer->addPass(pass, pass->index());
+            renderer->addPass( pass, pass->index() );
             pass->activate();
         }
         //! [Adding a clear-screen pass]
@@ -60,28 +83,65 @@ class RendererController : public RadiumNBR::RenderControlFunctor {
             pass->setResourcesDir( resourcesPath );
             pass->initializePass( w, h, shaderManager );
             // add the pass to the renderer and activate it
-            renderer->addPass(pass, pass->index());
+            renderer->addPass( pass, pass->index() );
             pass->activate();
         }
         //! [Adding a Z-only pass]
 
-        //! [Adding a wireframe pass]
+        //! [Adding a CustomAttribToColorPass pass]
         {
-            // this pass draw all the objects in wareframe, using the shared z-buffer for hidden line removal and drawing
-            // in the shared color buffer.
-            auto pass = std::make_shared<RadiumNBR::WireframePass>( renderer->allRenderObjects(), 2 );
+            // this pass draw all the objects using the custom color function,
+            // Rendering is done against the shared z-buffer for hidden line removal and
+            // into the shared color buffer.
+            m_customPass = std::make_shared<RadiumNBR::CustomAttribToColorPass>(
+                renderer->allRenderObjects(), 2 );
             // set the input/output textures
-            pass->setInputs( *depthtexture );
-            pass->setOutput( *colortexture );
+            m_customPass->setInputs( *depthtexture );
+            m_customPass->setOutput( *colortexture );
+            m_customPass->setLightManager( renderer->getLightManager() );
+            m_customPass->setAttribToColorFunc( m_vertexFunction, m_fragmentFunction );
             // configure access to shader/resources files and initialize the pass
-            pass->setResourcesDir( resourcesPath );
-            pass->initializePass( w, h, shaderManager );
+            m_customPass->setResourcesDir( resourcesPath );
+            m_customPass->initializePass( w, h, shaderManager );
             // add the pass to the renderer and activate it
-            renderer->addPass(pass, pass->index());
-            pass->activate();
+            renderer->addPass( m_customPass, m_customPass->index() );
+            m_customPass->activate();
         }
-        //! [Adding a wireframe pass]
+        //! [Adding a CustomAttribToColorPass pass]
+    }
+
+    void update( const Ra::Engine::Data::ViewingParameters& ) override{
+        /* This function is called once before each frame.
+         * You can use it to modify the scene, or modify the computation function of the renderer
+         * For this, just call
+         * m_customPass->setAttribToColorFunc( customVertexAttrib, customFragmentColor );
+         * with the wanted shader glsl source code.
+         */
+    };
+
+    void resize( int w, int h ) override{
+        /* if you have resizeable resources in your controller, this method is called each time the
+         * viewer window is resized so that you can dimension your resources accordingly
+         */
+    };
+
+    /// Set the custom glsl function for attrib management (vertex) and colorcomputation (fragment)
+    void setAttribToColorFunc( const std::string& vertex_source,
+                               const std::string& fragment_source ) {
+        m_vertexFunction   = vertex_source;
+        m_fragmentFunction = fragment_source;
+        if ( m_customPass )
+        { m_customPass->setAttribToColorFunc( m_vertexFunction, m_fragmentFunction ); }
     }
+
+  private:
+    /// The custom pass if needed for modification
+    std::shared_ptr<RadiumNBR::CustomAttribToColorPass> m_customPass;
+
+    ///
+    std::string m_vertexFunction{"void outputCustomAttribs() {}"};
+    std::string m_fragmentFunction{"vec4 computeCustomColor(Material mat, vec3 lightDir, vec3 "
+                                   "viewDir) {return vec4(1, 0, 0, 1);}"};
 };
 
 /**
@@ -90,19 +150,142 @@ class RendererController : public RadiumNBR::RenderControlFunctor {
 class DemoWindowFactory : public Ra::Gui::BaseApplication::WindowFactory
 {
   public:
-    DemoWindowFactory() = delete;
+    DemoWindowFactory()  = delete;
     ~DemoWindowFactory() = default;
-    explicit DemoWindowFactory( std::shared_ptr<RadiumNBR::NodeBasedRenderer> r) : renderer(r) {}
+    explicit DemoWindowFactory( std::shared_ptr<RadiumNBR::NodeBasedRenderer> r ) : renderer( r ) {}
     inline Ra::Gui::MainWindowInterface* createMainWindow() const override {
         auto window = new Ra::Gui::SimpleWindow();
-        window->addRenderer(renderer->getRendererName(), renderer);
+        window->addRenderer( renderer->getRendererName(), renderer );
         return window;
     }
+
   private:
     std::shared_ptr<RadiumNBR::NodeBasedRenderer> renderer;
 };
 
+/** Process the scene to add the custom attribs to all objects
+ */
+void AddCustomAttributeToMeshes() {
+    auto entityManager = Ra::Engine::RadiumEngine::getInstance()->getEntityManager();
+    auto entities      = entityManager->getEntities();
+    auto roManager     = Ra::Engine::RadiumEngine::getInstance()->getRenderObjectManager();
+
+    for ( const auto e : entities )
+    {
+        if ( e == Ra::Engine::Scene::SystemEntity::getInstance() ) { continue; }
+        for ( const auto& c : e->getComponents() )
+        {
+            for ( const auto& roIdx : c->m_renderObjects )
+            {
+                const auto& ro = roManager->getRenderObject( roIdx );
+                if ( ro->getType() == Ra::Engine::Rendering::RenderObjectType::Geometry )
+                {
+                    auto theMesh = ro->getMesh();
+
+                    /* Could be helpfull if one can do
+                    auto displayable = dynamic_cast<Ra::Engine::Data::CoreGeometryDisplayable< ???
+                    >*>( theMesh.get() );
+                     */
+                    auto displayMesh = dynamic_cast<Ra::Engine::Data::Mesh*>( theMesh.get() );
+                    if ( displayMesh )
+                    {
+                        LOG( logINFO ) << "\t\tMesh " << displayMesh->getName()
+                                       << " (TriangleMesh) is processed.";
+                        displayMesh->addAttrib<Ra::Core::Vector4>(
+                            "myCustomAttrib",
+                            Ra::Core::Vector4Array{displayMesh->getNumVertices(),
+                                                   Ra::Core::Utils::Color::Yellow()} );
+                        displayMesh->setDirty( "myCustomAttrib" );
+                    }
+                    else
+                    {
+                        auto polyMesh = dynamic_cast<Ra::Engine::Data::PolyMesh*>( theMesh.get() );
+                        if ( polyMesh )
+                        {
+                            LOG( logINFO ) << "\t\tMesh " << polyMesh->getName()
+                                           << " (PolyMesh) is processed.";
+                            polyMesh->addAttrib<Ra::Core::Vector4>(
+                                "myCustomAttrib",
+                                Ra::Core::Vector4Array{polyMesh->getNumVertices(),
+                                                       Ra::Core::Utils::Color::Magenta()} );
+                            polyMesh->setDirty( "myCustomAttrib" );
+                        }
+                        else
+                        {
+                            auto pointCloud =
+                                dynamic_cast<Ra::Engine::Data::PointCloud*>( theMesh.get() );
+                            if ( pointCloud )
+                            {
+                                LOG( logINFO ) << "\t\tPointCloud " << pointCloud->getName()
+                                               << " is processed.";
+                                pointCloud->addAttrib<Ra::Core::Vector4>(
+                                    "myCustomAttrib",
+                                    Ra::Core::Vector4Array{pointCloud->getNumVertices(),
+                                                           Ra::Core::Utils::Color::Blue()} );
+                                pointCloud->setDirty( "myCustomAttrib" );
+                            }
+                            else
+                            {
+                                LOG( logINFO ) << "\t\tMesh " << theMesh->getName()
+                                               << " is nor a trianglemesh, nor a polymesh, nor a "
+                                                  "point cloud ... skipping";
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+/**
+ * The custom color computation shaders
+ */
+
+/* Vertex shader : here, the objective is to transform any set ov vertex
+ * attribute to any set of fragment shaders input.
+ * In this example, we just copy the custom attrib to the output.
+ * All standards attribs can be accessed using their Radium name.
+ * If needed, all these vectors are expresed in the world frame :
+ * out_viewVector, out_lightVector, out_position, out_normal (if available)
+ * if available from the underlying geometry, the following parameters can be used :
+ * out_texcoord, out_vertexcolor.
+ *
+ * Note that this string could also be loaded from a file and/or manually edited if you
+ * add an edition widget as in Radium app demo
+ */
+const std::string customVertexAttrib{"in vec4 myCustomAttrib;\n"
+                                     "out vec4 fragCustomAttrib;\n"
+                                     "\nvoid outputCustomAttribs() { \n"
+                                     "fragCustomAttrib=myCustomAttrib;\n"
+                                     "}\n"};
 
+/* Fragment shader : here, the objective is to compute the final color of the fragment.
+ * This function get the Material associated with the objet. This material could be
+ * used through the Radium GLSL BSDF interface (see Radium doc)
+ * All standard interpolated attributes from vertices can be fetched using the Radium
+ * GLSL VertexAttribInterface (see Radium doc)
+ * The Light could be used through the Radium GLSL light interface.
+ *
+ * In the following exemple, the lighting is computed using the custom attrib as
+ * diffuse reflectance.
+ *
+ * Note that this string could also be loaded from a file and/or manually edited if you
+ * add an edition widget as in Radium app demo
+ */
+const std::string customFragmentColor{
+    "in vec4 fragCustomAttrib;\n"
+    "\nvec4 computeCustomColor(Material mat, vec3 lightDir, vec3 viewDir) {\n"
+    "vec3 diffColor; \n"
+    "vec3 specColor; \n"
+    "getSeparateBSDFComponent( mat, getPerVertexTexCoord(), lightDir,  viewDir,\n"
+    "vec3(0, 0, 1), diffColor, specColor );\n"
+    "return vec4( (specColor+fragCustomAttrib.rgb)*max(lightDir.z, 0), 1); \n"
+    "}\n"};
+
+/**
+ * main function.
+ */
 int main( int argc, char* argv[] ) {
     if ( argc < 2 )
     {
@@ -112,6 +295,7 @@ int main( int argc, char* argv[] ) {
     //! [Instatiating the renderer giving a customization functor]
     RendererController renderControl;
     auto renderer = std::make_shared<RadiumNBR::NodeBasedRenderer>( renderControl );
+    renderControl.setAttribToColorFunc( customVertexAttrib, customFragmentColor );
     //! [Instatiating the renderer giving a customization functor]
 
     //! [Instatiating the application]
@@ -123,5 +307,63 @@ int main( int argc, char* argv[] ) {
     app.initialize( DemoWindowFactory( renderer ) );
     //! [Initializing the application]
 
+    //! [Processing the scene to define custom attributes]
+    AddCustomAttributeToMeshes();
+    //! [Processing the scene to define custom attributes]
+
+    return app.exec();
+}
+
+#if 0
+int main( int argc, char* argv[] ) {
+
+    //! [Instatiating the renderer giving a customization functor]
+    RendererController renderControl;
+    auto renderer = std::make_shared<RadiumNBR::NodeBasedRenderer>( renderControl );
+
+    //! [Instatiating the renderer giving a customization functor]
+
+    //! [Instatiating the application]
+    Ra::Gui::BaseApplication app( argc, argv );
+    //! [Instatiating the application]
+
+    //! [Initializing the application]
+    // The customization functor is called here
+    app.initialize( DemoWindowFactory( renderer ) );
+    //! [Initializing the application]
+
+    //! [show the base grid on the renderer]
+    renderer->showDebug( false );
+    //! [show the base grid on the renderer]
+
+    //! [Creating the cube]
+    auto cube = Ra::Core::Geometry::makeSharpBox( {0.1f, 0.1f, 0.1f} );
+    //! [Creating the cube]
+
+    //! [Colorize the Cube]
+    cube.addAttrib(
+        "myCustomAttrib",
+        Ra::Core::Vector4Array{cube.vertices().size(), Ra::Core::Utils::Color::Yellow()} );
+    //! [Colorize the Cube]
+
+    //! [Create the engine entity for the cube]
+    auto e = app.m_engine->getEntityManager()->createEntity( "Green cube" );
+    //! [Create the engine entity for the cube]
+
+    //! [Create a geometry component with the cube]
+    auto c =
+        new Ra::Engine::Scene::TriangleMeshComponent( "Cube Mesh", e, std::move( cube ), nullptr );
+    //! [Create a geometry component with the cube]
+
+    //! [Register the entity/component association to the geometry system ]
+    auto geometrySystem = app.m_engine->getSystem( "GeometrySystem" );
+    geometrySystem->addComponent( e, c );
+    //! [Register the entity/component association to the geometry system ]
+
+    //! [Tell the window that something is to be displayed]
+    app.m_mainWindow->prepareDisplay();
+    //! [Tell the window that something is to be displayed]
+
     return app.exec();
 }
+#endif
diff --git a/src/libRender/CMakeLists.txt b/src/libRender/CMakeLists.txt
index fc67a72..7853fff 100644
--- a/src/libRender/CMakeLists.txt
+++ b/src/libRender/CMakeLists.txt
@@ -34,6 +34,7 @@ set(sources
     RadiumNBR/FullFeatureRenderer.cpp
     RadiumNBR/SphereSampler.cpp
     RadiumNBR/Passes/ClearPass.cpp
+    RadiumNBR/Passes/CustomAttribToColorPass.cpp
     RadiumNBR/Passes/DebugPass.cpp
     RadiumNBR/Passes/GeomPrepass.cpp
     RadiumNBR/Passes/AccessibilityBufferPass.cpp
@@ -53,6 +54,7 @@ set(public_headers
     RadiumNBR/RenderPass.hpp
     RadiumNBR/SphereSampler.hpp
     RadiumNBR/Passes/ClearPass.hpp
+    RadiumNBR/Passes/CustomAttribToColorPass.hpp
     RadiumNBR/Passes/DebugPass.hpp
     RadiumNBR/Passes/GeomPrepass.hpp
     RadiumNBR/Passes/AccessibilityBufferPass.hpp
diff --git a/src/libRender/RadiumNBR/NodeBasedRenderer.hpp b/src/libRender/RadiumNBR/NodeBasedRenderer.hpp
index b7dfb13..0c7950d 100644
--- a/src/libRender/RadiumNBR/NodeBasedRenderer.hpp
+++ b/src/libRender/RadiumNBR/NodeBasedRenderer.hpp
@@ -88,6 +88,9 @@ class NodeBasedRenderer_LIBRARY_API NodeBasedRenderer : public Ra::Engine::Rende
     /// Hide or show the Debug objects
     void showDebug( bool b );
 
+    /// Access the default light manager
+    Ra::Engine::Scene::LightManager* getLightManager() { return m_lightmanagers[0]; }
+
   protected:
     void initializeInternal() override;
     void resizeInternal() override;
diff --git a/src/libRender/RadiumNBR/Passes/CustomAttribToColorPass.cpp b/src/libRender/RadiumNBR/Passes/CustomAttribToColorPass.cpp
new file mode 100644
index 0000000..34674aa
--- /dev/null
+++ b/src/libRender/RadiumNBR/Passes/CustomAttribToColorPass.cpp
@@ -0,0 +1,247 @@
+#include <RadiumNBR/Passes/CustomAttribToColorPass.hpp>
+
+#ifdef PASSES_LOG
+#    include <Core/Utils/Log.hpp>
+using namespace Ra::Core::Utils; // log
+#endif
+
+#include <Engine/Data/Material.hpp>
+#include <Engine/Data/ShaderConfigFactory.hpp>
+#include <Engine/Data/ShaderProgramManager.hpp>
+#include <Engine/Data/Texture.hpp>
+#include <Engine/Rendering/RenderObject.hpp>
+#include <Engine/Scene/LightManager.hpp>
+
+#include <globjects/Framebuffer.h>
+
+namespace RadiumNBR {
+using namespace gl;
+
+static const GLenum buffers[] = {GL_COLOR_ATTACHMENT0};
+
+CustomAttribToColorPass::CustomAttribToColorPass(
+    const std::vector<RenderObjectPtr>* objectsToRender,
+    const Ra::Core::Utils::Index& idx ) :
+    RenderPass( "Custom AttribToColor pass", idx, objectsToRender ) {}
+
+CustomAttribToColorPass::~CustomAttribToColorPass() = default;
+
+bool CustomAttribToColorPass::initializePass( size_t /* width */,
+                                              size_t /* height */,
+                                              Ra::Engine::Data::ShaderProgramManager* shaderMngr ) {
+    m_shaderMngr = shaderMngr;
+    m_fbo        = std::make_unique<globjects::Framebuffer>();
+
+    return true;
+}
+
+void CustomAttribToColorPass::setInputs( const SharedTextures& depthBuffer ) {
+    addImportedTextures( {"CustomAtt2Clr::Depth", depthBuffer.second} );
+}
+
+void CustomAttribToColorPass::setOutput( const SharedTextures& colorBuffer ) {
+    m_outputTexture = colorBuffer;
+}
+
+bool CustomAttribToColorPass::update() {
+
+    return true;
+}
+
+void CustomAttribToColorPass::resize( size_t width, size_t height ) {
+
+    // Only resize the owned textures. Imported one are resized by their owner
+    for ( auto& t : m_localTextures )
+    {
+        t.second->resize( width, height );
+    }
+
+    for ( auto& t : m_sharedTextures )
+    {
+        t.second->resize( width, height );
+    }
+
+    m_fbo->bind();
+    m_fbo->attachTexture( GL_DEPTH_ATTACHMENT,
+                          m_importedTextures["CustomAtt2Clr::Depth"]->texture() );
+    m_fbo->attachTexture( GL_COLOR_ATTACHMENT0, m_outputTexture.second->texture() );
+#ifdef PASSES_LOG
+    if ( m_fbo->checkStatus() != GL_FRAMEBUFFER_COMPLETE )
+    { LOG( logERROR ) << "FBO Error (EmissivityPass::resize): " << m_fbo->checkStatus(); }
+#endif
+    // finished with fbo, undbind to bind default
+    globjects::Framebuffer::unbind();
+}
+
+void CustomAttribToColorPass::execute(
+    const Ra::Engine::Data::ViewingParameters& viewParams ) const {
+
+    m_fbo->bind();
+    // only draw into 1 buffers (Color)
+    GL_ASSERT( glDrawBuffers( 1, buffers ) );
+    GL_ASSERT( glDepthMask( GL_FALSE ) );
+    GL_ASSERT( glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ) );
+
+    GL_ASSERT( glEnable( GL_DEPTH_TEST ) );
+    GL_ASSERT( glDepthFunc( GL_LEQUAL ) );
+    GL_ASSERT( glEnable( GL_BLEND ) );
+
+    glBlendFunc( GL_ONE, GL_ONE );
+    // To prevent z-fighting, remove polygon offset
+    glDisable( GL_POLYGON_OFFSET_FILL );
+
+    if ( m_lightmanager->count() > 0 )
+    {
+        // for ( const auto& l : m_lights )
+        for ( size_t i = 0; i < m_lightmanager->count(); ++i )
+        {
+            Ra::Engine::Data::RenderParameters passParams;
+            passParams.concatParameters( m_passParams );
+            const auto l = m_lightmanager->getLight( i );
+            l->getRenderParameters( passParams );
+
+            for ( const auto& ro : *m_objectsToRender )
+            {
+                ro->render( passParams, viewParams, passIndex() );
+            }
+        }
+    }
+    // need to maintain global state invariant
+    glEnable( GL_POLYGON_OFFSET_FILL );
+
+    GL_ASSERT( glDisable( GL_BLEND ) );
+}
+
+bool CustomAttribToColorPass::buildRenderTechnique(
+    const Ra::Engine::Rendering::RenderObject* ro,
+    Ra::Engine::Rendering::RenderTechnique& rt ) const {
+    if ( m_needConfigRebuild )
+    {
+        m_needConfigRebuild = false;
+        Ra::Engine::Data::ShaderConfigurationFactory::removeConfiguration(
+            {"CustomAtt2ClrPass::CustomColorProgram"} );
+    }
+    std::string resourcesRootDir = getResourcesDir();
+    auto mat = const_cast<Ra::Engine::Rendering::RenderObject*>( ro )->getMaterial();
+    // Volumes are not allowed here
+    if ( mat->getMaterialAspect() == Ra::Engine::Data::Material::MaterialAspect::MAT_DENSITY )
+    { return false; }
+    if ( auto cfg = Ra::Engine::Data::ShaderConfigurationFactory::getConfiguration(
+             {"CustomAtt2ClrPass::CustomColorProgram"} ) )
+    { rt.setConfiguration( *cfg, passIndex() ); }
+    else
+    {
+        // This part is the default configuration for this pass
+        // In this shader, the definition of the function void outputCustomAttrib(), along the
+        // declaration of input attribs and output result must be appended to the source.
+        // e.g.
+        // in vec3 attribName;
+        // out vec3 frag_attribName;
+        // void outputCustomAttrib() {
+        //    frag_attribName = attribName;
+        // }
+        const std::string vertexShaderSource{
+            "#include \"TransformStructs.glsl\"\n"
+            "#include \"DefaultLight.glsl\"\n"
+            "layout (location = 0) in vec3 in_position;\n"
+            "layout (location = 1) in vec3 in_normal;\n"
+            "layout (location = 2) in vec3 in_texcoord;\n"
+            "layout (location = 3) in vec3 in_vertexcolor;\n"
+            "#ifndef DONT_USE_INPUT_TANGENT\n"
+            "layout( location = 4 ) in vec3 in_tangent;\n"
+            "#endif\n"
+            "layout (location = 0) out vec3 out_position;\n"
+            "layout (location = 1) out vec3 out_normal;\n"
+            "layout (location = 2) out vec3 out_texcoord;\n"
+            "layout (location = 3) out vec3 out_vertexcolor;\n"
+            "#ifndef DONT_USE_INPUT_TANGENT\n"
+            "layout( location = 4 ) out vec3 out_tangent;\n"
+            "#endif\n"
+            "layout (location = 5) out vec3 out_viewVector;\n"
+            "layout (location = 6) out vec3 out_lightVector;\n"
+            "uniform Transform transform;\n"
+            "void outputCustomAttribs();"
+            "void main() {\n"
+            "mat4 mvp = transform.proj * transform.view * transform.model;\n"
+            "gl_Position = mvp * vec4(in_position, 1.0);\n"
+            "vec4 pos = transform.model * vec4(in_position, 1.0);\n"
+            "pos /= pos.w;\n"
+            "vec3 normal = mat3(transform.worldNormal) * in_normal;\n"
+            "vec3 tangent = mat3(transform.model) * in_tangent;\n"
+            "vec3 eye = -transform.view[3].xyz * mat3(transform.view);\n"
+            "out_position    = vec3(pos);\n"
+            "out_normal      = normal;\n"
+            "out_texcoord    = in_texcoord;\n"
+            "out_vertexcolor = in_vertexcolor;\n"
+            "#ifndef DONT_USE_INPUT_TANGENT\n"
+            "out_tangent     = tangent;\n"
+            "#endif\n"
+            "out_viewVector  = vec3(eye - out_position);\n"
+            "out_lightVector = getLightDirection(light, out_position);\n"
+            "outputCustomAttribs();"
+            "}\n"};
+        // the function computeCustomColor, alongside the declaration of frag_attrib is appended to
+        // the source
+        const std::string fragmentShadersource{
+            "#include \"DefaultLight.glsl\"\n"
+            "#include \"VertexAttribInterface.frag.glsl\"\n"
+            "layout (location = 5) in vec3 in_viewVector;\n"
+            "layout (location = 6) in vec3 in_lightVector;\n"
+            "layout (location = 0) out vec4 out_color;\n"
+            "vec4 computeCustomColor(Material mat, vec3 light_dir, vec3 view_dir);\n"
+            "void main()\n"
+            "{\n"
+            "// All vectors are in world space\n"
+            "// A material is always evaluated in the fragment local Frame  \n"
+            "// compute matrix from World to local Frame                    \n"
+            "vec3 normalWorld     = getWorldSpaceNormal();// normalized interpolated normal   \n"
+            "vec3 tangentWorld    = getWorldSpaceTangent();// normalized tangent            \n"
+            "vec3 binormalWorld   = getWorldSpaceBiTangent();// normalized bitangent     \n"
+            "// discard non opaque fragment                                        \n"
+            "vec4 bc = getDiffuseColor(material, getPerVertexTexCoord());      \n"
+            "if (toDiscard(material, bc))             \n"
+            "discard;              \n"
+            "// Apply normal mapping   \n"
+            "normalWorld         = getNormal(material, getPerVertexTexCoord(),       \n"
+            "normalWorld, tangentWorld, binormalWorld);// normalized bump-mapped normal   \n"
+            "binormalWorld         = normalize(cross(normalWorld, tangentWorld));// normalized "
+            "tangent      \n"
+            "tangentWorld         = normalize(cross(binormalWorld, normalWorld));// normalized "
+            "bitangent  \n"
+            "mat3 world2local;             \n"
+            "world2local[0]  = vec3(tangentWorld.x, binormalWorld.x, normalWorld.x);      \n"
+            "world2local[1]  = vec3(tangentWorld.y, binormalWorld.y, normalWorld.y);    \n"
+            "world2local[2]  = vec3(tangentWorld.z, binormalWorld.z, normalWorld.z);  \n"
+            "// transform all vectors in local frame so that N = (0, 0, 1);   \n"
+            "vec3 lightDir = normalize(world2local * in_lightVector);// incident direction   \n"
+            "vec3 viewDir = normalize(world2local * in_viewVector);// outgoing direction\n"
+            "out_color = computeCustomColor(material, lightDir, viewDir);\n"
+            "}\n"};
+
+        Ra::Engine::Data::ShaderConfiguration theConfig{"CustomAtt2ClrPass::CustomColorProgram"};
+        theConfig.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX,
+                                   vertexShaderSource + m_customVertexAttrib );
+        theConfig.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
+                                   fragmentShadersource + m_customFragmentColor );
+        theConfig.addInclude( "\"" + mat->getMaterialName() + ".glsl\"",
+                              Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT );
+        // Add to the ShaderConfigManager
+        Ra::Engine::Data::ShaderConfigurationFactory::addConfiguration( theConfig );
+        // Add to the RenderTechnique
+        rt.setConfiguration( theConfig, passIndex() );
+    }
+    rt.setParametersProvider( mat, passIndex() );
+    return true;
+}
+
+void CustomAttribToColorPass::setAttribToColorFunc( const std::string& vertex_source,
+                                                    const std::string& fragment_source ) {
+    m_customVertexAttrib  = vertex_source;
+    m_customFragmentColor = fragment_source;
+    m_needConfigRebuild   = true;
+}
+
+void CustomAttribToColorPass::setLightManager( const Ra::Engine::Scene::LightManager* lm ) {
+    m_lightmanager = lm;
+}
+} // namespace RadiumNBR
diff --git a/src/libRender/RadiumNBR/Passes/CustomAttribToColorPass.hpp b/src/libRender/RadiumNBR/Passes/CustomAttribToColorPass.hpp
new file mode 100644
index 0000000..59b0e2f
--- /dev/null
+++ b/src/libRender/RadiumNBR/Passes/CustomAttribToColorPass.hpp
@@ -0,0 +1,66 @@
+#pragma once
+#include <RadiumNBR/RenderPass.hpp>
+
+#include <Core/Utils/Color.hpp>
+
+namespace Ra::Engine::Scene {
+class LightManager;
+}
+
+namespace Ra::Engine::Data {
+class ShaderProgram;
+} // namespace Ra::Engine::Data
+
+namespace globjects {
+class Framebuffer;
+}
+
+namespace RadiumNBR {
+/// Render pass that draws objects using a custom color computation function (in glsl)
+class CustomAttribToColorPass : public RenderPass
+{
+  public:
+    CustomAttribToColorPass( const std::vector<RenderObjectPtr>* objectsToRender,
+                   const Ra::Core::Utils::Index& idx );
+    ~CustomAttribToColorPass() 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
+    void setOutput( const SharedTextures& colorBuffer );
+
+    /// These inputs must be computed before executing this pass : depth buffer
+    void setInputs( const SharedTextures& depthBuffer );
+
+    void setLightManager( const Ra::Engine::Scene::LightManager* lm );
+
+    /// Set the custom glsl function for attrib management (vertex) and colorcomputation (fragment)
+    void setAttribToColorFunc(const std::string &vertex_source, const std::string & fragment_source);
+  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 custom Attrib Vertex shader function
+    std::string m_customVertexAttrib{};
+    /// the custom FragmentColor shader function
+    std::string m_customFragmentColor{};
+    /// State indicator
+    mutable bool m_needConfigRebuild{true};
+
+    /// The light manager to use
+    const Ra::Engine::Scene::LightManager* m_lightmanager;
+};
+} // namespace RadiumNBR
-- 
GitLab