Postprocessing for complex scene

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
tom_gamer
Posts: 15
Joined: Sat Jun 01, 2013 8:51 am

Postprocessing for complex scene

Post by tom_gamer »

Hi folks,

I've played with postprocessing shaders a bit. Now I want to share my results... :D

The first one is a full screen glow effect. There is nothing special about the shader itself. But there my approach is slightly different.
In most articles about glow/blur you should render your scene twice, once for the glow map and second time for the color map. Because my scenes are complex, it's not possible to render them twice. So, I rendered it to al large texture and created a smaller one from this large one.

Here is some code for init stuff:

Code: Select all

 
    CScreenQuadSceneNode *screenQuadNode[3];
    ITexture* rtt[3];
 
    rtt[0] = driver->addRenderTargetTexture(dimension2d<u32>(2048, 1024), "RTT0", ECF_A8R8G8B8);
    rtt[1] = driver->addRenderTargetTexture(dimension2d<u32>(512, 512), "RTT1", ECF_A8R8G8B8);
    rtt[2] = driver->addRenderTargetTexture(dimension2d<u32>(512, 512), "RTT2", ECF_A8R8G8B8);
 
    screenQuadNode[0] = new CScreenQuadSceneNode(smgr->getRootSceneNode(), smgr, -1);
    screenQuadNode[0]->getMaterial(0).MaterialType = EMT_SOLID;
    screenQuadNode[0]->getMaterial(0).setTexture(0, rtt[0]);
    screenQuadNode[0]->flipHorizontal();
 
    screenQuadNode[1] = new CScreenQuadSceneNode(smgr->getRootSceneNode(), smgr, -1);
    screenQuadNode[1]->getMaterial(0).MaterialType = (E_MATERIAL_TYPE)shader->getShaderMaterial(SShader::GlowShader1);
    screenQuadNode[1]->getMaterial(0).setTexture(0, rtt[1]);
 
    screenQuadNode[2] = new CScreenQuadSceneNode(smgr->getRootSceneNode(), smgr, -1);
    screenQuadNode[2]->getMaterial(0).MaterialType = (E_MATERIAL_TYPE)shader->getShaderMaterial(SShader::GlowShader2);
    screenQuadNode[2]->getMaterial(0).setTexture(0, rtt[0]);
    screenQuadNode[2]->getMaterial(0).setTexture(1, rtt[2]);
 
Note, that the first screen quad have to by flipped.

In each frame I render the scene to rtt[0], create a smaller version in rtt[1], do the horizontal blur into rtt[2] and do the vertical blur and the blending into the frame buffer.

Code: Select all

 
    /* draw the complete scene to a large rtt */
    driver->setRenderTarget(rtt[0], true, true, SColor(0,0,0,0));
    skyBox->setVisible(false);
    smgr->drawAll();
 
    if (pp_glowOn)
    {
        /* now copy the large rtt to a smaller one */
        driver->setRenderTarget(rtt[1], false, false, SColor(0,0,0,0));
        screenQuadNode[0]->render();
 
        /* calculate the glow effect into the third rtt (based on the second one) */
        driver->setRenderTarget(rtt[2], false, false, SColor(0,0,0,0));
        screenQuadNode[1]->render();
    }
    else
        driver->setRenderTarget(rtt[2], true, false, SColor(0,0,0,0));
 
    /* and finally draw the base rtt + the glow rtt into the frame buffer */
    driver->setRenderTarget(ERT_FRAME_BUFFER, false, false, SColor(0,0,0,0));
    skyBox->setVisible(true);
    smgr->drawSkyBox();
    screenQuadNode[2]->render();
 
Because the skybox should have no glow (which looks strange :lol: ) I switch it off in the first run and draw it later.

The base image looks like this:
Image

With the glow effect (look at the engines):
Image

GlowShader 1 (Vertex, glsl)

Code: Select all

 
varying vec2 Texcoord;
 
void main()
{
    gl_Position = ftransform();
    Texcoord    = gl_MultiTexCoord0.xy;
}
 
GlowShader 1 (Fragment, glsl)

Code: Select all

 
uniform sampler2D colorMap;
varying vec2 Texcoord;
 
const int gaussRadius = 15;
const int gaussRadiusHalf = 7;
float gaussFilter[gaussRadius] = float[gaussRadius](
 0.001691,0.005779,0.016346,0.038269,0.074163,0.118968,0.157970,0.173629,0.157970,0.118968,
 0.074163,0.038269,0.016346,0.005779,0.001691);
 
const vec2 uShift = vec2(1.0/512.0, 0.0);
 
