Noise Reduction Shader

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Noiecity
Posts: 383
Joined: Wed Aug 23, 2023 7:22 pm
Contact:

Noise Reduction Shader

Post by Noiecity »

An anisotropic smooth operation:

Image

main.cpp

Code: Select all

#include <irrlicht.h>
#include <vector>
#include "denoise_reduction.hpp"

#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
#endif

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

static const int   STRENGTH = 7;
static const float OPACITY  = 1.0f;

// Helper to get original animation speed from an MD2 mesh (fallback 24 fps)
f32 getOriginalAnimationSpeed(IAnimatedMeshSceneNode* node)
{
    if (!node) return 24.0f;
    IAnimatedMesh* mesh = node->getMesh();
    if (!mesh) return 24.0f;
    return 24.0f; // safe default for dwarf.x
}

int main()
{
    IrrlichtDevice* device = createDevice(
        EDT_OPENGL,
        dimension2d<u32>(800,600),
        16, false, true, false, 0);

    if (!device) return 1;

    IVideoDriver* driver = device->getVideoDriver();
    ISceneManager* smgr = device->getSceneManager();
    IGUIEnvironment* gui = device->getGUIEnvironment();
    ITimer* timer = device->getTimer();

    driver->setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, false);

    if (!driver->queryFeature(EVDF_RENDER_TO_TARGET))
    {
        device->drop();
        return 1;
    }

    IAnimatedMesh* mesh = smgr->getMesh("../../media/dwarf.x");
    if (!mesh)
    {
        device->drop();
        return 1;
    }

    std::vector<IShadowVolumeSceneNode*> shadowNodes;
    std::vector<IAnimatedMeshSceneNode*> animatedNodes;

    // Helper lambda (not used, we'll just do it manually for C++98)
    IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode(mesh);
    if (node)
    {
        node->setMaterialFlag(EMF_LIGHTING, true);
        node->setMaterialFlag(EMF_BILINEAR_FILTER, false);
        node->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
        node->setMD2Animation(scene::EMAT_STAND);

        node->setAnimationSpeed(0);
        node->setCurrentFrame(0);

        node->setPosition(vector3df(0,-9,0));

        IShadowVolumeSceneNode* shadow = node->addShadowVolumeSceneNode();
        if (shadow)
        {
            shadow->setFreeze(ESF_FREEZE);
            shadowNodes.push_back(shadow);
        }
        animatedNodes.push_back(node);
    }

    for (int i = 0; i < 3; ++i)
    {
        IAnimatedMeshSceneNode* newNode = smgr->addAnimatedMeshSceneNode(mesh);
        if (newNode)
        {
            newNode->setMaterialFlag(EMF_LIGHTING, true);
            newNode->setMaterialFlag(EMF_BILINEAR_FILTER, false);
            newNode->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
            newNode->setMD2Animation(scene::EMAT_STAND);

            newNode->setAnimationSpeed(0);
            newNode->setCurrentFrame(0);

            f32 angle = i * (2 * PI / 8);
            f32 radius = 60.0f;
            newNode->setPosition(vector3df(radius*cos(angle), -9, radius*sin(angle)));

            IShadowVolumeSceneNode* shadow = newNode->addShadowVolumeSceneNode();
            if (shadow)
            {
                shadow->setFreeze(ESF_FREEZE);
                shadowNodes.push_back(shadow);
            }
            animatedNodes.push_back(newNode);
        }
    }

    // --- Apply reflection material to the FIRST material of all dwarves ---
    // Load the reflection texture once
    ITexture* reflectionTex = driver->getTexture("../../media/irrlicht2_rt.jpg");
    for (size_t idx = 0; idx < animatedNodes.size(); ++idx)
    {
        IAnimatedMeshSceneNode* animNode = animatedNodes[idx];
        if (animNode && animNode->getMaterialCount() > 0)
        {
            video::SMaterial& mat = animNode->getMaterial(0); // first material
            mat.MaterialType = video::EMT_REFLECTION_2_LAYER;
            mat.setTexture(1, reflectionTex); // second texture layer for reflection
            // Optionally set the first texture as well (original diffuse)
            // The original diffuse texture is already there at layer 0, we keep it.
        }
    }

    // Get original animation speed (same for all)
    f32 originalSpeed = 24.0f;
    if (!animatedNodes.empty())
        originalSpeed = getOriginalAnimationSpeed(animatedNodes[0]);

    const f32 targetUpdateRate = 8.0f;      // Hz
    const u32 updateIntervalMs = (u32)(1000.0f / targetUpdateRate); // 125 ms
    f32 framesPerStep = originalSpeed / targetUpdateRate;

    u32 lastUpdateTime = timer->getRealTime();
    f32 animationTime = 0.0f;

    smgr->setShadowColor(video::SColor(230,0,0,0));

    ILightSceneNode* sun = smgr->addLightSceneNode(0,
        vector3df(90,150,-45), SColorf(1.0f,1.0f,1.0f), 700.0f);
        video::SLight lightData = sun->getLightData();
        lightData.Attenuation = core::vector3df(1.0f, 0.000001f, 0.000001f); // quadratic
        sun->setLightData(lightData);
    IMeshSceneNode* floor = smgr->addCubeSceneNode(1.0f);
    if (floor)
    {
        floor->setScale(vector3df(200,1,200));
        floor->setPosition(vector3df(0,-10,0));
        floor->setMaterialFlag(EMF_LIGHTING, true);
        floor->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
        floor->setMaterialTexture(0, driver->getTexture("../../media/wall.jpg"));
    }

    ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS();
    camera->setPosition(vector3df(0,20,-40));
    camera->setTarget(vector3df(0,5,0));
    device->getCursorControl()->setVisible(false);

    // --- RTT setup ---
    ITexture* rtt = driver->addRenderTargetTexture(dimension2d<u32>(128,128), "RTT_Scene");
    if (!rtt) { device->drop(); return 1; }

    ITexture* rttA = driver->addRenderTargetTexture(dimension2d<u32>(128,128), "RTT_A");
    ITexture* rttB = driver->addRenderTargetTexture(dimension2d<u32>(128,128), "RTT_B");
    if (!rttA || !rttB) { device->drop(); return 1; }

    S3DVertex vertices[4];
    vertices[0] = S3DVertex(-1, 1,0, 0,0,1, SColor(255,255,255,255), 0,0);
    vertices[1] = S3DVertex( 1, 1,0, 0,0,1, SColor(255,255,255,255), 1,0);
    vertices[2] = S3DVertex( 1,-1,0, 0,0,1, SColor(255,255,255,255), 1,1);
    vertices[3] = S3DVertex(-1,-1,0, 0,0,1, SColor(255,255,255,255), 0,1);
    u16 indices[6] = {0,1,2, 0,2,3};

    // --- Shaders ---
    IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
    if (!gpu) { device->drop(); return 1; }

    io::IReadFile* file = device->getFileSystem()->createAndOpenFile("../../media/denoise_reduction.glsl");
    if (!file) { device->drop(); return 1; }
    irr::c8* buf = new irr::c8[file->getSize() + 1];
    file->read(buf, file->getSize());
    buf[file->getSize()] = 0;
    file->drop();
    io::path noiseFragStr = buf;
    delete[] buf;

    NoiseReductionCallback* noiseCB = new NoiseReductionCallback();
    noiseCB->setSize(128, 128);
    s32 noiseMatType = gpu->addHighLevelShaderMaterial(
        FULLSCREEN_VERT_SHADER, noiseFragStr.c_str(),
        noiseCB, EMT_SOLID, 0);
    noiseCB->drop();
    if (noiseMatType == -1) { device->drop(); return 1; }

    SMaterial noiseMat;
    noiseMat.Lighting = false;
    noiseMat.ZBuffer = false;
    noiseMat.ZWriteEnable = EZW_OFF;
    noiseMat.MaterialType = (E_MATERIAL_TYPE)noiseMatType;

    BlendCallback* blendCB = new BlendCallback();
    blendCB->opacity = OPACITY;
    s32 blendMatType = gpu->addHighLevelShaderMaterial(
        FULLSCREEN_VERT_SHADER, BLEND_FRAG_SHADER,
        blendCB, EMT_SOLID, 0);
    blendCB->drop();
    if (blendMatType == -1) { device->drop(); return 1; }

    SMaterial blendMat;
    blendMat.Lighting = false;
    blendMat.ZBuffer = false;
    blendMat.ZWriteEnable = EZW_OFF;
    blendMat.MaterialType = (E_MATERIAL_TYPE)blendMatType;

    // --- Initial shadow update (once) ---
    for (size_t i = 0; i < shadowNodes.size(); ++i)
    {
        IShadowVolumeSceneNode* shadow = shadowNodes[i];
        if (shadow)
        {
            shadow->setFreeze(ESF_RUN);
            shadow->updateShadowVolumes();
            shadow->setFreeze(ESF_FREEZE);
        }
    }

    int lastFPS = -1;

    while (device->run())
    {
        if (!device->isWindowActive()) continue;

        u32 now = timer->getRealTime();

        // --- Single timer for both animation and shadow updates (8 Hz) ---
        if (now - lastUpdateTime >= updateIntervalMs)
        {
            u32 elapsedMs = now - lastUpdateTime;
            lastUpdateTime = now;

            // 1) Update animation frames
            animationTime += elapsedMs / 1000.0f;
            f32 totalFrames = animationTime * originalSpeed;

            s32 frameCount = 0;
            if (!animatedNodes.empty())
            {
                IAnimatedMesh* animMesh = animatedNodes[0]->getMesh();
                if (animMesh)
                {
                    frameCount = animatedNodes[0]->getEndFrame() - animatedNodes[0]->getStartFrame();
                    if (frameCount <= 0) frameCount = 40;
                }
            }
            if (frameCount <= 0) frameCount = 40;

            s32 frame = (s32)fmodf(totalFrames, (f32)frameCount);
            frame = animatedNodes[0]->getStartFrame() + frame;

            for (size_t i = 0; i < animatedNodes.size(); ++i)
            {
                IAnimatedMeshSceneNode* animNode = animatedNodes[i];
                if (animNode)
                    animNode->setCurrentFrame((f32)frame);
            }

            // 2) Update shadow volumes (same interval)
            for (size_t i = 0; i < shadowNodes.size(); ++i)
            {
                IShadowVolumeSceneNode* shadow = shadowNodes[i];
                if (shadow)
                {
                    shadow->setFreeze(ESF_RUN);
                    shadow->updateShadowVolumes();
                    shadow->setFreeze(ESF_FREEZE);
                }
            }
        }

        // --- Render to RTT and apply denoise ---
        driver->setRenderTarget(rtt, ECBF_COLOR | ECBF_DEPTH, SColor(0,0,0,255));
        smgr->drawAll();

        driver->setRenderTarget(rttA, ECBF_COLOR | ECBF_DEPTH, SColor(0,0,0,255));
        {
            SMaterial copyMat;
            copyMat.Lighting = false;
            copyMat.ZBuffer = false;
            copyMat.ZWriteEnable = EZW_OFF;
            copyMat.setFlag(EMF_BILINEAR_FILTER, true);
            copyMat.setTexture(0, rtt);
            driver->setMaterial(copyMat);
            driver->setTransform(ETS_WORLD, IdentityMatrix);
            driver->setTransform(ETS_VIEW, IdentityMatrix);
            driver->setTransform(ETS_PROJECTION, IdentityMatrix);
            driver->drawIndexedTriangleList(vertices, 4, indices, 2);
        }

        for (int i = 0; i < STRENGTH; ++i)
        {
            ITexture* input  = (i % 2 == 0) ? rttA : rttB;
            ITexture* output = (i % 2 == 0) ? rttB : rttA;

            driver->setRenderTarget(output, ECBF_COLOR | ECBF_DEPTH, SColor(0,0,0,255));
            noiseMat.setTexture(0, input);
            driver->setMaterial(noiseMat);
            driver->setTransform(ETS_WORLD, IdentityMatrix);
            driver->setTransform(ETS_VIEW, IdentityMatrix);
            driver->setTransform(ETS_PROJECTION, IdentityMatrix);
            driver->drawIndexedTriangleList(vertices, 4, indices, 2);
        }

        ITexture* filteredTex = (STRENGTH % 2 == 0) ? rttA : rttB;
        if (STRENGTH == 0) filteredTex = rtt;

        driver->setRenderTarget(0, ECBF_COLOR | ECBF_DEPTH, SColor(0,100,100,100));
        driver->beginScene(false, false, SColor(0,0,0,0));

        blendMat.setTexture(0, rtt);
        blendMat.setTexture(1, filteredTex);
        blendMat.setFlag(EMF_BILINEAR_FILTER, true);
        driver->setMaterial(blendMat);
        driver->setTransform(ETS_WORLD, IdentityMatrix);
        driver->setTransform(ETS_VIEW, IdentityMatrix);
        driver->setTransform(ETS_PROJECTION, IdentityMatrix);
        driver->drawIndexedTriangleList(vertices, 4, indices, 2);

        driver->endScene();

        int fps = driver->getFPS();
        if (fps != lastFPS)
        {
            stringw str = L"RTT + Denoise (";
            str += STRENGTH;
            str += L" passes)  FPS: ";
            str += fps;
            device->setWindowCaption(str.c_str());
            lastFPS = fps;
        }
    }

    device->drop();
    return 0;
}


