Game Development Community

Time-dependent light effects

by Daniel Buckmaster · in Torque Game Engine · 09/26/2009 (12:51 am) · 6 replies

I'm adding flickering and pulsing lights to my swarm class (see blogs), and having some trouble updating the light intensity at a constant rate. I tried implementing an mLastLightTime like the mLastRenderTime in fxRenderObject, but it doesn't seem to be giving me good results. I've used both Platform::getVirtualMilliseconds (as in fxRenderObject) and Sim::getCurrentTime (as in Item) to try to get the time elapsed between light registers. I'm wondering if I should break the time-dependent stuff out of registerLights completely, to somewhere that I have a reliable timestep.

Here's my registerLights method:
void Swarm::registerLights(LightManager *lm,bool lightingScene)
{
   //Don't want to cause changes to the scene lightmap
   if(lightingScene)
      return;

   //Last render time
   S32 time = Sim::getCurrentTime();
   //Time in seconds since last render
	F32 elapsedTime = (time - mLastLightTime) * 0.001f;
	mLastLightTime = time;

   switch(mDataBlock->lightType)
   {
   case SwarmData::Constant:
      {
         mLightIntensity = 1.0f;
         break;
      }

   case SwarmData::Pulsing:
      {
         //Get the radian angle of our light intensity
         F32 current = mAsin(mLightIntensity);
         //Add to it depending on our period
         current += M_2PI_F / (mDataBlock->lightPeriod * elapsedTime);
         //Constrain it to 0...2PI
         while(current > M_2PI_F)
            current -= M_2PI_F;
         //Put it back in the range 0...1
         mLightIntensity = 0.5f + 0.5f * mSin(current);
         break;
      }

   case SwarmData::Flickering:
      {
         //lightPeriod now gives the average number of flickers per second
         bool current = (mLightIntensity > 0.0f);
         MRandomLCG random(Platform::getRealMilliseconds());
         if(random.randF() <= mDataBlock->lightPeriod * elapsedTime)
            current = !current; //Switch light state
         //Set intensity based on current
         mLightIntensity = current? 1.0f : 0.0f;
         break;
      }

   default:
      return;
   }

   mLight.mColor = mDataBlock->lightColour * mLightIntensity;
   mLight.mColor.clamp();
   mLight.mType = LightInfo::Point;
   mLight.mRadius = mDataBlock->lightRadius;

   mLight.mPos = getBoxCenter();

   lm->sgRegisterGlobalLight(&mLight);
}
Pulsing is the main problem there. Flickering is also broken, but I think I know why :P.



EDIT: I separated the update stuff out into advanceTime, which gives dt in seconds, so I'm fairly sure it's not a problem with my time-geting method, but with my maths. Hmm.

About the author

Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!


#1
09/26/2009 (2:02 am)
Quote:current += M_2PI_F / (mDataBlock->lightPeriod * elapsedTime);

