(C++) Scene Node Fade Animator (Transparency)

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
maverickbu
Posts: 17
Joined: Tue Dec 05, 2006 5:07 pm

(C++) Scene Node Fade Animator (Transparency)

Post by maverickbu »

I couldn't find good answer on how to effeciently fade a scene node, so I came up with one using the existing scene node animator examples. Many suggested using lock()/unlock() on the node's texture to edit the pixels and change the alpha value, which works, but I was warned that it is costly in terms of resources. So basically what I did is select the texture I want to be able to fade, load multiple versions of it into memory using createImageFromData(), save them in a vector, pass it as a reference to the animator, and now the animator can iterate through the textures over a designated period of time causing a "fade" effect.

Here's the CFadeAnimator class:

CFadeAnimator.h

Code: Select all

#ifndef _CFadeAnimator_h__
#define _CFadeAnimator_h__

#define CFA_FADEIN  1
#define CFA_FADEOUT 2

#include "ISceneNode.h"
#include <vector>

namespace irr {
namespace scene {
    class CFadeAnimator : public ISceneNodeAnimator {
    public:
        //! constructor
        CFadeAnimator(int mode, u32 timeForWay, u32 now, std::vector<video::ITexture*> *textures);

        //! destructor
        virtual ~CFadeAnimator();

        //! animates a scene node
        virtual void animateNode(ISceneNode* node, u32 timeMs);

    private:
        int FadeMode;
        f32 TimeFactor;
        u32 StartTime;
        u32 TimeForWay;
        int Start, End;
        std::vector<video::ITexture*> *Textures;
    };
}}

#endif
and CFadeAnimator.cpp:

Code: Select all

#include "CFadeAnimator.h"

namespace irr {
namespace scene {

CFadeAnimator::CFadeAnimator(int mode, u32 timeForWay, u32 now,
    std::vector<video::ITexture*> *textures) : StartTime(now), TimeForWay(timeForWay),
    Textures(textures), FadeMode(mode) {
    #ifdef _DEBUG
	setDebugName("CFadeAnimator");
	#endif

    int max = 0;

    if (!textures) {
        Start = 0;
        End = 0;
        TimeFactor = 0;
        return;
    } else {
        if (textures->size() <= 0) {
            Start = 0;
            End = 0;
            TimeFactor = 0;
            return;
        }
    }

    max = textures->size() - 1;
    if (FadeMode == CFA_FADEIN) {
        Start = 0;
        End = max;
    } else if (FadeMode == CFA_FADEOUT) {
        Start = max;
        End = 0;
    } else {
        Start = 0;
        End = 0;
        TimeFactor = 0;
        return;
    }

    TimeFactor = (f32)max / TimeForWay;
}

CFadeAnimator::~CFadeAnimator() {

}

void CFadeAnimator::animateNode(ISceneNode* node, u32 timeMs) {
    if (!node) {
        return;
    }

    u32 t = timeMs - StartTime;
    int index = Start;

    if (t >= TimeForWay) {
        index = End;
    } else {
        if (FadeMode == CFA_FADEIN) {
            index = (int)((f32)fmod(t, TimeForWay) * TimeFactor);
        } else if (FadeMode == CFA_FADEOUT) {
            index = Start - (int)((f32)fmod(t, TimeForWay) * TimeFactor);
        }
    }

    node->setMaterialTexture(0, Textures->at(index));
}

}}
Here is also some example code on how to generate the vector of ITexture ponters necessary to fade your texture. This should be done during the initialization of your program:

Code: Select all

    // Necessary Includes //
    #include <vector>
    #include <sstream> // cause I'm lazy

    // The vector to hold your fade textures //
    std::vector<irr::video::ITexture*> FadeTextures;

    // Get your texture and some necessary attributes
    irr::video::ITexture *source = Driver->getTexture("/path/to/texture");
    core::dimension2d<s32> dim = source->getSize();
    video::ECOLOR_FORMAT format = source->getColorFormat();
    s32 pitch = source->getPitch();
    
    for (int i = 0; i <= 255; i=i+5) {
        // Change alpha channel based on "i" //
        unsigned char *pixels = (unsigned char*)source->lock();
        for (int x = 0; x < dim.Width; x++) {
            for (int y = 0; y < dim.Height; y++) {
                s32 offset = (y*pitch)+(x*4);
                (pixels+offset)[3] = i;
            }
        }
        source->unlock();

        // Create a new image and load it as a texture //
        irr::video::IImage* image = Driver->createImageFromData(format, dim, pixels, false);
        std::stringstream tname;
        tname << "texturename-" << i;
        irr::video::ITexture *texture = Driver->addTexture(tname.str().c_str(), image);
        FadeTextures.push_back(texture);
    }
