From 0d50d1cf456ca73a808c48fda6ce7888501a62a3 Mon Sep 17 00:00:00 2001
From: Nessim DAHMANI <ndahmani@bramer.irit.fr>
Date: Tue, 8 Feb 2022 14:19:42 +0100
Subject: [PATCH] adding new premade nodes

---
 proposal/APIPreSpecification.md               |  96 +++++++++++
 src/MaraBoutage/RadiumPlayer.cpp              |  34 +++-
 src/libRender/CMakeLists.txt                  |  16 ++
 .../RadiumNBR/NodeGraph/NodeFactory.cpp       |  54 ++++++
 .../PremadeNodes/AntiAliasing/FXAANode.cpp    | 163 ++++++++++++++++++
 .../PremadeNodes/AntiAliasing/FXAANode.hpp    |  34 ++++
 .../Compositing/LessThanThresholdNode.cpp     | 111 ++++++++++++
 .../Compositing/LessThanThresholdNode.hpp     |  36 ++++
 .../PremadeNodes/Compositing/MaxNode.cpp      | 113 ++++++++++++
 .../PremadeNodes/Compositing/MaxNode.hpp      |  34 ++++
 .../PremadeNodes/Compositing/MinNode.cpp      | 113 ++++++++++++
 .../PremadeNodes/Compositing/MinNode.hpp      |  34 ++++
 .../Compositing/MoreThanThresholdNode.cpp     | 111 ++++++++++++
 .../Compositing/MoreThanThresholdNode.hpp     |  36 ++++
 .../PremadeNodes/Compositing/MultiplyNode.cpp | 113 ++++++++++++
 .../PremadeNodes/Compositing/MultiplyNode.hpp |  34 ++++
 .../PremadeNodes/Compositing/OneMinusNode.cpp | 104 +++++++++++
 .../PremadeNodes/Compositing/OneMinusNode.hpp |  34 ++++
 .../PremadeNodes/PremadeNodesIncludes.hpp     |   8 +
 19 files changed, 1274 insertions(+), 4 deletions(-)
 create mode 100644 proposal/APIPreSpecification.md
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.cpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.hpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.cpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.hpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.cpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.hpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.cpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.hpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.cpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.hpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.cpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.hpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.cpp
 create mode 100644 src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.hpp