denoise_reduction.hpp

Code: Select all

// denoise_reduction.hpp
#ifndef DENOISE_REDUCTION_HPP
#define DENOISE_REDUCTION_HPP

#include <irrlicht.h>

// Vertex shader with Y correction for RTT in OpenGL
static const char* FULLSCREEN_VERT_SHADER =
    "#version 120\n"
    "void main() {\n"
    "    gl_Position = ftransform();\n"
    "    gl_TexCoord[0] = vec4(gl_MultiTexCoord0.x, 1.0 - gl_MultiTexCoord0.y, 0.0, 1.0);\n"
    "}";

// Final blend fragment shader
static const char* BLEND_FRAG_SHADER =
    "#version 120\n"
    "uniform sampler2D texOriginal;\n"
    "uniform sampler2D texFiltered;\n"
    "uniform float opacity;\n"
    "void main() {\n"
    "    vec2 uv = gl_TexCoord[0].st;\n"
    "    vec3 orig = texture2D(texOriginal, uv).rgb;\n"
    "    vec3 filt = texture2D(texFiltered, uv).rgb;\n"
    "    vec3 mixed = mix(orig, filt, opacity);\n"
    "    gl_FragColor = vec4(mixed, 1.0);\n"
    "}";

// Callback for noise reduction shader
struct NoiseReductionCallback : public irr::video::IShaderConstantSetCallBack
{
    NoiseReductionCallback() : texelSizeID(-1), width(0), height(0) {}