And there you go. I'm well aware that there are probably much better ways to do pretty much everything involved here, but I'm still learning Irrlicht so this is what I came up with. This works well in my current project, but thats as far as I can warrant its quality for now. I intend to add "begin" and "end" fade values to the constructor to do partial fades in the near future. If there's any interest, I'll post it up here.

Hope this helps! :D
Acki
Posts: 3496
Joined: Tue Jun 29, 2004 12:04 am
Location: Nobody's Place (Venlo NL)
Contact:

Post by Acki »

Looks interesting... ;)
A little demo/tutorial for how to use it would be nice for noobs, too... ;)
while(!asleep) sheep++;
IrrExtensions:Image
http://abusoft.g0dsoft.com
try Stendhal a MORPG written in Java
sio2
Competition winner
Posts: 1003
Joined: Thu Sep 21, 2006 5:33 pm
Location: UK

Post by sio2 »

Don't do this. Use vertex alpha.

I'll put together something to do this in a better way, once I get the chance.
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

I don't think that either solution will be really a good choice. The first one has the disadvantage of high memory consumption and is also limited to one texture per mesh (otherwise it will become really weird). And using vertex colors is non-restorable and also quite slow.
sio2
Competition winner
Posts: 1003
Joined: Thu Sep 21, 2006 5:33 pm
Location: UK

Post by sio2 »

hybrid wrote:I don't think that either solution will be really a good choice. The first one has the disadvantage of high memory consumption and is also limited to one texture per mesh (otherwise it will become really weird). And using vertex colors is non-restorable and also quite slow.
Using D3D the source for the vertex colour can come from a constant - constant to the draw call. I forget the exact texture stage setup - I don't use fixed-function stuff much at all now.
sio2
Competition winner
Posts: 1003
Joined: Thu Sep 21, 2006 5:33 pm
Location: UK

Post by sio2 »

Here's something that works and is fast ( as in no mesh modification). It uses VS1.1 and PS1.1:

main.cpp

Code: Select all

#include <irrlicht.h>

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

#pragma comment(lib, "Irrlicht.lib")

// --------------------------------------------------------------------------

class CFadeMaterial : public video::IShaderConstantSetCallBack
{
private:
	f32	m_fadeFactor; // Fade factor 0..1
	s32 m_fadeMaterial; // Handle/id of fade material
public:
	CFadeMaterial() {m_fadeFactor = 1.0f; m_fadeMaterial = video::EMT_SOLID;};
	bool init(ISceneManager* smgr, IVideoDriver* driver);
	virtual void OnSetConstants(video::IMaterialRendererServices* services, s32 userData);
public:
	s32 getFadeMaterial(void) {return m_fadeMaterial;};
	f32 getFadeFactor(void) {return m_fadeFactor;};
	void setFadeFactor(f32 fadeFactor) {m_fadeFactor = fadeFactor;};
};

void CFadeMaterial::OnSetConstants(video::IMaterialRendererServices* services, s32 userData)
{
	video::IVideoDriver* driver = services->getVideoDriver();

	// World/View/Projection matrix
	core::matrix4 worldViewProj;
	worldViewProj = driver->getTransform(video::ETS_PROJECTION);			
	worldViewProj *= driver->getTransform(video::ETS_VIEW);
	worldViewProj *= driver->getTransform(video::ETS_WORLD);
	services->setVertexShaderConstant("mWorldViewProj", &worldViewProj.M[0], 16);

	services->setPixelShaderConstant("fadeFactor", &m_fadeFactor, 1);
};