diff --git a/proposal/APIPreSpecification.md b/proposal/APIPreSpecification.md
new file mode 100644
index 0000000..5ea9f87
--- /dev/null
+++ b/proposal/APIPreSpecification.md
@@ -0,0 +1,96 @@
+# RadiumAPI Pre-specification
+
+## Goals
+The goal is to define a graphics API to wrap existing, any level, API graphics like OpenGL, Vulkan, Direct X, Metal, ...
+
+## API level
+Being able to wrap any level of API, like an high-level API like OpenGL and a low-level API like Vulkan means that the level for the RadiumAPI must be chosen accordingly. Some operations may be noop depending on the API it wraps.
+
+Is it interesting to be able to have a control on synchronization like the Vulkan API or put calculated barriers after each operation to abstract this aspect of the low-levels API ? Is it interesting to have control on the memory chunks or just use a memory allocator for the low-level implementations, that will carry all this aspect by itself ?
+
+## Structure
+The structure of the API will define how its functions are called.
+
+It can be an abstract class, being implemented by various API.
+
+The advantage being that each implementation is defined in its own class and the interface to follow to make an implementation for another API is clear.
+
+The disadvantage is that using a class means that it needs an object to pass everywhere to use the functions.
+```C++
+class RadiumAPI {
+public:
+    virtual ~RadiumAPI() = 0;
+    
+    virtual void init() {}
+    virtual void destroy() {}
+    ...
+};
+
+class RadiumAPI_Vulkan : public RadiumAPI {
+    void init() override {
+        vkX();
+    }
+    
+    void destroy() override {
+        vkY();
+    }
+    ...
+};
+```
+
+It can be a namespace, with namespaces to choose an API.
+
+The advantage is that this version can also be implemented in different files and does not need an object to call the functions.
+
+The disadvantage is that its readability is not guaranteed.
+```C++
+namespace RadiumAPI {
+    void init() {
+        if (api == VULKAN) {
+            RadiumAPI::Vulkan::init();
+        }
+        else if (api == OPENGL) {
+            RadiumAPI::OpenGL::init();
+        }
+    }
+    
+    void destroy() {
+        if (api == VULKAN) {
+            RadiumAPI::Vulkan::destroy();
+        }
+        else if (api == OPENGL) {
+            RadiumAPI::OpenGL::destroy();
+        }
+    }
+    
+    namespace Vulkan {
+        void init() {
+            vkX();
+        }
+        
+        void destroy() {
+            vkY();
+        }
+    }
+    
+    namespace OpenGL {
+        void init() {
+            glX();
+        }
+        
+        void destroy() {
+            glY();
+        }
+    }
+}
+```
+
+## API selection
+One major point is the ability, or not, to choose the API at runtime. The surface of the window system may not accept that multiple API are used at once so being able to select the API via a command-line option may be a right option (and is the one used in many games for this reason).
+
+## Cross-platform
+This API must be cross-platform, which comes with some disadvantages. On Apple products, OpenGL 4.1 is the last version supported and MoltenVK can (at the moment) only support a certain subset of Vulkan 1.1's features (portability subset).
+
+Should the latest version of OpenGL and Vulkan be implemented to have access to the latest features on both API, and have a Metal backend for Apple products, or should it just follow Apple's limitation (OpenGL 3.1 and Vulkan 1.1) ?
+
+The Metal backend can be considered, and may be more effective on Apple products anyway but someone has to implement it.
\ No newline at end of file
diff --git a/src/MaraBoutage/RadiumPlayer.cpp b/src/MaraBoutage/RadiumPlayer.cpp
index 3cbec7d..76def20 100644
--- a/src/MaraBoutage/RadiumPlayer.cpp
+++ b/src/MaraBoutage/RadiumPlayer.cpp
@@ -255,14 +255,40 @@ void RadiumPlayer::openNodeEditor( MainWindow* mainWindow,
             ret->registerModel<NodeAdapterModel<WireframeNode>>(
                 NodeCreator<NodeAdapterModel<WireframeNode>>( renderer->getRenderGraph() ),
                 "Premade Nodes" );
-            
+
+            ret->registerModel<NodeAdapterModel<DifferenceNode>>(
+                NodeCreator<NodeAdapterModel<DifferenceNode>>( renderer->getRenderGraph() ),
+                "Compositing" );
+            ret->registerModel<NodeAdapterModel<SumNode>>(
+                NodeCreator<NodeAdapterModel<SumNode>>( renderer->getRenderGraph() ),
+                "Compositing" );
+            ret->registerModel<NodeAdapterModel<MultiplyNode>>(
+                NodeCreator<NodeAdapterModel<MultiplyNode>>( renderer->getRenderGraph() ),
+                "Compositing" );
+            ret->registerModel<NodeAdapterModel<OneMinusNode>>(
+                NodeCreator<NodeAdapterModel<OneMinusNode>>( renderer->getRenderGraph() ),
+                "Compositing" );
+            ret->registerModel<NodeAdapterModel<MaxNode>>(
+                NodeCreator<NodeAdapterModel<MaxNode>>( renderer->getRenderGraph() ),
+                "Compositing" );
+            ret->registerModel<NodeAdapterModel<MinNode>>(
+                NodeCreator<NodeAdapterModel<MinNode>>( renderer->getRenderGraph() ),
+                "Compositing" );
+            ret->registerModel<NodeAdapterModel<MoreThanThresholdNode>>(
+                NodeCreator<NodeAdapterModel<MoreThanThresholdNode>>( renderer->getRenderGraph() ),
+                "Compositing" );
+            ret->registerModel<NodeAdapterModel<LessThanThresholdNode>>(
+                NodeCreator<NodeAdapterModel<LessThanThresholdNode>>( renderer->getRenderGraph() ),
+                "Compositing" );
+
+            ret->registerModel<NodeAdapterModel<FXAANode>>(
+                NodeCreator<NodeAdapterModel<FXAANode>>( renderer->getRenderGraph() ),
+                "Anti Aliasing" );
+
             // Demo
             ret->registerModel<NodeAdapterModel<SimpleNode>>(
                 NodeCreator<NodeAdapterModel<SimpleNode>>( renderer->getRenderGraph() ),
                 "Premade Nodes" );
-            ret->registerModel<NodeAdapterModel<DifferenceNode>>(
-                NodeCreator<NodeAdapterModel<DifferenceNode>>( renderer->getRenderGraph() ),
-                "Premade Nodes" );
 
             ret->registerModel<SourceNodeModel<NodeTypeColor>>(
                 NodeCreator<SourceNodeModel<NodeTypeColor>>( renderer->getRenderGraph() ),
diff --git a/src/libRender/CMakeLists.txt b/src/libRender/CMakeLists.txt
index 237cc2f..69f7d35 100644
--- a/src/libRender/CMakeLists.txt
+++ b/src/libRender/CMakeLists.txt
@@ -50,6 +50,14 @@ set(sources
 
     RadiumNBR/NodeGraph/PremadeNodes/Compositing/DifferenceNode.cpp
     RadiumNBR/NodeGraph/PremadeNodes/Compositing/SumNode.cpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.cpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.cpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.cpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.cpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.cpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.cpp
+
+    RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.cpp
 
     RadiumNBR/NodeGraph/RenderGraph.cpp
     RadiumNBR/NodeGraph/NodeFactory.cpp
@@ -116,6 +124,14 @@ set(public_headers
 
     RadiumNBR/NodeGraph/PremadeNodes/Compositing/DifferenceNode.hpp
     RadiumNBR/NodeGraph/PremadeNodes/Compositing/SumNode.hpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.hpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.hpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.hpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.hpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.hpp
+    RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.hpp
+
+    RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.hpp
 
     RadiumNBR/NodeGraph/PremadeNodes/Demo/SimpleNode.hpp
 
diff --git a/src/libRender/RadiumNBR/NodeGraph/NodeFactory.cpp b/src/libRender/RadiumNBR/NodeGraph/NodeFactory.cpp
index 082e0b4..e18f9cc 100644
--- a/src/libRender/RadiumNBR/NodeGraph/NodeFactory.cpp
+++ b/src/libRender/RadiumNBR/NodeGraph/NodeFactory.cpp
@@ -205,6 +205,60 @@ void initializeNodeFactory() {
                 return node;
             }
         } );
+
+        NodeFactory::registerNode<SumNode>( []( const nlohmann::json& data ) {
+            auto sum =
+                new SumNode( "sum" + std::to_string( NodeFactory::newNodeId() ) );
+            return sum;
+        } );
+
+        NodeFactory::registerNode<DifferenceNode>( []( const nlohmann::json& data ) {
+            auto difference =
+                new DifferenceNode( "difference" + std::to_string( NodeFactory::newNodeId() ) );
+            return difference;
+        } );
+
+        NodeFactory::registerNode<MultiplyNode>( []( const nlohmann::json& data ) {
+            auto multiply =
+                new MultiplyNode( "multiply" + std::to_string( NodeFactory::newNodeId() ) );
+            return multiply;
+        } );
+
+        NodeFactory::registerNode<OneMinusNode>( []( const nlohmann::json& data ) {
+            auto oneMinus =
+                new OneMinusNode( "one minus" + std::to_string( NodeFactory::newNodeId() ) );
+            return oneMinus;
+        } );
+
+        NodeFactory::registerNode<MaxNode>( []( const nlohmann::json& data ) {
+            auto max =
+                new MaxNode( "max" + std::to_string( NodeFactory::newNodeId() ) );
+            return max;
+        } );
+
+        NodeFactory::registerNode<MinNode>( []( const nlohmann::json& data ) {
+            auto min =
+                new MinNode( "min" + std::to_string( NodeFactory::newNodeId() ) );
+            return min;
+        } );
+
+        NodeFactory::registerNode<MoreThanThresholdNode>( []( const nlohmann::json& data ) {
+            auto moreThanThreshold =
+                new MoreThanThresholdNode( "more than threshold" + std::to_string( NodeFactory::newNodeId() ) );
+            return moreThanThreshold;
+        } );
+
+        NodeFactory::registerNode<LessThanThresholdNode>( []( const nlohmann::json& data ) {
+            auto lessThanThreshold =
+                new LessThanThresholdNode( "less than threshold" + std::to_string( NodeFactory::newNodeId() ) );
+            return lessThanThreshold;
+        } );
+
+        NodeFactory::registerNode<FXAANode>( []( const nlohmann::json& data ) {
+            auto fxaa =
+                new FXAANode( "fxaa" + std::to_string( NodeFactory::newNodeId() ) );
+            return fxaa;
+        } );
     }
 }
 };
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.cpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.cpp
new file mode 100644
index 0000000..0308d8b
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.cpp
@@ -0,0 +1,163 @@
+#include <RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.hpp>
+
+FXAANode::FXAANode( const std::string& name ) : Node( name ) {
+    auto portInColor1 = new PortIn<NodeTypeTexture>( "inColorTexture1", this );
+    addInput( portInColor1 );
+    portInColor1->mustBeLinked();
+
+    Ra::Engine::Data::TextureParameters colorTexParams = { "Color Texture",
+                                                           gl::GL_TEXTURE_2D,
+                                                           1,
+                                                           1,
+                                                           1,
+                                                           gl::GL_RGBA,
+                                                           gl::GL_RGBA32F,
+                                                           gl::GL_FLOAT,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_LINEAR,
+                                                           gl::GL_LINEAR,
+                                                           nullptr };
+    m_colorTexture = new Ra::Engine::Data::Texture( colorTexParams );
+
+    auto portOutColorTex = new PortOut<NodeTypeTexture>( "out_texTexture", this );
+    addOutput( portOutColorTex, m_colorTexture );
+}
+
+void FXAANode::init() {
+    m_framebuffer = new globjects::Framebuffer();
+
+    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();
+
+    const std::string composeVertexShader{ "layout (location = 0) in vec3 in_position;\n"
+                                           "out vec2 varTexcoord;\n"
+                                           "void main()\n"
+                                           "{\n"
+                                           "  gl_Position = vec4(in_position, 1.0);\n"
+                                           "  varTexcoord = (in_position.xy + 1.0) * 0.5;\n"
+                                           "}\n" };
+    const std::string composeFragmentShader{
+        "layout (location = 0) out vec4 out_tex;\n"
+        "uniform sampler2D tex1_sampler;\n"
+        "in vec2 varTexcoord;\n"
+        "const float THRESHOLD = 0.0312;\n"
+        "const float RELATIVE_THRESHOLD = 0.125;\n"
+        "void main() {\n"
+        "vec2 texSize = vec2(textureSize(tex1_sampler, 0));\n"
+        "vec2 texelSize = 1.0 / texSize;\n"
+        "vec3 n = texture(tex1_sampler, varTexcoord + (vec2(0.0, -1.0) * texelSize)).rgb;\n"
+        "vec3 s = texture(tex1_sampler, varTexcoord + (vec2(0.0, 1.0) * texelSize)).rgb;\n"
+        "vec3 e = texture(tex1_sampler, varTexcoord + (vec2(1.0, 0.0) * texelSize)).rgb;\n"
+        "vec3 w = texture(tex1_sampler, varTexcoord + (vec2(-1.0, 0.0) * texelSize)).rgb;\n"
+        "vec3 m = texture(tex1_sampler, varTexcoord).rgb;\n"
+        "vec3 brightnessCoefficients = vec3(0.2126, 0.7152, 0.0722);\n"
+        "float brightnessN = dot(n, brightnessCoefficients);\n"
+        "float brightnessS = dot(s, brightnessCoefficients);\n"
+        "float brightnessE = dot(e, brightnessCoefficients);\n"
+        "float brightnessW = dot(w, brightnessCoefficients);\n"
+        "float brightnessM = dot(m, brightnessCoefficients);\n"
+        "float brightnessMin = min(brightnessM, min(min(brightnessN, brightnessS), min(brightnessE, brightnessW)));\n"
+        "float brightnessMax = max(brightnessM, max(max(brightnessN, brightnessS), max(brightnessE, brightnessW)));\n"
+        "float contrast = brightnessMax - brightnessMin;\n"
+        "float threshold = max(THRESHOLD, RELATIVE_THRESHOLD * brightnessMax);\n"
+        "if (contrast < threshold) {\n"
+        "out_tex = vec4(m, 1.0);\n"
+        "}\n"
+        "else {\n"
+        "vec3 nw = texture(tex1_sampler, varTexcoord + (vec2(-1.0, -1.0) * texelSize)).rgb;\n"
+        "vec3 ne = texture(tex1_sampler, varTexcoord + (vec2(1.0, -1.0) * texelSize)).rgb;\n"
+        "vec3 sw = texture(tex1_sampler, varTexcoord + (vec2(-1.0, 1.0) * texelSize)).rgb;\n"
+        "vec3 se = texture(tex1_sampler, varTexcoord + (vec2(1.0, 1.0) * texelSize)).rgb;\n"
+        "float brightnessNW = dot(nw, brightnessCoefficients);\n"
+        "float brightnessNE = dot(ne, brightnessCoefficients);\n"
+        "float brightnessSW = dot(sw, brightnessCoefficients);\n"
+        "float brightnessSE = dot(se, brightnessCoefficients);\n"
+        "float factor = 2 * (brightnessN + brightnessS + brightnessE + brightnessW);\n"
+        "factor += (brightnessNW + brightnessNE + brightnessSW + brightnessSE);\n"
+        "factor *= (1.0 / 12.0);\n"
+        "factor = abs(factor - brightnessM);\n"
+        "factor = clamp(factor / contrast, 0.0, 1.0);\n"
+        "factor = smoothstep(0.0, 1.0, factor);\n"
+        "factor = factor * factor;\n"
+        "float horizontal = abs(brightnessN + brightnessS - (2 * brightnessM)) * 2 +\n"
+        "abs(brightnessNE + brightnessSE - (2 * brightnessE)) +\n"
+        "abs(brightnessNW + brightnessSW - (2 * brightnessW));\n"
+        "float vertical = abs(brightnessE + brightnessW - (2 * brightnessM)) * 2 +\n"
+        "abs(brightnessNE + brightnessSE - (2 * brightnessN)) +\n"
+        "abs(brightnessNW + brightnessSW - (2 * brightnessS));\n"
+        "bool isHorizontal = horizontal > vertical;\n"
+        "float pixelStep = isHorizontal ? texelSize.y : texelSize.x;\n"
+        "float posBrightness = isHorizontal ? brightnessS : brightnessE;\n"
+        "float negBrightness = isHorizontal ? brightnessN : brightnessW;\n"
+        "float posGradient = abs(posBrightness - brightnessM);\n"
+        "float negGradient = abs(negBrightness - brightnessM);\n"
+        "pixelStep *= (posGradient < negGradient) ? -1 : 1;\n"
+        "vec2 blendUV = varTexcoord;\n"
+        "if (isHorizontal) {\n"
+        "blendUV.y = varTexcoord.y + (pixelStep * factor);\n"
+        "}\n"
+        "else {\n"
+        "blendUV.x = varTexcoord.x + (pixelStep * factor); \n"
+        "}\n"
+        "out_tex = texture(tex1_sampler, blendUV);\n"
+        "}\n"
+        "}" };
+
+    Ra::Engine::Data::ShaderConfiguration config{ "ComposeMax" };
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, composeVertexShader );
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
+                            composeFragmentShader );
+    auto added = m_shaderMngr->addShaderProgram( config );
+    if ( added ) { m_shader = added.value(); }
+}
+
+void FXAANode::update() {}
+
+void FXAANode::execute() {
+    Ra::Engine::Data::RenderParameters inPassParams;
+
+    // Texture 1
+    auto inputColor1           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[0].get() );
+    NodeTypeTexture* texColor1 = &inputColor1->getData();
+
+    m_framebuffer->bind();
+    m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() );
+
+    const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 };
+    gl::glDrawBuffers( 1, buffers );
+    gl::glDisable( gl::GL_DEPTH_TEST );
+    gl::glDepthMask( gl::GL_FALSE );
+    gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE );
+
+    float clearBlack[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+    gl::glClearBufferfv( gl::GL_COLOR, 0, clearBlack );
+
+    gl::glDisable( gl::GL_BLEND );
+
+    m_shader->bind();
+    m_shader->setUniform( "tex1_sampler", texColor1, 0 );
+
+    m_quadMesh->render( m_shader );
+
+    m_framebuffer->detach( gl::GL_COLOR_ATTACHMENT0 );
+    m_framebuffer->unbind();
+}
+
+void FXAANode::destroy() {
+    delete m_framebuffer;
+    delete m_colorTexture;
+}
+
+void FXAANode::resize( uint32_t width, uint32_t height ) {
+    m_colorTexture->resize( width, height );
+}
+
+void FXAANode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                           Ra::Engine::Rendering::RenderTechnique& rt ) const {}
+
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.hpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.hpp
new file mode 100644
index 0000000..f47a73e
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.hpp
@@ -0,0 +1,34 @@
+#pragma once
+#include <RadiumNBR/NodeGraph/Node.hpp>
+
+#include <globjects/Framebuffer.h>
+
+#include <Core/Geometry/MeshPrimitives.hpp>
+#include <Engine/Data/Mesh.hpp>
+#include <Engine/Data/RenderParameters.hpp>
+
+class NodeBasedRenderer_LIBRARY_API FXAANode : public Node
+{
+  public:
+    explicit FXAANode( const std::string& name );
+
+    void init() override;
+    void update() override;
+    void execute() override;
+    void destroy() override;
+    void resize( uint32_t width, uint32_t height ) override;
+
+    void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                               Ra::Engine::Rendering::RenderTechnique& rt ) const override;
+
+    static const std::string getTypename() { return "FXAA"; }
+
+  private:
+    Ra::Engine::Data::Texture* m_colorTexture{ nullptr };
+
+    std::unique_ptr<Ra::Engine::Data::Displayable> m_quadMesh{ nullptr };
+
+    const Ra::Engine::Data::ShaderProgram* m_shader{ nullptr };
+
+    globjects::Framebuffer* m_framebuffer{ nullptr };
+};
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.cpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.cpp
new file mode 100644
index 0000000..c797b5b
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.cpp
@@ -0,0 +1,111 @@
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.hpp>
+
+LessThanThresholdNode::LessThanThresholdNode( const std::string& name ) : Node( name ) {
+    auto portInColor1 = new PortIn<NodeTypeTexture>( "inColorTexture1", this );
+    addInput( portInColor1 );
+    portInColor1->mustBeLinked();
+
+    Ra::Engine::Data::TextureParameters colorTexParams = { "Color Texture",
+                                                           gl::GL_TEXTURE_2D,
+                                                           1,
+                                                           1,
+                                                           1,
+                                                           gl::GL_RGBA,
+                                                           gl::GL_RGBA32F,
+                                                           gl::GL_FLOAT,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_LINEAR,
+                                                           gl::GL_LINEAR,
+                                                           nullptr };
+    m_colorTexture = new Ra::Engine::Data::Texture( colorTexParams );
+
+    auto portOutColorTex = new PortOut<NodeTypeTexture>( "outColorTexture", this );
+    addOutput( portOutColorTex, m_colorTexture );
+
+    auto editableThreshold = new EditableParameter<float>( "threshold", &m_editableThreshold );
+    editableThreshold->addAdditionalData(0.0f);
+    editableThreshold->addAdditionalData(1.0f);
+    addEditableParameter( editableThreshold );
+}
+
+void LessThanThresholdNode::init() {
+    m_framebuffer = new globjects::Framebuffer();
+
+    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();
+
+    const std::string composeVertexShader{ "layout (location = 0) in vec3 in_position;\n"
+                                           "out vec2 varTexcoord;\n"
+                                           "void main()\n"
+                                           "{\n"
+                                           "  gl_Position = vec4(in_position, 1.0);\n"
+                                           "  varTexcoord = (in_position.xy + 1.0) * 0.5;\n"
+                                           "}\n" };
+    const std::string composeFragmentShader{
+        "layout (location = 0) out vec4 out_tex;\n"
+        "uniform sampler2D tex1_sampler;\n"
+        "uniform float threshold;\n"
+        "in vec2 varTexcoord;\n"
+        "void main() {\n"
+        "   out_tex = dot(texture(tex1_sampler, varTexcoord).rgb, vec3(0.2126, 0.7152, 0.0722)) <= threshold ? texture(tex1_sampler, varTexcoord) : vec4(0.0, 0.0, 0.0, 1.0);\n"
+        "}" };
+
+    Ra::Engine::Data::ShaderConfiguration config{ "ComposeMax" };
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, composeVertexShader );
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
+                            composeFragmentShader );
+    auto added = m_shaderMngr->addShaderProgram( config );
+    if ( added ) { m_shader = added.value(); }
+}
+
+void LessThanThresholdNode::update() {}
+
+void LessThanThresholdNode::execute() {
+    Ra::Engine::Data::RenderParameters inPassParams;
+
+    // Texture 1
+    auto inputColor1           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[0].get() );
+    NodeTypeTexture* texColor1 = &inputColor1->getData();
+
+    m_framebuffer->bind();
+    m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() );
+
+    const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 };
+    gl::glDrawBuffers( 1, buffers );
+    gl::glDisable( gl::GL_DEPTH_TEST );
+    gl::glDepthMask( gl::GL_FALSE );
+    gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE );
+
+    float clearBlack[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+    gl::glClearBufferfv( gl::GL_COLOR, 0, clearBlack );
+
+    gl::glDisable( gl::GL_BLEND );
+
+    m_shader->bind();
+    m_shader->setUniform( "tex1_sampler", texColor1, 0 );
+    m_shader->setUniform( "threshold", m_editableThreshold );
+
+    m_quadMesh->render( m_shader );
+
+    m_framebuffer->detach( gl::GL_COLOR_ATTACHMENT0 );
+    m_framebuffer->unbind();
+}
+
+void LessThanThresholdNode::destroy() {
+    delete m_framebuffer;
+    delete m_colorTexture;
+}
+
+void LessThanThresholdNode::resize( uint32_t width, uint32_t height ) {
+    m_colorTexture->resize( width, height );
+}
+
+void LessThanThresholdNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                           Ra::Engine::Rendering::RenderTechnique& rt ) const {}
+
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.hpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.hpp
new file mode 100644
index 0000000..482df29
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#include <RadiumNBR/NodeGraph/Node.hpp>
+
+#include <globjects/Framebuffer.h>
+
+#include <Core/Geometry/MeshPrimitives.hpp>
+#include <Engine/Data/Mesh.hpp>
+#include <Engine/Data/RenderParameters.hpp>
+
+class NodeBasedRenderer_LIBRARY_API LessThanThresholdNode : public Node
+{
+  public:
+    explicit LessThanThresholdNode( const std::string& name );
+
+    void init() override;
+    void update() override;
+    void execute() override;
+    void destroy() override;
+    void resize( uint32_t width, uint32_t height ) override;
+
+    void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                               Ra::Engine::Rendering::RenderTechnique& rt ) const override;
+
+    static const std::string getTypename() { return "Less Than Threshold"; }
+
+  private:
+    Ra::Engine::Data::Texture* m_colorTexture{ nullptr };
+
+    std::unique_ptr<Ra::Engine::Data::Displayable> m_quadMesh{ nullptr };
+
+    const Ra::Engine::Data::ShaderProgram* m_shader{ nullptr };
+
+    globjects::Framebuffer* m_framebuffer{ nullptr };
+
+    float m_editableThreshold{0.0};
+};
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.cpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.cpp
new file mode 100644
index 0000000..e82f87d
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.cpp
@@ -0,0 +1,113 @@
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.hpp>
+
+MaxNode::MaxNode( const std::string& name ) : Node( name ) {
+    auto portInColor1 = new PortIn<NodeTypeTexture>( "inColorTexture1", this );
+    addInput( portInColor1 );
+    portInColor1->mustBeLinked();
+    auto portInColor2 = new PortIn<NodeTypeTexture>( "inColorTexture2", this );
+    addInput( portInColor2 );
+    portInColor2->mustBeLinked();
+
+    Ra::Engine::Data::TextureParameters colorTexParams = { "Color Texture",
+                                                           gl::GL_TEXTURE_2D,
+                                                           1,
+                                                           1,
+                                                           1,
+                                                           gl::GL_RGBA,
+                                                           gl::GL_RGBA32F,
+                                                           gl::GL_FLOAT,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_LINEAR,
+                                                           gl::GL_LINEAR,
+                                                           nullptr };
+    m_colorTexture = new Ra::Engine::Data::Texture( colorTexParams );
+
+    auto portOutColorTex = new PortOut<NodeTypeTexture>( "outColorTexture", this );
+    addOutput( portOutColorTex, m_colorTexture );
+}
+
+void MaxNode::init() {
+    m_framebuffer = new globjects::Framebuffer();
+
+    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();
+
+    const std::string composeVertexShader{ "layout (location = 0) in vec3 in_position;\n"
+                                           "out vec2 varTexcoord;\n"
+                                           "void main()\n"
+                                           "{\n"
+                                           "  gl_Position = vec4(in_position, 1.0);\n"
+                                           "  varTexcoord = (in_position.xy + 1.0) * 0.5;\n"
+                                           "}\n" };
+    const std::string composeFragmentShader{
+        "layout (location = 0) out vec4 out_tex;\n"
+        "uniform sampler2D tex1_sampler;\n"
+        "uniform sampler2D tex2_sampler;\n"
+        "in vec2 varTexcoord;\n"
+        "void main() {\n"
+        "   out_tex = max(texture(tex1_sampler, varTexcoord), texture(tex2_sampler, varTexcoord));\n"
+        "}" };
+
+    Ra::Engine::Data::ShaderConfiguration config{ "ComposeMax" };
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, composeVertexShader );
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
+                            composeFragmentShader );
+    auto added = m_shaderMngr->addShaderProgram( config );
+    if ( added ) { m_shader = added.value(); }
+}
+
+void MaxNode::update() {}
+
+void MaxNode::execute() {
+    Ra::Engine::Data::RenderParameters inPassParams;
+
+    // Texture 1
+    auto inputColor1           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[0].get() );
+    NodeTypeTexture* texColor1 = &inputColor1->getData();
+
+    // Texture 2
+    auto inputColor2           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[1].get() );
+    NodeTypeTexture* texColor2 = &inputColor2->getData();
+
+    m_framebuffer->bind();
+    m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() );
+
+    const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 };
+    gl::glDrawBuffers( 1, buffers );
+    gl::glDisable( gl::GL_DEPTH_TEST );
+    gl::glDepthMask( gl::GL_FALSE );
+    gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE );
+
+    float clearBlack[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+    gl::glClearBufferfv( gl::GL_COLOR, 0, clearBlack );
+
+    gl::glDisable( gl::GL_BLEND );
+
+    m_shader->bind();
+    m_shader->setUniform( "tex1_sampler", texColor1, 0 );
+    m_shader->setUniform( "tex2_sampler", texColor2, 1 );
+
+    m_quadMesh->render( m_shader );
+
+    m_framebuffer->detach( gl::GL_COLOR_ATTACHMENT0 );
+    m_framebuffer->unbind();
+}
+
+void MaxNode::destroy() {
+    delete m_framebuffer;
+    delete m_colorTexture;
+}
+
+void MaxNode::resize( uint32_t width, uint32_t height ) {
+    m_colorTexture->resize( width, height );
+}
+
+void MaxNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                           Ra::Engine::Rendering::RenderTechnique& rt ) const {}
+
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.hpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.hpp
new file mode 100644
index 0000000..716338f
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.hpp
@@ -0,0 +1,34 @@
+#pragma once
+#include <RadiumNBR/NodeGraph/Node.hpp>
+
+#include <globjects/Framebuffer.h>
+
+#include <Core/Geometry/MeshPrimitives.hpp>
+#include <Engine/Data/Mesh.hpp>
+#include <Engine/Data/RenderParameters.hpp>
+
+class NodeBasedRenderer_LIBRARY_API MaxNode : public Node
+{
+  public:
+    explicit MaxNode( const std::string& name );
+
+    void init() override;
+    void update() override;
+    void execute() override;
+    void destroy() override;
+    void resize( uint32_t width, uint32_t height ) override;
+
+    void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                               Ra::Engine::Rendering::RenderTechnique& rt ) const override;
+
+    static const std::string getTypename() { return "Max"; }
+
+  private:
+    Ra::Engine::Data::Texture* m_colorTexture{ nullptr };
+
+    std::unique_ptr<Ra::Engine::Data::Displayable> m_quadMesh{ nullptr };
+
+    const Ra::Engine::Data::ShaderProgram* m_shader{ nullptr };
+
+    globjects::Framebuffer* m_framebuffer{ nullptr };
+};
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.cpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.cpp
new file mode 100644
index 0000000..2785e4d
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.cpp
@@ -0,0 +1,113 @@
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.hpp>
+
+MinNode::MinNode( const std::string& name ) : Node( name ) {
+    auto portInColor1 = new PortIn<NodeTypeTexture>( "inColorTexture1", this );
+    addInput( portInColor1 );
+    portInColor1->mustBeLinked();
+    auto portInColor2 = new PortIn<NodeTypeTexture>( "inColorTexture2", this );
+    addInput( portInColor2 );
+    portInColor2->mustBeLinked();
+
+    Ra::Engine::Data::TextureParameters colorTexParams = { "Color Texture",
+                                                           gl::GL_TEXTURE_2D,
+                                                           1,
+                                                           1,
+                                                           1,
+                                                           gl::GL_RGBA,
+                                                           gl::GL_RGBA32F,
+                                                           gl::GL_FLOAT,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_LINEAR,
+                                                           gl::GL_LINEAR,
+                                                           nullptr };
+    m_colorTexture = new Ra::Engine::Data::Texture( colorTexParams );
+
+    auto portOutColorTex = new PortOut<NodeTypeTexture>( "outColorTexture", this );
+    addOutput( portOutColorTex, m_colorTexture );
+}
+
+void MinNode::init() {
+    m_framebuffer = new globjects::Framebuffer();
+
+    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();
+
+    const std::string composeVertexShader{ "layout (location = 0) in vec3 in_position;\n"
+                                           "out vec2 varTexcoord;\n"
+                                           "void main()\n"
+                                           "{\n"
+                                           "  gl_Position = vec4(in_position, 1.0);\n"
+                                           "  varTexcoord = (in_position.xy + 1.0) * 0.5;\n"
+                                           "}\n" };
+    const std::string composeFragmentShader{
+        "layout (location = 0) out vec4 out_tex;\n"
+        "uniform sampler2D tex1_sampler;\n"
+        "uniform sampler2D tex2_sampler;\n"
+        "in vec2 varTexcoord;\n"
+        "void main() {\n"
+        "   out_tex = min(texture(tex1_sampler, varTexcoord), texture(tex2_sampler, varTexcoord));\n"
+        "}" };
+
+    Ra::Engine::Data::ShaderConfiguration config{ "ComposeMin" };
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, composeVertexShader );
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
+                            composeFragmentShader );
+    auto added = m_shaderMngr->addShaderProgram( config );
+    if ( added ) { m_shader = added.value(); }
+}
+
+void MinNode::update() {}
+
+void MinNode::execute() {
+    Ra::Engine::Data::RenderParameters inPassParams;
+
+    // Texture 1
+    auto inputColor1           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[0].get() );
+    NodeTypeTexture* texColor1 = &inputColor1->getData();
+
+    // Texture 2
+    auto inputColor2           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[1].get() );
+    NodeTypeTexture* texColor2 = &inputColor2->getData();
+
+    m_framebuffer->bind();
+    m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() );
+
+    const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 };
+    gl::glDrawBuffers( 1, buffers );
+    gl::glDisable( gl::GL_DEPTH_TEST );
+    gl::glDepthMask( gl::GL_FALSE );
+    gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE );
+
+    float clearBlack[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+    gl::glClearBufferfv( gl::GL_COLOR, 0, clearBlack );
+
+    gl::glDisable( gl::GL_BLEND );
+
+    m_shader->bind();
+    m_shader->setUniform( "tex1_sampler", texColor1, 0 );
+    m_shader->setUniform( "tex2_sampler", texColor2, 1 );
+
+    m_quadMesh->render( m_shader );
+
+    m_framebuffer->detach( gl::GL_COLOR_ATTACHMENT0 );
+    m_framebuffer->unbind();
+}
+
+void MinNode::destroy() {
+    delete m_framebuffer;
+    delete m_colorTexture;
+}
+
+void MinNode::resize( uint32_t width, uint32_t height ) {
+    m_colorTexture->resize( width, height );
+}
+
+void MinNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                           Ra::Engine::Rendering::RenderTechnique& rt ) const {}
+
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.hpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.hpp
new file mode 100644
index 0000000..28b48f3
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.hpp
@@ -0,0 +1,34 @@
+#pragma once
+#include <RadiumNBR/NodeGraph/Node.hpp>
+
+#include <globjects/Framebuffer.h>
+
+#include <Core/Geometry/MeshPrimitives.hpp>
+#include <Engine/Data/Mesh.hpp>
+#include <Engine/Data/RenderParameters.hpp>
+
+class NodeBasedRenderer_LIBRARY_API MinNode : public Node
+{
+  public:
+    explicit MinNode( const std::string& name );
+
+    void init() override;
+    void update() override;
+    void execute() override;
+    void destroy() override;
+    void resize( uint32_t width, uint32_t height ) override;
+
+    void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                               Ra::Engine::Rendering::RenderTechnique& rt ) const override;
+
+    static const std::string getTypename() { return "Min"; }
+
+  private:
+    Ra::Engine::Data::Texture* m_colorTexture{ nullptr };
+
+    std::unique_ptr<Ra::Engine::Data::Displayable> m_quadMesh{ nullptr };
+
+    const Ra::Engine::Data::ShaderProgram* m_shader{ nullptr };
+
+    globjects::Framebuffer* m_framebuffer{ nullptr };
+};
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.cpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.cpp
new file mode 100644
index 0000000..8b03d51
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.cpp
@@ -0,0 +1,111 @@
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.hpp>
+
+MoreThanThresholdNode::MoreThanThresholdNode( const std::string& name ) : Node( name ) {
+    auto portInColor1 = new PortIn<NodeTypeTexture>( "inColorTexture1", this );
+    addInput( portInColor1 );
+    portInColor1->mustBeLinked();
+
+    Ra::Engine::Data::TextureParameters colorTexParams = { "Color Texture",
+                                                           gl::GL_TEXTURE_2D,
+                                                           1,
+                                                           1,
+                                                           1,
+                                                           gl::GL_RGBA,
+                                                           gl::GL_RGBA32F,
+                                                           gl::GL_FLOAT,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_LINEAR,
+                                                           gl::GL_LINEAR,
+                                                           nullptr };
+    m_colorTexture = new Ra::Engine::Data::Texture( colorTexParams );
+
+    auto portOutColorTex = new PortOut<NodeTypeTexture>( "outColorTexture", this );
+    addOutput( portOutColorTex, m_colorTexture );
+
+    auto editableThreshold = new EditableParameter<float>( "threshold", &m_editableThreshold );
+    editableThreshold->addAdditionalData(0.0f);
+    editableThreshold->addAdditionalData(1.0f);
+    addEditableParameter( editableThreshold );
+}
+
+void MoreThanThresholdNode::init() {
+    m_framebuffer = new globjects::Framebuffer();
+
+    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();
+
+    const std::string composeVertexShader{ "layout (location = 0) in vec3 in_position;\n"
+                                           "out vec2 varTexcoord;\n"
+                                           "void main()\n"
+                                           "{\n"
+                                           "  gl_Position = vec4(in_position, 1.0);\n"
+                                           "  varTexcoord = (in_position.xy + 1.0) * 0.5;\n"
+                                           "}\n" };
+    const std::string composeFragmentShader{
+        "layout (location = 0) out vec4 out_tex;\n"
+        "uniform sampler2D tex1_sampler;\n"
+        "uniform float threshold;\n"
+        "in vec2 varTexcoord;\n"
+        "void main() {\n"
+        "   out_tex = dot(texture(tex1_sampler, varTexcoord).rgb, vec3(0.2126, 0.7152, 0.0722)) >= threshold ? texture(tex1_sampler, varTexcoord) : vec4(0.0, 0.0, 0.0, 1.0);\n"
+        "}" };
+
+    Ra::Engine::Data::ShaderConfiguration config{ "ComposeMax" };
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, composeVertexShader );
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
+                            composeFragmentShader );
+    auto added = m_shaderMngr->addShaderProgram( config );
+    if ( added ) { m_shader = added.value(); }
+}
+
+void MoreThanThresholdNode::update() {}
+
+void MoreThanThresholdNode::execute() {
+    Ra::Engine::Data::RenderParameters inPassParams;
+
+    // Texture 1
+    auto inputColor1           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[0].get() );
+    NodeTypeTexture* texColor1 = &inputColor1->getData();
+
+    m_framebuffer->bind();
+    m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() );
+
+    const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 };
+    gl::glDrawBuffers( 1, buffers );
+    gl::glDisable( gl::GL_DEPTH_TEST );
+    gl::glDepthMask( gl::GL_FALSE );
+    gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE );
+
+    float clearBlack[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+    gl::glClearBufferfv( gl::GL_COLOR, 0, clearBlack );
+
+    gl::glDisable( gl::GL_BLEND );
+
+    m_shader->bind();
+    m_shader->setUniform( "tex1_sampler", texColor1, 0 );
+    m_shader->setUniform( "threshold", m_editableThreshold );
+
+    m_quadMesh->render( m_shader );
+
+    m_framebuffer->detach( gl::GL_COLOR_ATTACHMENT0 );
+    m_framebuffer->unbind();
+}
+
+void MoreThanThresholdNode::destroy() {
+    delete m_framebuffer;
+    delete m_colorTexture;
+}
+
+void MoreThanThresholdNode::resize( uint32_t width, uint32_t height ) {
+    m_colorTexture->resize( width, height );
+}
+
+void MoreThanThresholdNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                           Ra::Engine::Rendering::RenderTechnique& rt ) const {}
+
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.hpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.hpp
new file mode 100644
index 0000000..5abe805
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#include <RadiumNBR/NodeGraph/Node.hpp>
+
+#include <globjects/Framebuffer.h>
+
+#include <Core/Geometry/MeshPrimitives.hpp>
+#include <Engine/Data/Mesh.hpp>
+#include <Engine/Data/RenderParameters.hpp>
+
+class NodeBasedRenderer_LIBRARY_API MoreThanThresholdNode : public Node
+{
+  public:
+    explicit MoreThanThresholdNode( const std::string& name );
+
+    void init() override;
+    void update() override;
+    void execute() override;
+    void destroy() override;
+    void resize( uint32_t width, uint32_t height ) override;
+
+    void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                               Ra::Engine::Rendering::RenderTechnique& rt ) const override;
+
+    static const std::string getTypename() { return "More Than Threshold"; }
+
+  private:
+    Ra::Engine::Data::Texture* m_colorTexture{ nullptr };
+
+    std::unique_ptr<Ra::Engine::Data::Displayable> m_quadMesh{ nullptr };
+
+    const Ra::Engine::Data::ShaderProgram* m_shader{ nullptr };
+
+    globjects::Framebuffer* m_framebuffer{ nullptr };
+
+    float m_editableThreshold{0.0};
+};
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.cpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.cpp
new file mode 100644
index 0000000..aec4da6
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.cpp
@@ -0,0 +1,113 @@
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.hpp>
+
+MultiplyNode::MultiplyNode( const std::string& name ) : Node( name ) {
+    auto portInColor1 = new PortIn<NodeTypeTexture>( "inColorTexture1", this );
+    addInput( portInColor1 );
+    portInColor1->mustBeLinked();
+    auto portInColor2 = new PortIn<NodeTypeTexture>( "inColorTexture2", this );
+    addInput( portInColor2 );
+    portInColor2->mustBeLinked();
+
+    Ra::Engine::Data::TextureParameters colorTexParams = { "Color Texture",
+                                                           gl::GL_TEXTURE_2D,
+                                                           1,
+                                                           1,
+                                                           1,
+                                                           gl::GL_RGBA,
+                                                           gl::GL_RGBA32F,
+                                                           gl::GL_FLOAT,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_LINEAR,
+                                                           gl::GL_LINEAR,
+                                                           nullptr };
+    m_colorTexture = new Ra::Engine::Data::Texture( colorTexParams );
+
+    auto portOutColorTex = new PortOut<NodeTypeTexture>( "outColorTexture", this );
+    addOutput( portOutColorTex, m_colorTexture );
+}
+
+void MultiplyNode::init() {
+    m_framebuffer = new globjects::Framebuffer();
+
+    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();
+
+    const std::string composeVertexShader{ "layout (location = 0) in vec3 in_position;\n"
+                                           "out vec2 varTexcoord;\n"
+                                           "void main()\n"
+                                           "{\n"
+                                           "  gl_Position = vec4(in_position, 1.0);\n"
+                                           "  varTexcoord = (in_position.xy + 1.0) * 0.5;\n"
+                                           "}\n" };
+    const std::string composeFragmentShader{
+        "layout (location = 0) out vec4 out_tex;\n"
+        "uniform sampler2D tex1_sampler;\n"
+        "uniform sampler2D tex2_sampler;\n"
+        "in vec2 varTexcoord;\n"
+        "void main() {\n"
+        "   out_tex = texture(tex1_sampler, varTexcoord) * texture(tex2_sampler, varTexcoord);\n"
+        "}" };
+
+    Ra::Engine::Data::ShaderConfiguration config{ "ComposeMultiply" };
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, composeVertexShader );
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
+                            composeFragmentShader );
+    auto added = m_shaderMngr->addShaderProgram( config );
+    if ( added ) { m_shader = added.value(); }
+}
+
+void MultiplyNode::update() {}
+
+void MultiplyNode::execute() {
+    Ra::Engine::Data::RenderParameters inPassParams;
+
+    // Texture 1
+    auto inputColor1           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[0].get() );
+    NodeTypeTexture* texColor1 = &inputColor1->getData();
+
+    // Texture 2
+    auto inputColor2           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[1].get() );
+    NodeTypeTexture* texColor2 = &inputColor2->getData();
+
+    m_framebuffer->bind();
+    m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() );
+
+    const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 };
+    gl::glDrawBuffers( 1, buffers );
+    gl::glDisable( gl::GL_DEPTH_TEST );
+    gl::glDepthMask( gl::GL_FALSE );
+    gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE );
+
+    float clearBlack[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+    gl::glClearBufferfv( gl::GL_COLOR, 0, clearBlack );
+
+    gl::glDisable( gl::GL_BLEND );
+
+    m_shader->bind();
+    m_shader->setUniform( "tex1_sampler", texColor1, 0 );
+    m_shader->setUniform( "tex2_sampler", texColor2, 1 );
+
+    m_quadMesh->render( m_shader );
+
+    m_framebuffer->detach( gl::GL_COLOR_ATTACHMENT0 );
+    m_framebuffer->unbind();
+}
+
+void MultiplyNode::destroy() {
+    delete m_framebuffer;
+    delete m_colorTexture;
+}
+
+void MultiplyNode::resize( uint32_t width, uint32_t height ) {
+    m_colorTexture->resize( width, height );
+}
+
+void MultiplyNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                           Ra::Engine::Rendering::RenderTechnique& rt ) const {}
+
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.hpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.hpp
new file mode 100644
index 0000000..8b231b4
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.hpp
@@ -0,0 +1,34 @@
+#pragma once
+#include <RadiumNBR/NodeGraph/Node.hpp>
+
+#include <globjects/Framebuffer.h>
+
+#include <Core/Geometry/MeshPrimitives.hpp>
+#include <Engine/Data/Mesh.hpp>
+#include <Engine/Data/RenderParameters.hpp>
+
+class NodeBasedRenderer_LIBRARY_API MultiplyNode : public Node
+{
+  public:
+    explicit MultiplyNode( const std::string& name );
+
+    void init() override;
+    void update() override;
+    void execute() override;
+    void destroy() override;
+    void resize( uint32_t width, uint32_t height ) override;
+
+    void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                               Ra::Engine::Rendering::RenderTechnique& rt ) const override;
+
+    static const std::string getTypename() { return "Multiply"; }
+
+  private:
+    Ra::Engine::Data::Texture* m_colorTexture{ nullptr };
+
+    std::unique_ptr<Ra::Engine::Data::Displayable> m_quadMesh{ nullptr };
+
+    const Ra::Engine::Data::ShaderProgram* m_shader{ nullptr };
+
+    globjects::Framebuffer* m_framebuffer{ nullptr };
+};
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.cpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.cpp
new file mode 100644
index 0000000..d1e60dd
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.cpp
@@ -0,0 +1,104 @@
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.hpp>
+
+OneMinusNode::OneMinusNode( const std::string& name ) : Node( name ) {
+    auto portInColor1 = new PortIn<NodeTypeTexture>( "inColorTexture1", this );
+    addInput( portInColor1 );
+    portInColor1->mustBeLinked();
+
+    Ra::Engine::Data::TextureParameters colorTexParams = { "Color Texture",
+                                                           gl::GL_TEXTURE_2D,
+                                                           1,
+                                                           1,
+                                                           1,
+                                                           gl::GL_RGBA,
+                                                           gl::GL_RGBA32F,
+                                                           gl::GL_FLOAT,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_CLAMP_TO_EDGE,
+                                                           gl::GL_LINEAR,
+                                                           gl::GL_LINEAR,
+                                                           nullptr };
+    m_colorTexture = new Ra::Engine::Data::Texture( colorTexParams );
+
+    auto portOutColorTex = new PortOut<NodeTypeTexture>( "outColorTexture", this );
+    addOutput( portOutColorTex, m_colorTexture );
+}
+
+void OneMinusNode::init() {
+    m_framebuffer = new globjects::Framebuffer();
+
+    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();
+
+    const std::string composeVertexShader{ "layout (location = 0) in vec3 in_position;\n"
+                                           "out vec2 varTexcoord;\n"
+                                           "void main()\n"
+                                           "{\n"
+                                           "  gl_Position = vec4(in_position, 1.0);\n"
+                                           "  varTexcoord = (in_position.xy + 1.0) * 0.5;\n"
+                                           "}\n" };
+    const std::string composeFragmentShader{
+        "layout (location = 0) out vec4 out_tex;\n"
+        "uniform sampler2D tex1_sampler;\n"
+        "in vec2 varTexcoord;\n"
+        "void main() {\n"
+        "   out_tex = vec4(1.0) - texture(tex1_sampler, varTexcoord);\n"
+        "}" };
+
+    Ra::Engine::Data::ShaderConfiguration config{ "ComposeOneMinus" };
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, composeVertexShader );
+    config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT,
+                            composeFragmentShader );
+    auto added = m_shaderMngr->addShaderProgram( config );
+    if ( added ) { m_shader = added.value(); }
+}
+
+void OneMinusNode::update() {}
+
+void OneMinusNode::execute() {
+    Ra::Engine::Data::RenderParameters inPassParams;
+
+    // Texture 1
+    auto inputColor1           = dynamic_cast<PortIn<NodeTypeTexture>*>( m_inputs[0].get() );
+    NodeTypeTexture* texColor1 = &inputColor1->getData();
+
+    m_framebuffer->bind();
+    m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() );
+
+    const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 };
+    gl::glDrawBuffers( 1, buffers );
+    gl::glDisable( gl::GL_DEPTH_TEST );
+    gl::glDepthMask( gl::GL_FALSE );
+    gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE );
+
+    float clearBlack[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+    gl::glClearBufferfv( gl::GL_COLOR, 0, clearBlack );
+
+    gl::glDisable( gl::GL_BLEND );
+
+    m_shader->bind();
+    m_shader->setUniform( "tex1_sampler", texColor1, 0 );
+
+    m_quadMesh->render( m_shader );
+
+    m_framebuffer->detach( gl::GL_COLOR_ATTACHMENT0 );
+    m_framebuffer->unbind();
+}
+
+void OneMinusNode::destroy() {
+    delete m_framebuffer;
+    delete m_colorTexture;
+}
+
+void OneMinusNode::resize( uint32_t width, uint32_t height ) {
+    m_colorTexture->resize( width, height );
+}
+
+void OneMinusNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                           Ra::Engine::Rendering::RenderTechnique& rt ) const {}
+
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.hpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.hpp
new file mode 100644
index 0000000..ea02468
--- /dev/null
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.hpp
@@ -0,0 +1,34 @@
+#pragma once
+#include <RadiumNBR/NodeGraph/Node.hpp>
+
+#include <globjects/Framebuffer.h>
+
+#include <Core/Geometry/MeshPrimitives.hpp>
+#include <Engine/Data/Mesh.hpp>
+#include <Engine/Data/RenderParameters.hpp>
+
+class NodeBasedRenderer_LIBRARY_API OneMinusNode : public Node
+{
+  public:
+    explicit OneMinusNode( const std::string& name );
+
+    void init() override;
+    void update() override;
+    void execute() override;
+    void destroy() override;
+    void resize( uint32_t width, uint32_t height ) override;
+
+    void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro,
+                               Ra::Engine::Rendering::RenderTechnique& rt ) const override;
+
+    static const std::string getTypename() { return "1 -"; }
+
+  private:
+    Ra::Engine::Data::Texture* m_colorTexture{ nullptr };
+
+    std::unique_ptr<Ra::Engine::Data::Displayable> m_quadMesh{ nullptr };
+
+    const Ra::Engine::Data::ShaderProgram* m_shader{ nullptr };
+
+    globjects::Framebuffer* m_framebuffer{ nullptr };
+};
diff --git a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/PremadeNodesIncludes.hpp b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/PremadeNodesIncludes.hpp
index dd160df..126b9d5 100644
--- a/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/PremadeNodesIncludes.hpp
+++ b/src/libRender/RadiumNBR/NodeGraph/PremadeNodes/PremadeNodesIncludes.hpp
@@ -26,5 +26,13 @@
 
 #include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/DifferenceNode.hpp>
 #include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/SumNode.hpp>
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/MaxNode.hpp>
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/MinNode.hpp>
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/MoreThanThresholdNode.hpp>
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/LessThanThresholdNode.hpp>
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/MultiplyNode.hpp>
+#include <RadiumNBR/NodeGraph/PremadeNodes/Compositing/OneMinusNode.hpp>
+
+#include <RadiumNBR/NodeGraph/PremadeNodes/AntiAliasing/FXAANode.hpp>
 
 #include <RadiumNBR/NodeGraph/PremadeNodes/Demo/SimpleNode.hpp>
\ No newline at end of file
-- 
GitLab