    void setSize(int w, int h)
    {
        width  = w;
        height = h;
    }

    virtual void OnCreate(irr::video::IMaterialRendererServices* services, irr::s32 userData)
    {
        texelSizeID = services->getPixelShaderConstantID("texelSize");
    }

    virtual void OnSetConstants(irr::video::IMaterialRendererServices* services, irr::s32 userData)
    {
        if (texelSizeID == -1) return;
        irr::f32 ts[2] = { 1.0f / width, 1.0f / height };
        services->setPixelShaderConstant(texelSizeID, ts, 2);
    }

    virtual void OnSetMaterial(const irr::video::SMaterial& material) {}

private:
    irr::s32 texelSizeID;
    int width, height;
};

// Callback for final blending (assigns texture units)
struct BlendCallback : public irr::video::IShaderConstantSetCallBack
{
    BlendCallback() : opacity(1.0f), opacityID(-1), texOriginalID(-1), texFilteredID(-1) {}

    irr::f32 opacity;

    virtual void OnCreate(irr::video::IMaterialRendererServices* services, irr::s32 userData)
    {
        opacityID      = services->getPixelShaderConstantID("opacity");
        texOriginalID  = services->getPixelShaderConstantID("texOriginal");
        texFilteredID  = services->getPixelShaderConstantID("texFiltered");
    }

