AtmosphereNpr: colour flash at dusk/dawn caused by fabs(result.time) in updatePreset()

Discussion area about developing with Ogre-Next (2.1, 2.2 and beyond)


Lax
Orc Shaman
Posts: 701
Joined: Mon Aug 06, 2007 12:53 pm
Location: Saarland, Germany
x 73

AtmosphereNpr: colour flash at dusk/dawn caused by fabs(result.time) in updatePreset()

Post by Lax »

Hi dark_sylinc,

I’ve been integrating AtmosphereNpr with a continuous day/night cycle and ran into a reproducible colour flash (violet/blue) at dusk and dawn, even when time progression is smooth and monotonic.

After extensive debugging, this turns out to be an engine-side issue in AtmosphereNpr, not user code.

Symptoms

  • Smooth time progression (no jumps, no wrapping discontinuities)

    Presets sorted and valid

    Sun direction continuous

    Still: one-frame blue/violet colour flash near horizon (≈18:00 / ≈06:00)

The flash happens even when:

  • timeOfDay is monotonic
    preset interpolation is stable
    no reset or teleport occurs

Root cause (confirmed in OgreAtmosphereNpr.cpp)

In AtmosphereNpr::updatePreset():

Code: Select all

mNormalizedTimeOfDay = std::min( fabsf( result.time ), 1.0f - 1e-6f );

This line destroys the sign of time.

As a result:

  • result.time = -0.98 (evening)

    result.time = +0.98 (morning)

Both collapse to the same mNormalizedTimeOfDay = 0.98.

This causes a non-physical discontinuity in sun height, which then feeds into exponential scattering math:

Code: Select all

const float sunHeight = std::sin( mNormalizedTimeOfDay * Math::PI );
const float sunHeightWeight = std::exp2( -1.0f / sunHeight );

Near the horizon (sunHeight → 0), this produces a large Rayleigh spike → visible violet/blue flash.

This explains why:

  • time is smooth

    yet colour spikes occur exactly at dusk/dawn

Why this is engine-side

  • User code can ensure smooth time, sorted presets, correct wrapping

    But sign information is irreversibly lost inside AtmosphereNpr

    There is no way to fix this externally without fighting the engine each frame

Suggested fix (minimal and safe)
1️⃣ Preserve the sign of time

Replace:

Code: Select all

mNormalizedTimeOfDay = std::min( fabsf( result.time ), 1.0f - 1e-6f );

With:

Code: Select all

mNormalizedTimeOfDay = Math::Clamp( result.time, -1.0f + 1e-6f, 1.0f - 1e-6f);

2️⃣ Avoid double-frequency sun motion

Currently sun height is computed as:

Code: Select all

sin( mNormalizedTimeOfDay * PI )

For a signed [-1…1] time domain, this completes two half-cycles per day.

A physically consistent mapping would be:

Code: Select all

sin( (mNormalizedTimeOfDay * 0.5f) * PI )

This produces one smooth day/night cycle without horizon spikes.

3️⃣ (Optional safety) Clamp sunHeight before division

There are divisions by sunHeight in multiple places.
Clamping once avoids numerical spikes:

const float sunHeightSafe = std::max( sunHeight, 0.01f );

Result after patch

Code: Select all

No colour flash at dusk or dawn
Continuous sky colour
Stable Rayleigh/Mie behaviour
Correct separation between morning and evening

Notes
AtmosphereNpr appears to have originally been designed for a [0…1] time domain and later extended to [-1…1], but the fabs() usage was never adjusted.

This bug only shows up when running a full continuous day/night cycle, which may explain why it went unnoticed.

Best Regars
Lax

http://www.lukas-kalinowski.com/Homepage/?page_id=1631
Please support Second Earth Technic Base built of Lego bricks for Lego ideas: https://ideas.lego.com/projects/81b9bd1 ... b97b79be62

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5551
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1402

Re: AtmosphereNpr: colour flash at dusk/dawn caused by fabs(result.time) in updatePreset()

Post by dark_sylinc »

The LLM got many things wrong, but it is right the problem is related to numerical spikes at sunHeight.

In fact changing all instances of:

Code: Select all

std::sin( mNormalizedTimeOfDay * Math::PI )

with:

Code: Select all

std::max( std::sin( mNormalizedTimeOfDay * Math::PI ), 0.05f )

Fixes it. But IMO the night / purple colour is desirable, problem is that it's a sudden flash. Unless you don't want that colour at all, in which case the clamping is enough.

Otherwise, perhaps this could be fixed by "de-exponentiating" or "de-stretch" the time domain when it's close to 0.

I'm thinking of various formula approaches but they all failed except for this one:

Code: Select all

static const float kPowT = 6.0f;
static const float kPow = 12.0f;
mNormalizedTimeOfDay = std::min( fabsf( result.time ), 1.0f - 1e-6f );
mNormalizedTimeOfDay = 1.0f / ( 1.0f + std::exp( -mNormalizedTimeOfDay * kPow + kPowT ) );

I'm sure there are better solutions but I've spent too much time on this, and I'm terribly busy right now.

The idea is to make time "slower" when it's close to 0, and make time faster outside of that range to compensate.

Cheers

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5551
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1402

Re: AtmosphereNpr: colour flash at dusk/dawn caused by fabs(result.time) in updatePreset()

Post by dark_sylinc »

So much time spent when the following seems to do the trick:

Code: Select all

mNormalizedTimeOfDay = std::min( fabsf( result.time ), 1.0f - 1e-6f );
mNormalizedTimeOfDay = Math::smoothstep( 0.0f, 1.0f, mNormalizedTimeOfDay );
mNormalizedTimeOfDay = std::min( mNormalizedTimeOfDay, 1.0f - 1e-6f );
        
//And in setSunDir() mNormalizedTimeOfDay = std::min( normalizedTimeOfDay, 1.0f - 1e-6f ); mNormalizedTimeOfDay = Math::smoothstep( 0.0f, 1.0f, mNormalizedTimeOfDay ); mNormalizedTimeOfDay = std::min( mNormalizedTimeOfDay, 1.0f - 1e-6f );
Lax
Orc Shaman
Posts: 701
Joined: Mon Aug 06, 2007 12:53 pm
Location: Saarland, Germany
x 73

Re: AtmosphereNpr: colour flash at dusk/dawn caused by fabs(result.time) in updatePreset()

Post by Lax »

Hi,

I temporary added your suggestions to AtmosphereNpr and ONLY this now fixes it:

Code: Select all

std::max( std::sin( mNormalizedTimeOfDay * Math::PI ), 0.05f )

if replacing the old formula on several places.

Thanks! :D

Yeah this topic is hard. I did logging, debugging by myself and then added help of LLM, in which we found out, that the bug is not on my side.

Yes, i know, you are busy. I test newest Ogre-Next features and will post my findings. If you find time (in a month, or months :) ), then its fine.
Note also: I found a small bug on Ogre side to get ocean working. I posted it on the ocean thread. Its just a missing guard for array border. I hope you can in the future add also that fix to Ogre-Next.

Best Regards
Lax

http://www.lukas-kalinowski.com/Homepage/?page_id=1631
Please support Second Earth Technic Base built of Lego bricks for Lego ideas: https://ideas.lego.com/projects/81b9bd1 ... b97b79be62