bool CFadeMaterial::init(ISceneManager* smgr, IVideoDriver* driver)
{
	if (!smgr || ! driver)
		return false;

	video::IGPUProgrammingServices *gpu = driver->getGPUProgrammingServices();
	if (!gpu) return false;

	video::E_DRIVER_TYPE edt = driver->getDriverType();

	if (edt == video::EDT_OPENGL) {
		m_fadeMaterial = gpu->addHighLevelShaderMaterialFromFiles(
			"fademesh.vp", "main", video::EVST_VS_1_1,
			"fademesh.fp", "main", video::EPST_PS_1_1,
			this, video::EMT_TRANSPARENT_ALPHA_CHANNEL);
	} else if (edt == video::EDT_DIRECT3D9) {
		m_fadeMaterial = gpu->addHighLevelShaderMaterialFromFiles(
			"fademesh.hlsl", "vertexMain", video::EVST_VS_1_1,
			"fademesh.hlsl", "pixelMain", video::EPST_PS_1_1,
			this, video::EMT_TRANSPARENT_ALPHA_CHANNEL);
	} else {
		m_fadeMaterial = video::EMT_SOLID;
	}

	return true;
}

// --------------------------------------------------------------------------

int main()
{
	IrrlichtDevice *device =
		createDevice( video::EDT_DIRECT3D9, dimension2d<s32>(640, 480), 32,
			false, false, false);

	device->setWindowCaption(L"FadeMesh - Demo of Fading a Mesh");

	IVideoDriver* driver = device->getVideoDriver();
	ISceneManager* smgr = device->getSceneManager();
	IGUIEnvironment* guienv = device->getGUIEnvironment();

	IAnimatedMesh* mesh = smgr->getMesh("sydney.md2");
	IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode( mesh );
	if (node)
	{
		node->setMaterialFlag(EMF_LIGHTING, false);
		node->setMD2Animation ( scene::EMAT_STAND );
		node->setMaterialTexture( 0, driver->getTexture("sydney.bmp") );
	}

	CFadeMaterial fadeMaterial;
	fadeMaterial.init(smgr, driver);
	node->setMaterialType((video::E_MATERIAL_TYPE)fadeMaterial.getFadeMaterial());

	fadeMaterial.setFadeFactor(0.5f);

	smgr->addCameraSceneNode(0, vector3df(0,30,-40), vector3df(0,5,0));

	while(device->run() && device->isWindowActive())
	{
		driver->beginScene(true, true, SColor(255,100,101,140));
		smgr->drawAll();
		driver->endScene();

		f32 fTime = float(device->getTimer()->getRealTime()) / 1000.0f;
		f32 fade = sinf(fTime);
		fade = core::clamp(fade, 0.4f, 1.0f);
		fadeMaterial.setFadeFactor(fade);
	}

	device->drop();

	return 0;
}

// --------------------------------------------------------------------------
fademesh.hlsl:

Code: Select all

uniform float4x4 mWorldViewProj;
uniform float fadeFactor;
sampler2D diffuseTexture : register(s0);

struct VS_OUT {
	float4 Pos : POSITION;
	float2 TexCoord0 : TEXCOORD0;
};

VS_OUT vertexMain(float3 Pos : POSITION, float2 Tex0 : TEXCOORD0)
{
	VS_OUT o = (VS_OUT)0;
	o.Pos = mul(float4(Pos,1), mWorldViewProj);
	o.TexCoord0 = Tex0;
	return o;
}

float4 pixelMain(VS_OUT i) : COLOR
{
	float4 gl_FragColor;
	gl_FragColor.rgb = tex2D(diffuseTexture, i.TexCoord0.xy);
	gl_FragColor.a = fadeFactor;
	return gl_FragColor;
}
Acki
Posts: 3496
Joined: Tue Jun 29, 2004 12:04 am
Location: Nobody's Place (Venlo NL)
Contact:

Post by Acki »

Well, I made a small example for this (for the main post):

Code: Select all

#include "CFadeAnimator.h"
#include <irrlicht.h>
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

IrrlichtDevice* device;
IAnimatedMeshSceneNode* node;
// The vector to hold your fade textures //
std::vector<ITexture*> FadeTextures;

class MyEventReceiver : public IEventReceiver{
public:
  virtual bool OnEvent(SEvent event){
    if (event.EventType == EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown){
      switch(event.KeyInput.Key){
        case KEY_KEY_1:{ // fade out the node within 2 seconds (2000 ms)
          node->removeAnimators();
          CFadeAnimator* anim = new CFadeAnimator(CFA_FADEOUT, (u32)2000, device->getTimer()->getTime(), &FadeTextures);
          node->addAnimator(anim);
          anim->drop();
          return true;
		    }
        case KEY_KEY_2:{ // fade in the node within 2 seconds (2000 ms)
          node->removeAnimators();
          CFadeAnimator* anim = new CFadeAnimator(CFA_FADEIN, (u32)2000, device->getTimer()->getTime(), &FadeTextures);
          node->addAnimator(anim);
          anim->drop();
          return true;
		    }
        case KEY_ESCAPE:{ // close the program
          device->closeDevice();
          reurn true;
        }
      }
    }
    return false;
  }
};