    virtual void OnSetConstants(irr::video::IMaterialRendererServices* services, irr::s32 userData)
    {
        services->setPixelShaderConstant(opacityID, &opacity, 1);

        // Assign texture units
        irr::s32 unit0 = 0;
        irr::s32 unit1 = 1;
        services->setPixelShaderConstant(texOriginalID, &unit0, 1);
        services->setPixelShaderConstant(texFilteredID, &unit1, 1);
    }

    virtual void OnSetMaterial(const irr::video::SMaterial& material) {}

private:
    irr::s32 opacityID;
    irr::s32 texOriginalID;
    irr::s32 texFilteredID;
};

#endif
denoise_reduction.glsl

Code: Select all

#version 120

uniform sampler2D tex;
uniform vec2 texelSize;

void main()
{
    vec2 uv = gl_TexCoord[0].st;

    vec3 c  = texture2D(tex, uv).rgb;
    vec3 n0 = texture2D(tex, uv + vec2(-texelSize.x, -texelSize.y)).rgb;
    vec3 n1 = texture2D(tex, uv + vec2( 0.0, -texelSize.y)).rgb;
    vec3 n2 = texture2D(tex, uv + vec2( texelSize.x, -texelSize.y)).rgb;
    vec3 n3 = texture2D(tex, uv + vec2(-texelSize.x,  0.0)).rgb;
    vec3 n4 = texture2D(tex, uv + vec2( texelSize.x,  0.0)).rgb;
    vec3 n5 = texture2D(tex, uv + vec2(-texelSize.x,  texelSize.y)).rgb;
    vec3 n6 = texture2D(tex, uv + vec2( 0.0,  texelSize.y)).rgb;
    vec3 n7 = texture2D(tex, uv + vec2( texelSize.x,  texelSize.y)).rgb;

    vec3 ref0 = 2.0*c - n0 - n7;  vec3 metric0 = ref0 * ref0;
    vec3 ref1 = 2.0*c - n1 - n6;  vec3 metric1 = ref1 * ref1;
    vec3 ref2 = 2.0*c - n2 - n5;  vec3 metric2 = ref2 * ref2;
    vec3 ref3 = 2.0*c - n3 - n4;  vec3 metric3 = ref3 * ref3;

    vec3 sum = c;
    float count = 1.0;

    // Neighbor 0
    {
        vec3 v = (c + n0) * 0.5;
        vec3 d0 = 2.0*v - n0 - n7; bvec3 p0 = lessThanEqual(d0*d0, metric0);
        vec3 d1 = 2.0*v - n1 - n6; bvec3 p1 = lessThanEqual(d1*d1, metric1);
        vec3 d2 = 2.0*v - n2 - n5; bvec3 p2 = lessThanEqual(d2*d2, metric2);
        vec3 d3 = 2.0*v - n3 - n4; bvec3 p3 = lessThanEqual(d3*d3, metric3);
        if (all(p0) && all(p1) && all(p2) && all(p3)) { sum += v; count += 1.0; }
    }
    // Neighbor 1
    {
        vec3 v = (c + n1) * 0.5;
        vec3 d0 = 2.0*v - n0 - n7; bvec3 p0 = lessThanEqual(d0*d0, metric0);
        vec3 d1 = 2.0*v - n1 - n6; bvec3 p1 = lessThanEqual(d1*d1, metric1);
        vec3 d2 = 2.0*v - n2 - n5; bvec3 p2 = lessThanEqual(d2*d2, metric2);
        vec3 d3 = 2.0*v - n3 - n4; bvec3 p3 = lessThanEqual(d3*d3, metric3);
        if (all(p0) && all(p1) && all(p2) && all(p3)) { sum += v; count += 1.0; }
    }
    // Neighbor 2
    {
        vec3 v = (c + n2) * 0.5;
        vec3 d0 = 2.0*v - n0 - n7; bvec3 p0 = lessThanEqual(d0*d0, metric0);
        vec3 d1 = 2.0*v - n1 - n6; bvec3 p1 = lessThanEqual(d1*d1, metric1);
        vec3 d2 = 2.0*v - n2 - n5; bvec3 p2 = lessThanEqual(d2*d2, metric2);
        vec3 d3 = 2.0*v - n3 - n4; bvec3 p3 = lessThanEqual(d3*d3, metric3);
        if (all(p0) && all(p1) && all(p2) && all(p3)) { sum += v; count += 1.0; }
    }
    // Neighbor 3
    {
        vec3 v = (c + n3) * 0.5;
        vec3 d0 = 2.0*v - n0 - n7; bvec3 p0 = lessThanEqual(d0*d0, metric0);
        vec3 d1 = 2.0*v - n1 - n6; bvec3 p1 = lessThanEqual(d1*d1, metric1);
        vec3 d2 = 2.0*v - n2 - n5; bvec3 p2 = lessThanEqual(d2*d2, metric2);
        vec3 d3 = 2.0*v - n3 - n4; bvec3 p3 = lessThanEqual(d3*d3, metric3);
        if (all(p0) && all(p1) && all(p2) && all(p3)) { sum += v; count += 1.0; }
    }
    // Neighbor 4
    {
        vec3 v = (c + n4) * 0.5;
        vec3 d0 = 2.0*v - n0 - n7; bvec3 p0 = lessThanEqual(d0*d0, metric0);
        vec3 d1 = 2.0*v - n1 - n6; bvec3 p1 = lessThanEqual(d1*d1, metric1);
        vec3 d2 = 2.0*v - n2 - n5; bvec3 p2 = lessThanEqual(d2*d2, metric2);
        vec3 d3 = 2.0*v - n3 - n4; bvec3 p3 = lessThanEqual(d3*d3, metric3);
        if (all(p0) && all(p1) && all(p2) && all(p3)) { sum += v; count += 1.0; }
    }
    // Neighbor 5
    {
        vec3 v = (c + n5) * 0.5;
        vec3 d0 = 2.0*v - n0 - n7; bvec3 p0 = lessThanEqual(d0*d0, metric0);
        vec3 d1 = 2.0*v - n1 - n6; bvec3 p1 = lessThanEqual(d1*d1, metric1);
        vec3 d2 = 2.0*v - n2 - n5; bvec3 p2 = lessThanEqual(d2*d2, metric2);
        vec3 d3 = 2.0*v - n3 - n4; bvec3 p3 = lessThanEqual(d3*d3, metric3);
        if (all(p0) && all(p1) && all(p2) && all(p3)) { sum += v; count += 1.0; }
    }
    // Neighbor 6
    {
        vec3 v = (c + n6) * 0.5;
        vec3 d0 = 2.0*v - n0 - n7; bvec3 p0 = lessThanEqual(d0*d0, metric0);
        vec3 d1 = 2.0*v - n1 - n6; bvec3 p1 = lessThanEqual(d1*d1, metric1);
        vec3 d2 = 2.0*v - n2 - n5; bvec3 p2 = lessThanEqual(d2*d2, metric2);
        vec3 d3 = 2.0*v - n3 - n4; bvec3 p3 = lessThanEqual(d3*d3, metric3);
        if (all(p0) && all(p1) && all(p2) && all(p3)) { sum += v; count += 1.0; }
    }
    // Neighbor 7
    {
        vec3 v = (c + n7) * 0.5;
        vec3 d0 = 2.0*v - n0 - n7; bvec3 p0 = lessThanEqual(d0*d0, metric0);
        vec3 d1 = 2.0*v - n1 - n6; bvec3 p1 = lessThanEqual(d1*d1, metric1);
        vec3 d2 = 2.0*v - n2 - n5; bvec3 p2 = lessThanEqual(d2*d2, metric2);
        vec3 d3 = 2.0*v - n3 - n4; bvec3 p3 = lessThanEqual(d3*d3, metric3);
        if (all(p0) && all(p1) && all(p2) && all(p3)) { sum += v; count += 1.0; }
    }

    vec3 result = sum / count;
    gl_FragColor = vec4(result, 1.0);
}
thanks mr CuteAlien
Irrlicht is love, Irrlicht is life, long live to Irrlicht
Post Reply