// Multiplies two Spectrums together, by multiplying the spectral powers at each wavelength sample. //static Spectrum Multiply(Spectrum a, Spectrum s) //{ // Spectrum result = new Spectrum(); // for (int i = 0; i < NumberOfSamples; i++) // { // result.Powers[i] = a.Powers[i] * s.Powers[i]; // } // return result; //} ///// <summary> ///// Multiplies the <see cref="Spectrum"/> by a scalar. ///// </summary> ///// <param name="s">The scalar.</param> //public void Multiply(float s) //{ // for (int i = 0; i < NumberOfSamples; i++) // Powers[i] = Powers[i] * s; //} /// <summary> /// Computes direct and indirect light information when this spectrum passes through the earth's /// atmosphere. /// </summary> /// <param name="zenithAngle"> /// The angle between the zenith and the direction of the light source emitting the simulated /// spectrum in radian. /// </param> /// <param name="turbidity"> /// The simulated atmospheric turbidity in the range [1.8, 20.0]. A turbidity of 2 describes a /// clear day whereas a turbidity of 20 represents thick haze. A commonly used value is 2.2. /// </param> /// <param name="altitude">The simulated altitude in meters above mean sea level.</param> /// <param name="directIrradiance"> /// Output: The spectral energy directly from the light source that survives transmission /// through the atmosphere. /// </param> /// <param name="scatteredIrradiance"> /// Output: The spectral energy scattered by the atmosphere, which makes up "skylight" (ambient /// light from the sky). /// </param> /// <remarks> /// <para> /// This method simulates the passage of this spectrum through earth's atmosphere, employing the /// National Renewable Energy Lab's "Bird model". Two new spectra, representing the direct and /// scattered irradiance resulting from passage through the atmosphere, are returned. /// </para> /// <para> /// If the light source is below the horizon, this model returns 0. Thus, it cannot be used to /// compute twilight. /// </para> /// </remarks> public void ApplyAtmosphericTransmittance(double zenithAngle, double turbidity, double altitude, Spectrum directIrradiance, Spectrum scatteredIrradiance) { // For more info, see the reference in the notes of this class. double[] Ao = { 0, 0, 0, 0, // 380 - 395 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.003, // 400 - 450 0.004, 0.006, 0.007, 0.009, 0.011, 0.014, 0.017, 0.021, 0.025, 0.03, // 455 - 500 0.035, 0.04, 0.044, 0.048, 0.055, 0.063, 0.071, 0.075, 0.08, 0.085, // 505 - 550 0.091, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.119, 0.12, // 555 - 600 0.12, 0.12, 0.10, 0.09, 0.09, 0.085, 0.08, 0.075, 0.07, 0.07, // 605 - 650 0.065, 0.06, 0.055, 0.05, 0.045, 0.04, 0.035, 0.028, 0.25, 0.023, // 655 - 700 0.02, 0.018, 0.016, 0.012, 0.012, 0.012, 0.012, 0.01, 0.01, 0.01, // 705 - 750 0.008, 0.007, 0.006, 0.005, 0.003, 0 }; double[] Au = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 380 - 500 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 505 - 550 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 550 - 600 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 605 - 650 0, 0, 0, 0, 0, 0, 0, 0.15, 0, 0, // 655 - 700 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 705 - 750 0, 4.0, 0, 0, 0, 0, // 755 - 780 }; double[] Aw = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 380 - 500 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 505 - 550 0, 0, 0, 0, 0, 0, 0, 0.075, 0, 0, // 550 -600 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 605 - 650 0, 0, 0, 0, 0, 0, 0, 0.016, 0.015, 0.014, // 655 - 700 0.013, 0.0125, 1.8, 2.3, 2.5, 2.3, 1.8, 0.061, 0.003, 0.0008, // 705 - 750 0.0001, 0.00001, 0.00001, 0.0001, 0.0003, 0.0006, // 755 - 780 }; double beta = 0.04608 * turbidity - 0.04586; double cosZenith = Math.Cos(zenithAngle); double zenithDeg = MathHelper.ToDegrees(zenithAngle); const double modelLimit = 90.0; //93.885 if (zenithDeg < modelLimit) { // NREL air mass model //double m = 1.0 / (cosZenith + 0.15 * pow(93.885 - zenithDeg, -1.253)); // International Comet Quarterly air mass model //double m = 1.0 / (cosZenith + 0.025 * exp(-11 * cosZenith)); // SPECTRL2 air mass model (also adopted by CIE) double m = 1.0 / (cosZenith + 0.50572 * Math.Pow(93.885 - zenithDeg, -1.6364)); // Account for high altitude. As you lose atmosphere, less scattering occurs. const double H = 8435.0; // pressure scale height double isothermalEffect = Math.Exp(-(altitude / H)); m *= isothermalEffect; // ozone mass const double O3 = 0.35; // ozone amount, atm-cm double Mo = 1.003454 / Math.Sqrt(cosZenith * cosZenith + 0.006908); const double W = 2.5; // precipitable water vapor (cm) const double omega = 0.945; // single scattering albedo, 0.4 microns const double omegap = 0.095; // Wavelength variation factor const double asym = 0.65; // aerosol asymmetry factor double alg = Math.Log(1.0 - asym); double afs = alg * (1.459 + alg * (0.1595 + alg * 0.4129)); double bfs = alg * (0.0783 + alg * (-0.3824 - alg * 0.5874)); double fsp = 1.0 - 0.5 * Math.Exp((afs + bfs / 1.8) / 1.8); double fs = 1.0 - 0.5 * Math.Exp((afs + bfs * cosZenith) * cosZenith); for (int i = 0; i < NumberOfSamples; i++) { double um = 0.380 + (i * 0.005); // Rayleigh scattering double Tr = Math.Exp(-m / (Math.Pow(um, 4.0) * (115.6406 - (1.335 / (um * um))))); // Aerosols double a = um < 0.5 ? 1.0274 : 1.2060; double c1 = beta * Math.Pow(2.0 * um, -a); double Ta = Math.Exp(-c1 * m); // Water vapor double aWM = Aw[i] * W * m; double Tw = Math.Exp(-0.2385 * aWM / Math.Pow(1.0 + 20.07 * aWM, 0.45)); // Ozone double To = Math.Exp(-Ao[i] * O3 * Mo); // Mixed gas is only important in infrared double Tm = Math.Exp((-1.41 * Au[i] * m) / Math.Pow(1.0 + 118.3 * Au[i] * m, 0.45)); // Aerosol scattering double logUmOver4 = Math.Log(um / 0.4); double omegl = omega * Math.Exp(-omegap * logUmOver4 * logUmOver4); double Tas = Math.Exp(-omegl * c1 * m); // Aerosol absorptance double Taa = Math.Exp((omegl - 1.0) * c1 * m); // Primed Rayleigh scattering (m = 1.8) double Trp = Math.Exp(-1.8 / (um * um * um * um) * (115.6406 - 1.3366 / (um * um))); // Primed water vapor scattering double Twp = Math.Exp(-0.4293 * Aw[i] * W / Math.Pow((1.0 + 36.126 * Aw[i] * W), 0.45)); // Mixed gas double Tup = Math.Exp(-2.538 * Au[i] / Math.Pow((1.0 + 212.94 * Au[i]), 0.45)); // Primed aerosol scattering double Tasp = Math.Exp(-omegl * c1 * 1.8); // Primed aerosol absorptance double Taap = Math.Exp((omegl - 1.0) * c1 * 1.8); // Direct energy double xmit = Tr * Ta * Tw * To * Tm; directIrradiance.Powers[i] = (float)(Powers[i] * xmit); // diffuse energy double c2 = To * Tw * cosZenith * Taa; double c4 = 1.0; if (um <= 0.45) { c4 = Math.Pow((um + 0.55), 1.8); } double rhoa = Tup * Twp * Taap * (0.5 * (1.0 - Trp) + (1.0 - fsp) * Trp * (1.0 - Tasp)); const double rho = 0.3; // ground albedo. Set less for ocean, more for snow. double dray = c2 * (1.0 - Math.Pow(Tr, 0.95)) / 2.0; double daer = c2 * Math.Pow(Tr, 1.5) * (1.0 - Tas) * fs; double drgd = (directIrradiance.Powers[i] * cosZenith + dray + daer) * rho * rhoa / (1.0 - rho * rhoa); scatteredIrradiance.Powers[i] = (float)(Powers[i] * (dray + daer + drgd) * c4); if (scatteredIrradiance.Powers[i] < 0) scatteredIrradiance.Powers[i] = 0; } } else { for (int i = 0; i < NumberOfSamples; i++) { directIrradiance.Powers[i] = 0; scatteredIrradiance.Powers[i] = 0; } } }
// Multiplies two Spectrums together, by multiplying the spectral powers at each wavelength sample. //static Spectrum Multiply(Spectrum a, Spectrum s) //{ // Spectrum result = new Spectrum(); // for (int i = 0; i < NumberOfSamples; i++) // { // result.Powers[i] = a.Powers[i] * s.Powers[i]; // } // return result; //} ///// <summary> ///// Multiplies the <see cref="Spectrum"/> by a scalar. ///// </summary> ///// <param name="s">The scalar.</param> //public void Multiply(float s) //{ // for (int i = 0; i < NumberOfSamples; i++) // Powers[i] = Powers[i] * s; //} /// <summary> /// Computes direct and indirect light information when this spectrum passes through the earth's /// atmosphere. /// </summary> /// <param name="zenithAngle"> /// The angle between the zenith and the direction of the light source emitting the simulated /// spectrum in radian. /// </param> /// <param name="turbidity"> /// The simulated atmospheric turbidity in the range [1.8, 20.0]. A turbidity of 2 describes a /// clear day whereas a turbidity of 20 represents thick haze. A commonly used value is 2.2. /// </param> /// <param name="altitude">The simulated altitude in meters above mean sea level.</param> /// <param name="directIrradiance"> /// Output: The spectral energy directly from the light source that survives transmission /// through the atmosphere. /// </param> /// <param name="scatteredIrradiance"> /// Output: The spectral energy scattered by the atmosphere, which makes up "skylight" (ambient /// light from the sky). /// </param> /// <remarks> /// <para> /// This method simulates the passage of this spectrum through earth's atmosphere, employing the /// National Renewable Energy Lab's "Bird model". Two new spectra, representing the direct and /// scattered irradiance resulting from passage through the atmosphere, are returned. /// </para> /// <para> /// If the light source is below the horizon, this model returns 0. Thus, it cannot be used to /// compute twilight. /// </para> /// </remarks> public void ApplyAtmosphericTransmittance(double zenithAngle, double turbidity, double altitude, Spectrum directIrradiance, Spectrum scatteredIrradiance) { // For more info, see the reference in the notes of this class. double[] Ao = { 0, 0, 0, 0, // 380 - 395 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.003, // 400 - 450 0.004, 0.006, 0.007, 0.009, 0.011, 0.014, 0.017, 0.021, 0.025,0.03, // 455 - 500 0.035, 0.04, 0.044, 0.048, 0.055, 0.063, 0.071, 0.075, 0.08,0.085, // 505 - 550 0.091, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.12, 0.119,0.12, // 555 - 600 0.12, 0.12, 0.10, 0.09, 0.09, 0.085, 0.08, 0.075, 0.07,0.07, // 605 - 650 0.065, 0.06, 0.055, 0.05, 0.045, 0.04, 0.035, 0.028, 0.25,0.023, // 655 - 700 0.02, 0.018, 0.016, 0.012, 0.012, 0.012, 0.012, 0.01, 0.01,0.01, // 705 - 750 0.008, 0.007, 0.006, 0.005, 0.003, 0 }; double[] Au = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 380 - 500 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 505 - 550 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 550 - 600 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 605 - 650 0, 0, 0, 0, 0, 0, 0, 0.15, 0, 0, // 655 - 700 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 705 - 750 0, 4.0, 0, 0, 0, 0, // 755 - 780 }; double[] Aw = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 380 - 500 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 505 - 550 0, 0, 0, 0, 0, 0, 0, 0.075, 0, 0, // 550 -600 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 605 - 650 0, 0, 0, 0, 0, 0, 0, 0.016, 0.015,0.014, // 655 - 700 0.013, 0.0125, 1.8, 2.3, 2.5, 2.3, 1.8, 0.061, 0.003,0.0008, // 705 - 750 0.0001, 0.00001, 0.00001, 0.0001, 0.0003,0.0006, // 755 - 780 }; double beta = 0.04608 * turbidity - 0.04586; double cosZenith = Math.Cos(zenithAngle); double zenithDeg = MathHelper.ToDegrees(zenithAngle); const double modelLimit = 90.0; //93.885 if (zenithDeg < modelLimit) { // NREL air mass model //double m = 1.0 / (cosZenith + 0.15 * pow(93.885 - zenithDeg, -1.253)); // International Comet Quarterly air mass model //double m = 1.0 / (cosZenith + 0.025 * exp(-11 * cosZenith)); // SPECTRL2 air mass model (also adopted by CIE) double m = 1.0 / (cosZenith + 0.50572 * Math.Pow(93.885 - zenithDeg, -1.6364)); // Account for high altitude. As you lose atmosphere, less scattering occurs. const double H = 8435.0; // pressure scale height double isothermalEffect = Math.Exp(-(altitude / H)); m *= isothermalEffect; // ozone mass const double O3 = 0.35; // ozone amount, atm-cm double Mo = 1.003454 / Math.Sqrt(cosZenith * cosZenith + 0.006908); const double W = 2.5; // precipitable water vapor (cm) const double omega = 0.945; // single scattering albedo, 0.4 microns const double omegap = 0.095; // Wavelength variation factor const double asym = 0.65; // aerosol asymmetry factor double alg = Math.Log(1.0 - asym); double afs = alg * (1.459 + alg * (0.1595 + alg * 0.4129)); double bfs = alg * (0.0783 + alg * (-0.3824 - alg * 0.5874)); double fsp = 1.0 - 0.5 * Math.Exp((afs + bfs / 1.8) / 1.8); double fs = 1.0 - 0.5 * Math.Exp((afs + bfs * cosZenith) * cosZenith); for (int i = 0; i < NumberOfSamples; i++) { double um = 0.380 + (i * 0.005); // Rayleigh scattering double Tr = Math.Exp(-m / (Math.Pow(um, 4.0) * (115.6406 - (1.335 / (um * um))))); // Aerosols double a = um < 0.5 ? 1.0274 : 1.2060; double c1 = beta * Math.Pow(2.0 * um, -a); double Ta = Math.Exp(-c1 * m); // Water vapor double aWM = Aw[i] * W * m; double Tw = Math.Exp(-0.2385 * aWM / Math.Pow(1.0 + 20.07 * aWM, 0.45)); // Ozone double To = Math.Exp(-Ao[i] * O3 * Mo); // Mixed gas is only important in infrared double Tm = Math.Exp((-1.41 * Au[i] * m) / Math.Pow(1.0 + 118.3 * Au[i] * m, 0.45)); // Aerosol scattering double logUmOver4 = Math.Log(um / 0.4); double omegl = omega * Math.Exp(-omegap * logUmOver4 * logUmOver4); double Tas = Math.Exp(-omegl * c1 * m); // Aerosol absorptance double Taa = Math.Exp((omegl - 1.0) * c1 * m); // Primed Rayleigh scattering (m = 1.8) double Trp = Math.Exp(-1.8 / (um * um * um * um) * (115.6406 - 1.3366 / (um * um))); // Primed water vapor scattering double Twp = Math.Exp(-0.4293 * Aw[i] * W / Math.Pow((1.0 + 36.126 * Aw[i] * W), 0.45)); // Mixed gas double Tup = Math.Exp(-2.538 * Au[i] / Math.Pow((1.0 + 212.94 * Au[i]), 0.45)); // Primed aerosol scattering double Tasp = Math.Exp(-omegl * c1 * 1.8); // Primed aerosol absorptance double Taap = Math.Exp((omegl - 1.0) * c1 * 1.8); // Direct energy double xmit = Tr * Ta * Tw * To * Tm; directIrradiance.Powers[i] = (float)(Powers[i] * xmit); // diffuse energy double c2 = To * Tw * cosZenith * Taa; double c4 = 1.0; if (um <= 0.45) { c4 = Math.Pow((um + 0.55), 1.8); } double rhoa = Tup * Twp * Taap * (0.5 * (1.0 - Trp) + (1.0 - fsp) * Trp * (1.0 - Tasp)); const double rho = 0.3; // ground albedo. Set less for ocean, more for snow. double dray = c2 * (1.0 - Math.Pow(Tr, 0.95)) / 2.0; double daer = c2 * Math.Pow(Tr, 1.5) * (1.0 - Tas) * fs; double drgd = (directIrradiance.Powers[i] * cosZenith + dray + daer) * rho * rhoa / (1.0 - rho * rhoa); scatteredIrradiance.Powers[i] = (float)(Powers[i] * (dray + daer + drgd) * c4); if (scatteredIrradiance.Powers[i] < 0) { scatteredIrradiance.Powers[i] = 0; } } } else { for (int i = 0; i < NumberOfSamples; i++) { directIrradiance.Powers[i] = 0; scatteredIrradiance.Powers[i] = 0; } } }