void main()
{
    vec3 color = vec3(0.0, 0.0, 0.0); 
    vec2 locTextCoord = Texcoord - gaussRadiusHalf*uShift;
    for (int i=0; i<gaussRadius; ++i)
    { 
        vec4 baseCol = texture2D(colorMap, locTextCoord);
        if (baseCol.r + baseCol.g + baseCol.b > 0.7)
            color += gaussFilter[i] * baseCol.xyz;
        locTextCoord += uShift;
    }
 
    gl_FragColor = vec4(color,1.0);
}
 
GlowShader 2 (Vertex, glsl)

Code: Select all

 
varying vec2 Texcoord1;
varying vec2 Texcoord2;
 
void main()
{
    gl_Position = ftransform();
    Texcoord1    = gl_MultiTexCoord0.xy;
    Texcoord2    = gl_MultiTexCoord1.xy;
}
 
GlowShader 2 (Fragment, glsl)

Code: Select all

 
uniform sampler2D colorMap;
uniform sampler2D glowMap;
varying vec2 Texcoord1;
varying vec2 Texcoord2;
 
const int gaussRadius = 15;
const int gaussRadiusHalf = 7;
float gaussFilter[gaussRadius] = float[gaussRadius](
 0.001691,0.005779,0.016346,0.038269,0.074163,0.118968,0.157970,0.173629,0.157970,0.118968,
 0.074163,0.038269,0.016346,0.005779,0.001691);
 
const vec2 uShift = vec2(0.0, 1.0/512.0);
 
void main()
{
    vec4 baseCol = texture2D(colorMap, Texcoord1);
 
    vec3 glowCol = vec3(0.0, 0.0, 0.0); 
    vec2 locTextCoord = Texcoord2 - gaussRadiusHalf*uShift;
    for (int i=0; i<gaussRadius; ++i)
    { 
        glowCol += gaussFilter[i] * texture2D(glowMap, locTextCoord).xyz;
        locTextCoord += uShift;
    }
 
    if ((baseCol.r == 0) && (baseCol.g == 0) && (baseCol.b == 0))
        gl_FragColor = vec4(glowCol, 0.4);
    else
        gl_FragColor = baseCol + vec4(glowCol, 0.0);
}
 
I created an open office sheet to calculate the gaussian weights... (PM, when you want it.)

-------------------------------------------------------------------------------------------------------

The second effect is much more a shader effect. It's some kind of volume light. I have a directional light in the scene and transform the vector into a texture coordinate.

Code: Select all

 
void SWorld::getCamStuff(ICameraSceneNode *cam)
{
    vector3df view = cam->getTarget() - cam->getPosition();
    view.normalize();
    vector3df light = MAIN_LIGHT_DIR_C;
    light.normalize();
    f32 a = view.dotProduct(light);
    vector3df sl = light - view * a;
    if (abs_(a) < 0.1)
            sl *= 20000.0/0.1;                   // this 20000.0 is my 'far' clipping plane
    else
            sl *= 20000.0/abs_(a);
    const SViewFrustum *frustum = cam->getViewFrustum();
    vector3df upVect = frustum->getFarLeftUp() - frustum->getFarLeftDown();
    vector3df rightVect = frustum->getFarRightDown() - frustum->getFarLeftDown();
    shader->calcLVect.X = 0.5f+rightVect.dotProduct(sl) / rightVect.dotProduct(rightVect);
    shader->calcLVect.Y = 0.5f+upVect.dotProduct(sl) / upVect.dotProduct(upVect);
}
 
So the scene rendering looks like:

Code: Select all

 
    /* draw the complete scene to a large rtt */
    driver->setRenderTarget(rtt[0], true, true, SColor(0,0,0,0));
    skyBox->setVisible(false);
    smgr->drawAll();
 
    if (pp_rayOn)
    {
        /* now copy the large rtt to a smaller one */
        driver->setRenderTarget(rtt[1], false, false, SColor(0,0,0,0));
        screenQuadNode[0]->render();
 
        /* do the ray stuff */
        driver->setRenderTarget(rtt[2], false, false, SColor(0,0,0,0));
        screenQuadNode[1]->render();
    }
    else
        driver->setRenderTarget(rtt[2], true, false, SColor(0,0,0,0));
 
    /* and finally draw the base rtt + the glow rtt into the frame buffer */
    driver->setRenderTarget(ERT_FRAME_BUFFER, false, false, SColor(0,0,0,0));
    skyBox->setVisible(true);
    smgr->drawSkyBox();
    screenQuadNode[2]->render();
 
The init stuff was this:

Code: Select all

 
    rtt[0] = driver->addRenderTargetTexture(dimension2d<u32>(2048, 1024), "RTT0", ECF_A8R8G8B8);
    rtt[1] = driver->addRenderTargetTexture(dimension2d<u32>(512, 512), "RTT1", ECF_A8R8G8B8);
    rtt[2] = driver->addRenderTargetTexture(dimension2d<u32>(512, 512), "RTT2", ECF_A8R8G8B8);
 
    screenQuadNode[0] = new CScreenQuadSceneNode(smgr->getRootSceneNode(), smgr, -1);
    screenQuadNode[0]->getMaterial(0).MaterialType = EMT_SOLID;
    screenQuadNode[0]->getMaterial(0).setTexture(0, rtt[0]);
    screenQuadNode[0]->flipHorizontal();
 
    screenQuadNode[1] = new CScreenQuadSceneNode(smgr->getRootSceneNode(), smgr, -1);
    screenQuadNode[1]->getMaterial(0).MaterialType = (E_MATERIAL_TYPE)shader->getShaderMaterial(SShader::RayShader);
    screenQuadNode[1]->getMaterial(0).setTexture(0, rtt[1]);
 
    screenQuadNode[2] = new CScreenQuadSceneNode(smgr->getRootSceneNode(), smgr, -1);
    screenQuadNode[2]->getMaterial(0).MaterialType = (E_MATERIAL_TYPE)shader->getShaderMaterial(SShader::BlendShader);
    screenQuadNode[2]->getMaterial(0).setTexture(0, rtt[0]);
    screenQuadNode[2]->getMaterial(0).setTexture(1, rtt[2]);
    screenQuadNode[2]->getMaterial(0).BlendOperation = EBO_ADD;
 
The ray effect looks like this:
Image

The ray shader (vertex glsl):

Code: Select all

 
varying vec2 Texcoord;
 
void main()
{
    gl_Position = ftransform();
    Texcoord    = gl_MultiTexCoord0.xy;
}
 
The ray shader (fragement glsl):

Code: Select all

 
uniform sampler2D colorMap;
uniform vec4 fogColor;
uniform vec2 lPos;
 
varying vec2 Texcoord;
 
void main()
{
    const float texSize=512.0;
    
    int cnt = 0; 
    vec2 uShift = Texcoord - lPos;
    float len = length(uShift) * texSize;
    uShift = uShift / len;
    const float ofs = 0.5/texSize;
    
    vec2 locTextCoord = Texcoord;
    while ((locTextCoord.x > ofs) && (locTextCoord.y > ofs) && (locTextCoord.x < 1.0-ofs) && (locTextCoord.y < 1.0-ofs) && (len > 0.0))
    {
        vec4 baseCol = texture2D(colorMap, locTextCoord);
        if (baseCol.r + baseCol.g + baseCol.b > 0.0)
            ++cnt;
        locTextCoord -= uShift;
        len = len - 1.0;
    }
 
    float value;
    if (cnt == 0)
        value = 0.05;
    else
        value = clamp(0.05 - 0.0007*cnt, 0.0, 0.05);
    gl_FragColor = vec4(fogColor.rgb, value);
}
 
And all together in the scene:
Image


If you have any questions, post them here or send me a PM.

And have fun with shaders... :wink:
SpaceCombat
http://sourceforge.net/projects/spacecombatgame
(OpenSource space combat simulation)
christianclavet
Posts: 1638
Joined: Mon Apr 30, 2007 3:24 am
Location: Montreal, CANADA
Contact:

Re: Postprocessing for complex scene

Post by christianclavet »

Hi,
Really nice stuff... Looking at this you draw it one time in a RTT then redraw using the texture and add the FX over the screenquad.

Where is the code for the ScreenQuad scene node?
Looking at the way you call it, seem to be external:
screenQuadNode[0] = new CScreenQuadSceneNode(smgr->getRootSceneNode(), smgr, -1);
tom_gamer
Posts: 15
Joined: Sat Jun 01, 2013 8:51 am

Re: Postprocessing for complex scene

Post by tom_gamer »

Here is the code for the CScreenQuadSceneNode. (based on some code from this forum)

Header-file

Code: Select all

 
#ifndef CSCREENQUADSCENENODE_H
#define CSCREENQUADSCENENODE_H
 
#include <irrlicht.h>
 
using namespace irr;
 
 
class CScreenQuadSceneNode : public scene::ISceneNode
{
          core::aabbox3df aabb;                //An axis aligned bounding box. Actually not needed.
          video::SMaterial material;            //The material used to render the Scene Node
          video::S3DVertex2TCoords vertices[4]; //The vertices of the Scene Node.
                                         //Normally we wouldn't need more
                                         //than one set of UV coordinates.
                                         //But if we are to use the builtin materials, this is necesary
 
    public:
        CScreenQuadSceneNode(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id);
        ~CScreenQuadSceneNode();
 
        const core::aabbox3df& getBoundingBox() const;
 
        void OnRegisterSceneNode();
        void ChangeMaterialType(video::E_MATERIAL_TYPE NewMat);
        void render();
        u32 getMaterialCount();
        video::SMaterial& getMaterial(u32 i);
        void flipHorizontal();
    };
 