int main(){
  MyEventReceiver receiver;
  device = createDevice(EDT_DIRECT3D9, dimension2d<s32>(640, 480), 16, false, false, false, &receiver);
  IVideoDriver* driver = device->getVideoDriver();
  ISceneManager* smgr = device->getSceneManager();
  IGUIEnvironment* guienv = device->getGUIEnvironment();

  // add some info text
  guienv->addStaticText(L"1 - fade out\n2 - fade in\nEscape - quit", rect<int>(10,10,200,220));

  // Get your texture and some necessary attributes
  ITexture *source = driver->getTexture("C:/CodeBlocks/AB-AddOn/Media/Irrlicht/sydney.bmp");
  dimension2d<s32> dim = source->getSize();
  ECOLOR_FORMAT format = source->getColorFormat();

  for (int i = 0; i <= 255; i+=5) {
    // Change alpha channel based on "i" //
    SColor* pixels = (SColor*)source->lock();
    for (int x = 0; x < dim.Width; x++) {
      for (int y = 0; y < dim.Height; y++) {
        pixels[y*dim.Width + x].setAlpha(i);
      }
    }
    source->unlock();

    // Create a new image and load it as a texture //
    IImage* image = driver->createImageFromData(format, dim, pixels, false);
    ITexture *texture = driver->addTexture("", image);
    FadeTextures.push_back(texture);
  }

  // Create the node
  IAnimatedMesh* mesh = smgr->getMesh("C:/CodeBlocks/AB-AddOn/Media/Irrlicht/sydney.md2");
  node = smgr->addAnimatedMeshSceneNode(mesh);
  node->setMaterialTexture(0, source);
  node->setMaterialFlag(EMF_LIGHTING, false);
  node->setFrameLoop(1, 1);

  // Set the nodes parameters for blending on alpha channel
  node->setMaterialType(video::EMT_TRANSPARENT_ALPHA_CHANNEL);
  node->getMaterial(0).MaterialTypeParam = 0.01;

  // run Irrlicht render loop
  smgr->addCameraSceneNode()->setPosition(vector3df(0,10,-50));
  while(device->run()){
    driver->beginScene(true, true, video::SColor(0,0,110,110));
    smgr->drawAll();
    guienv->drawAll();
    driver->endScene();
  }
  device->drop();
  return 0;
}
But yes, there are some restrictions, like memory usage and only 1 texture, but it works well... ;)
while(!asleep) sheep++;
IrrExtensions:Image
http://abusoft.g0dsoft.com
try Stendhal a MORPG written in Java
rogerborg
Admin
Posts: 3590
Joined: Mon Oct 09, 2006 9:36 am
Location: Scotland - gonnae no slag aff mah Engleesh
Contact:

Post by rogerborg »

hybrid wrote:And using vertex colors is non-restorable and also quite slow.
Slow to change the vertex alphas, or slow to render?
Please upload candidate patches to the tracker.
Need help now? IRC to #irrlicht on irc.freenode.net
How To Ask Questions The Smart Way
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Slow to change. You have to touch each vertex once to make one change. Depending on the mesh size and texture size it could be easier to chaneg the texture instead.
rogerborg
Admin
Posts: 3590
Joined: Mon Oct 09, 2006 9:36 am
Location: Scotland - gonnae no slag aff mah Engleesh
Contact:

Post by rogerborg »

Check my maths: changing the alpha on each texel of a 256x256 texture is 65536 colour operations, which is as many as you could possibly have in a mesh. CMeshManipulator::setVertexColorAlpha seems fairly efficiently implemented.

I'm considering going with the multiple-texture version, but I'm going to check the feasibility of also creating multiple alpha-translucent meshes as well.
Please upload candidate patches to the tracker.
Need help now? IRC to #irrlicht on irc.freenode.net
How To Ask Questions The Smart Way
Post Reply