Day night cycle

From wowdev
Revision as of 16:10, 25 November 2021 by Zee (talk | contribs) (`)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Summary

IMPORTANT: Most of the information on this page was obtained by reversing a build of Mists of Pandaria (15662). It seems to hold true for Warlords of Draenor, as well, and may also apply to early versions of the Legion alpha and/or beta. It appears that later Legion builds changed shading logic a fair bit, at least as it relates to fog (adding a fog height plane and moving fog shading logic to the fragment shader, among other things).

CGameTime

CGameTime contains the logic and values necessary to determine things like: time of day progression, adjustments necessary to transform local time in to server time, etc.

CGameTime::GameTimeGetDayProgression

The GameTimeGetDayProgression function in CGameTime is frequently used in the various DayNight calculations. This function calculates the minutes since midnight in server time, and divides by the total number of minutes in a day.

The return value is a floating point that ranges from 0.0 to 1.0, measuring the distance the current server time is from midnight. 0.0 represents the time just after midnight, 0.5 represents midday, and 1.0 represents the time just before midnight.

DayNight

DayNight Tables

Several static tables are defined in the client's DayNight classes. These tables are used to modify colors and values involved in lighting. The tables are typically lerped by DayNight::InterpTable (see below).

Known Tables

The known DayNight tables as of MoP are:

  • DayNight::DNSky::s_darkTable
    • Determines a multiplier used to mute the m_highlightSky float present in LightParams.dbc
  • DayNight::DNSky::s_fadeTable
  • DayNight::DNStars::s_fadeTable
  • DayNight::DNClouds::s_bumpFadeTable

Table Structure

DayNight tables are structured like this:

struct {
  float distance_along_interpolation;
  float value_at_distance;
};

For example,

DayNight::DNStars::s_fadeTable = {
  { 0.1250, 1.0 },
  { 0.1875, 0.0 },
  { 0.9375, 0.0 },
  { 1.0000, 1.0 }
};

DayNight::s_sidnTable

Determines the value stored in DayNight::CDayNightObject::GetSelfIlluminatedScalar.

This value is used to control things like window glow at night (ie emissive lighting for SMOMaterial).

DayNight::s_sidnTable = {
  { 0.25,     1.0 },
  { 0.291667, 0.0 },
  { 0.854167, 0.0 },
  { 0.895833, 1.0 }
};

DayNight::CDayNightObjectInt::SetDirection(void)::phiTable

Used to calculate sun direction (for use in shaders).

DayNight::CDayNightObjectInt::SetDirection(void)::phiTable = {
  { 0.0,  2.2165682315826416 },  // pi * 0.70555556
  { 0.25, 1.919862151145935  },  // pi * 0.6111111
  { 0.5,  2.2165682315826416 },  // pi * 0.70555556
  { 0.75, 1.919862151145935  }   // pi * 0.6111111
};

DayNight::CDayNightObjectInt::SetDirection(void)::thetaTable

Used to calculate sun direction (for use in shaders).

DayNight::CDayNightObjectInt::SetDirection(void)::thetaTable = {
  { 0.0,  3.9269907474517822 },  // pi * 1.25
  { 0.25, 3.9269907474517822 },  // pi * 1.25
  { 0.5,  3.9269907474517822 },  // pi * 1.25
  { 0.75, 3.9269907474517822 }   // pi * 1.25
};

DayNight::InterpTable

Given a table and an interpolation factor, this function returns a value linearly interpolated out of the table. The function is commonly called in various DayNight value / color update functions.

Return values are always floating points.

InterpTable Logic

Logic WIP

DayNight::DarkenColor

Adjusts a given RGB value's brightness by a given floating point multiplier. Converts RGB to HSV, applies the multiplier to the V component, converts the adjusted HSV value back to RGB, and returns it.

Called from the various SetColors functions within DayNight.

DarkenColor Logic

Logic WIP

DayNight::CDayNightObjectInt::CalcFogRate

Given a fog start distance and a fog end distance, this function uses a simple formula to calculate a fog rate. The formula makes use of a fog far clip, defined as: fogFarClip = (cameraFarClip > 700.0f) ? cameraFarClip - 200.f : 500.0f.

Note that the fog start distance fed to this function is typically computed by this formula: fogStart = fogEnd * fogScalar.

Also note that (at least from MoP+) calculating fog start from the various light DBC values uses a somewhat different formula than: fogStart = fogEnd * fogScalar. See CalcColors to get a sense of what changes happen to fogScalar before fogStart is calculated.

CalcFogRate Logic

float DayNight::CDayNightObjectInt::CalcFogRate(DayNight::CDayNightObjectInt *this, float fogStart, float fogEnd) {

  float fogRange = fogEnd - fogStart;

  float fogFarClip;

  if (this.info.cameraFarClip < 700.f) {
    fogFarClip = this.info.cameraFarClip - 200.0f;
  } else {
    fogFarClip = 500.f;
  }

  float fogRate;

  if (fogRange <= fogFarClip) {
    fogRate = ((1.0f - (fogRange / fogFarClip)) * 5.5f) + 1.5f;
  } else {
    fogRate = 1.5f;
  }

  return fogRate;

}

DayNight::CDayNightObjectInt::CalcColors

This function handles lerping / copying area light colors and parameters. Area lights are the lights defined by Light.dbc (and associated DBCs).

CalcColors Logic

int DayNight::CDayNightObjectInt::CalcColors(DayNight::CDayNightObjectInt *this, int time, DayNight::CurrentLight *currentLight, LightParamsRec *lightParamsRec) {

  // Not shown: obtain the appropriate DBC entries for the start and end
  float tstart = time - t1;
  float tdiff = t2 - t1;
  float tdist = tstart / tdiff;

  // Interpolate color values 0-17 from LightData.dbc
  currentLight->m_directColor     = DayNight::LerpColor(tdist, start->m_directColor,     end->m_directColor);
  currentLight->m_ambientColor    = DayNight::LerpColor(tdist, start->m_ambientColor,    end->m_ambientColor);
  currentLight->m_skyTopColor     = DayNight::LerpColor(tdist, start->m_skyTopColor,     end->m_skyTopColor);
  currentLight->m_skyMiddleColor  = DayNight::LerpColor(tdist, start->m_skyMiddleColor,  end->m_skyMiddleColor);
  currentLight->m_skyBand1Color   = DayNight::LerpColor(tdist, start->m_skyBand1Color,   end->m_skyBand1Color);
  currentLight->m_skyBand2Color   = DayNight::LerpColor(tdist, start->m_skyBand2Color,   end->m_skyBand2Color);
  currentLight->m_skySmogColor    = DayNight::LerpColor(tdist, start->m_skySmogColor,    end->m_skySmogColor);
  currentLight->m_skyFogColor     = DayNight::LerpColor(tdist, start->m_skyFogColor,     end->m_skyFogColor);
  currentLight->m_shadowOpacity   = DayNight::LerpColor(tdist, start->m_shadowOpacity,   end->m_shadowOpacity);
  currentLight->m_sunColor        = DayNight::LerpColor(tdist, start->m_sunColor,        end->m_sunColor);
  currentLight->m_cloudSunColor   = DayNight::LerpColor(tdist, start->m_cloudSunColor,   end->m_cloudSunColor);
  currentLight->m_cloudLayer1AmbientColor = DayNight::LerpColor(tdist, start->m_cloudLayer1AmbientColor, end->m_cloudLayer1AmbientColor);
  currentLight->m_cloudEmissiveColor      = DayNight::LerpColor(tdist, start->m_cloudEmissiveColor,      end->m_cloudEmissiveColor);
  currentLight->m_cloudLayer2AmbientColor = DayNight::LerpColor(tdist, start->m_cloudLayer2AmbientColor, end->m_cloudLayer2AmbientColor);
  currentLight->m_oceanCloseColor = DayNight::LerpColor(tdist, start->m_oceanCloseColor, end->m_oceanCloseColor);
  currentLight->m_oceanFarColor   = DayNight::LerpColor(tdist, start->m_oceanFarColor,   end->m_oceanFarColor);
  currentLight->m_riverCloseColor = DayNight::LerpColor(tdist, start->m_riverCloseColor, end->m_riverCloseColor);
  currentLight->m_riverFarColor   = DayNight::LerpColor(tdist, start->m_riverFarColor,   end->m_riverFarColor);

  // Handle cloud density
  currentLight->m_cloudDensity = start->m_cloudDensity + ((end->m_cloudDensity - start->m_cloudDensity) * tdist);

  // Copy light params (these values are not lerped)
  currentLight->m_highlightSky      = lightParamsRec->m_highlightSky;
  currentLight->m_glow              = lightParamsRec->m_glow;
  currentLight->m_waterShallowAlpha = lightParamsRec->m_waterShallowAlpha;
  currentLight->m_waterDeepAlpha    = lightParamsRec->m_waterDeepAlpha;
  currentLight->m_oceanShallowAlpha = lightParamsRec->m_oceanShallowAlpha;
  currentLight->m_oceanDeepAlpha    = lightParamsRec->m_oceanDeepAlpha;

  // Light params flag 0x01 set to off (unk?)
  if (!(lightParamsRec->m_flags & 0x01))
    currentLight->float7C = 0.0;
  } else {
    currentLight->float7C = 1.0;
  }

  // Light params flag 0x02 (?) set to on (unk?)
  currentLight->dword80 = (lightParamsRec->m_flags >> 1) & 0x01;

  currentLight->dword90 = 1.0;
  currentLight->dwordA8 = 1.0;
  currentLight->m_lightSkyboxID = lightParamsRec->m_lightSkyboxID;
  currentLight->m_cloudTypeID = lightParamsRec->m_cloudTypeID;

  // Handle fog
  currentLight->m_fogEnd = (start->m_fogEnd + ((end->m_fogEnd - start->m_fogEnd) * tdist)) / 36.0;
  currentLight->m_fogEnd = currentLight->m_fogEnd < 10.0f ? 10.0f : currentLight->m_fogEnd;

  currentLight->m_fogScalar = start->m_fogScalar + ((end->m_fogScalar - start->m_fogScalar) * tdist);
  currentLight->m_fogScalar = clamp(currentLight->m_fogScalar, -1.0, 1.0);

  currentLight->m_fogRate = 1.0f;

  // Fog not enabled? (unk)
  if (this->m_info.unk_0 != 1) {
    return 1;
  }

  if (currentLight->m_fogEnd >= 27.777779f) {
    float fogRange = currentLight->m_fogEnd - (currentLight->m_fogScalar * currentLight->m_fogEnd);

    float fogFarClip;

    if (this->m_info.m_cameraFarClip < 700.0f) {
      fogFarClip = this->m_info.m_cameraFarClip - 200.0f;
    } else {
      fogFarClip = 500.0;
    }

    float fogRate;

    if (fogRange <= fogFarClip) {
      fogRate = ((1.0f - (fogRange / fogFarClip)) * 5.5f) + 1.5f;
    } else {
      fogRate = 1.5f;
    }

    currentLight->m_fogRate = fogRate;
    currentLight->m_fogEnd = this->m_info.m_cameraFarClip;
  }

  currentLight->m_fogScalar = max(0.0f, currentLight->m_fogScalar);

  return 1;
}

CShaderEffect

CShaderEffect::SetFogParams

The given fog params and fog color set up to be passed to the GPU when this function is called. Values typically or always originate from DayNight.

SetFogParams Logic

void CShaderEffect::SetFogParams(float fogStart, float fogEnd, float fogRate, const CArgb *fogColor, float a5) {
  if (CShaderEffect::s_enableShaders) {
    CShaderEffect::s_fogColorAlphaRef = fogColor / 255.0f;

    float fogRange = fogEnd - fogStart;

    // s_fogMul is set to 1.0 in CShaderEffect::InitShaderSystem, and does not appear to be modified elsewhere
    float fogMul = CShaderEffect::s_fogMul;

    CShaderEffect::s_fogParams.x = -(1.0f / fogRange) * fogMul;
    CShaderEffect::s_fogParams.y = (1.0f / fogRange) * fogEnd;
    CShaderEffect::s_fogParams.z = fogRate;

    // w is unknown; often forcibly set to 0.0; possibly something to do with intersecting m_liquidPlane in CM2SceneRender::SetupLighting
    CShaderEffect::s_fogParams.w = a5;
  }
}

Rendering

The data for the colors used to render the skybox come from LightData.db2 and are interpolated using the functions described above and current game time.

Choosing the correct row in the LightData.db2 is done by checking in which of the polygons generated from ZoneLight.db2/ZoneLightPoint.db2 the camera resides in.

The game also interpolates between 2 LightData.db2 entries when navigating between the zone light polygons.

Sky Cone

Method 1

The blizzard way of rendering the sky cone is to generate a mesh at runtime that looks like this https://i.imgur.com/jkDxHqD.png (thanks Deamon), setting the vertex colors based on the values from LightData.db2, and let the blending of the colors be done automatically by the shader.

Method 2

It can also be achieved procedurally by rendering a regular sphere/cube and using lerp(mix), smoothstep to manually interpolate between the input colors, using elevation as the blend factor.

Elevation can be calculated like so

 float3 fragDir = normalize(worldPos - worldCameraPos);
 float3 upDir = float3(0, 1, 0)
 float3 elevation = dot(fragDir, upDir); // -1 .. 1
 elevation = (1 + elevation) / 2;  // 0..1

Here's a vertical panoramic showing where each value in the LightData.db2 is used: https://i.imgur.com/3ArucTD.png

And a table of guessed interpolation stops (I literally just eyeballed till they looked like in game)

 SkyTop - 1 <> 0.714
 SkyMiddle - 0.714 <> 0.547
 SkyBand1 - 0.547 <> 0.513
 SkyBand2 - 0.513 <> 0.5
 SkySmog - 0.5 <> 0.486
 SkyFog - 0.486 <> 0