... Yeah I'm pretty sure that ain't right. lightPeriod and elapsedTime should have the opposite affect on the equation. The greater the lightPeriod the slower the cycle (I'm assuming) so you'd want a smaller advance per cycle. OTOH the greater the elapsedTime, the larger the advance should be. Yet you're multiplying those values which makes no sense to me. How about:

current += M_2PI_F * (elapsedTime / mDataBlock->lightPeriod);

Now the greater the lightPeriod the smaller the advance; and the larger the elapsedTime the greater the advance. When elapsedTime == lightPeriod we advance a full 2PI (complete cycle). At elapsedTime == half lightPeriod we see a half cycle advance of PI. I'm pretty tired, but I think that's what you were after.

Yeah wow I'm tired I almost missed this: You'll need to perform the range shifting on both ends...
F32 current = mAsin(mLightIntensity * 2.f - 1.f);

(edit: Also to avoid a divide by zero now you'll either want to check for lightPeriod != 0 or have the DataBlock initPersistFields with a RangeValidator)
#2
09/26/2009 (2:44 am)
RE Flickering:

mLightIntensity = mClampF(Platform::getRandom() * mDataBlock->lightPeriod, 0.f, 1.f);
should produce a decent candle-like flicker with variable brightness, flickering fairly consistently at lightPeriod = 1, with increasingly intermittent flickering as you turn up lightPeriod.

Or for a flicker more like a florescent bulb where it's either on or off, you could say
mLightIntensity = (Platform::getRandom() * mDataBlock->lightPeriod > 0.5f) ? 1.f : 0.f;

Here elapsedTime doesn't matter so much since the distribution of the results from getRandom should be fairly consistent regardless of the sample rate. Although either way a higher framerate will produce a more "fine grain" flicker. How noticeable that will be I've no idea, but if you want to minimize the affect of a variable framerate you could do something like this:

if (elapsedTime > 0.032f)
   mLightIntensity = whatever;
else
   mLastLightTime -= S32(elapsedTime * 1000.f); // No update now, (un)credit mLastLightTime
Which will reduce the frequency of the flicker updates to once every 32ms
#3
09/26/2009 (8:45 am)
Wow, thanks! I wasn't expecting that detailed of a response!

Quote:Yeah I'm pretty sure that ain't right. lightPeriod and elapsedTime should have the opposite affect on the equation. The greater the lightPeriod the slower the cycle (I'm assuming) so you'd want a smaller advance per cycle.
Aha. That makes sense :P. I did the maths (speed = distane/time) for lightPeriod, but didn't consider the effects of elapsedTime.

Quote:You'll need to perform the range shifting on both ends...
Are you sure? Because mLightIntensity always scales from 0 to 1. But I'll try it both ways, thanks for the detailed analysis!
EDIT: Stupid me... mAtan takes a domain from -1 to 1. I'm forgetting my trig and I only finished HS a few months ago :P.

Quote:mLightIntensity = mClampF(Platform::getRandom() * mDataBlock->lightPeriod, 0.f, 1.f);
I was just looking to turn the light on/off with a random period between each switch, but this looks like it should work just as well - thanks!

Recompiling now ;). Thank you again for the swift and comprehensive help.

Oh - and thanks for the tip on Platform::getRandom. What's the return range for that?


EDIT: Oh. Doesn't seem to work :P. At least pulsing doesn't seem to work. Flickering is fine, though.

Debugging is a pain :P.

I think my trouble might be that elapsedTime seems to be constant at 1.024. I thought this was due to the fact that the debugger would take time to get itself ready when going into a breakpoint. But that shoudn't be the case when I hit the breakpoint first, should it? Only after hitting 'continue' and breaking again immediately? Maybe my time calculation is off - but it was lifted straight from fxRenderObject. Hmph.
#4
09/26/2009 (11:50 am)
Whoa no no no no no. Man I was tired last night. The entire pulse premise is flawed. Think about it: you're trying to create a progression based on the current state of a sin wave right? Only, at each cycle you have no way of knowing whether that value is supposed to be headed up or down. For example, say we're progressing up towards 1. Right now we're at 0.98. Great, let's increment it slightly until it would be at 1.1. Only this being a sin wave, once it peaks it starts heading down right? So the value should now be near 1 at 0.98 heading towards 0. Guess what? Next cycle the value is at 0.98.... see?

What you need is some value with a steady linear progression like say... time. Forget elapsedTime, just grab the current time (scale it to vary the frequency of the pulse), run it through mSin and you're done!

mLightIntensity = (Sim::getCurrentTime() * 0.001f) * M_2PI_F / mDataBlock->lightPeriod;
mLightIntensity = mSin(mLightIntensity) * 0.5f + 0.5f;

(again, check for divide by zero) And it's that simple.
#5
09/26/2009 (12:20 pm)
Arg :P. I really am forgetting my maths. I guess I should have done it the way Item did it right from the off :P. Ah well - thank you again for your keen eye!

Is it really as simple as getCurrentTime()? Are there performance poblems with putting large numbers through mSin?


EDIT: Works like a charm :).
#6
09/26/2009 (12:37 pm)
Quote:Are there performance poblems with putting large numbers through mSin?

Interesting question. I doubt it, but I don't know. Regardless, unless you've got mSin within a loop that's going to be run many many times per frame I wouldn't worry about it. In a situation like this I'm confident any possible performance impact would be too small to measure.

Oh, and Platform::getRandom() returns a random float in the range 0 to 1 inclusive, IIRC.