#endif // CSCREENQUADSCENENODE_H
 
CPP-File

Code: Select all

 
#include "CScreenQuadSceneNode.h"
 
CScreenQuadSceneNode::CScreenQuadSceneNode(scene::ISceneNode* parent, scene::ISceneManager* mgr, s32 id) :ISceneNode(parent,mgr,id)
{
    f32 shiftX,shiftY;
    core::dimension2d<u32> currentResolution;
 
    /** Here we initialize the vertices of the screen Aligned quad */
 
    currentResolution = mgr->getVideoDriver()->getScreenSize();
 
    aabb.reset(0,0,0);
 
    shiftX = 0.5/currentResolution.Width;    //This small shift is necesary to compensate the texture sampling bias
    shiftY = 0.5/currentResolution.Height;   //It avoids that our effect becomes too blurry.
 
    vertices[0] = video::S3DVertex2TCoords(-1.0f,-1.0f,0.0f, 0.0f,0.0f,-1.0f, video::SColor(255,255,255,255), shiftX,shiftY,            shiftX,shiftY);
    vertices[1] = video::S3DVertex2TCoords(1.0f,-1.0,0.0f,   0.0f,0.0f,-1.0f, video::SColor(255,255,255,255), 1.0f+shiftX,shiftY,       1.0f+shiftX,shiftY);
    vertices[2] = video::S3DVertex2TCoords(-1.0f,1.0,0.0f,   0.0f,0.0f,-1.0f, video::SColor(255,255,255,255), shiftX,1.0f+shiftY,       shiftX,1.0f+shiftY);
    vertices[3] = video::S3DVertex2TCoords(1.0f,1.0f,0.0f,   0.0f,0.0f,-1.0f, video::SColor(255,255,255,255), 1.0f+shiftX,1.0f+shiftY,  1.0f+shiftX,1.0f+shiftY);
 
    /** Now we proceed to initialize the appropriate settings for the material we are going to use
        We can alter these later, but for the time being, initializing them here will do no harm */
 
    material.Lighting = false;                  //No need for lighting.
    material.MaterialType = video::EMT_SOLID;   //This will add both first and second textures :)
    material.BackfaceCulling=false;             //not needed, but simplifies things
    setAutomaticCulling(scene::EAC_OFF);        //We don't need this scene node to be culled because we render it in screen space.
    material.ZBuffer = video::ECFN_ALWAYS;
    material.ZWriteEnable = false;
}
 
CScreenQuadSceneNode::~CScreenQuadSceneNode()
{
}
 
const core::aabbox3df& CScreenQuadSceneNode::getBoundingBox() const
{
    return aabb;
}
 
void CScreenQuadSceneNode::OnRegisterSceneNode()
{
    //This method is empty because it is best for us to render this scene node manually.
    //So, it is never really rendered on its own, if we don't tell it to do so.
}
 
void CScreenQuadSceneNode::ChangeMaterialType(video::E_MATERIAL_TYPE NewMat)
{
    material.MaterialType= NewMat;
}
 
void CScreenQuadSceneNode::render()
{
    video::IVideoDriver* drv = getSceneManager()->getVideoDriver();
    core::matrix4 proj;
    u16 indices[] = {0,1,2,3,1,2};
    //A triangle list
 
    drv->setMaterial(material);
 
    drv->setTransform(video::ETS_PROJECTION, core::IdentityMatrix);
    drv->setTransform(video::ETS_VIEW, core::IdentityMatrix);
    drv->setTransform(video::ETS_WORLD, core::IdentityMatrix);
 
    drv->drawIndexedTriangleList(&vertices[0],4,&indices[0],2);
}
 
u32 CScreenQuadSceneNode::getMaterialCount()
{
    return 1;   //There is only one material
}
 
video::SMaterial& CScreenQuadSceneNode::getMaterial(irr::u32 i)
{
    return material;//We always return the same material, so there is no need for more.
}
 
void CScreenQuadSceneNode::flipHorizontal()
{
    core::vector2d<f32> temp;
    temp = vertices[2].TCoords; vertices[2].TCoords = vertices[0].TCoords; vertices[0].TCoords = temp;
    temp = vertices[2].TCoords2; vertices[2].TCoords2 = vertices[0].TCoords2; vertices[0].TCoords2 = temp;
    temp = vertices[3].TCoords; vertices[3].TCoords = vertices[1].TCoords; vertices[1].TCoords = temp;
    temp = vertices[3].TCoords2; vertices[3].TCoords2 = vertices[1].TCoords2; vertices[1].TCoords2 = temp;
}
 
Maybe this is something for a future Irrlicht release. Everyone, that make shader postprocessing, will implement such a screen quad.
SpaceCombat
http://sourceforge.net/projects/spacecombatgame
(OpenSource space combat simulation)
Post Reply