/// Attach this script to a Directional Light in your Unity scene.
/// It will set the light's rotation to match the sun's position
/// based on the specified location, date, and time.
public class SunPosition : MonoBehaviour
[Header("Location (Degrees)")]
[Tooltip("Latitude in degrees (negative for Southern Hemisphere)")]
public float latitude = 0f;
[Tooltip("Longitude in degrees (negative for Western Hemisphere)")]
public float longitude = 0f;
[Header("Date and Time (UTC)")]
[Header("UTC Offset (Hours)")]
[Tooltip("Local time offset from UTC in hours (e.g., -5 for EST)")]
public float utcOffset = 0f;
// Create a DateTime in UTC, then apply the user's UTC offset to get local time
DateTime dateTime = new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc)
// Calculate the Sun's azimuth and altitude
double azimuth, altitude;
CalculateSunPosition(dateTime, latitude, longitude, out azimuth, out altitude);
// Convert azimuth/altitude to a rotation for a Unity directional light
// In typical astronomical convention:
// Altitude = 0 at horizon, +90 at zenith
// Azimuth = 0 at South (or North depending on definition), increasing towards West or East
// In Unity (assuming Y is up), we can do a simple mapping:
// Tilt "down" by altitude and rotate around Y by azimuth
transform.rotation = Quaternion.Euler((float)(-altitude), (float)azimuth, 0f);
/// Calculates the Sun's position in the sky for the given date/time and location.
/// Returns azimuth (degrees) and altitude (degrees).
/// <param name="dateTime">Local date/time (already adjusted for local offset if desired)</param>
/// <param name="lat">Latitude in degrees</param>
/// <param name="lon">Longitude in degrees</param>
/// <param name="azimuth">Output azimuth in degrees (0 = North, 90 = East, etc.)</param>
/// <param name="altitude">Output altitude in degrees (0 = horizon, +90 = zenith)</param>
public void CalculateSunPosition(
// 1. Convert the date/time to Julian day
double julianDay = GetJulianDay(dateTime);
// Julian centuries from J2000.0
double T = (julianDay - 2451545.0) / 36525.0;
// 2. Calculate the Sun's mean anomaly, ecliptic longitude, etc.
// (Based on NOAA's solar calculations or standard astronomical formulas)
double meanLong = 280.46646 + 36000.76983 * T; // Mean longitude of the Sun (degrees)
double meanAnom = 357.52911 + 35999.05029 * T; // Mean anomaly of the Sun (degrees)
double eccEarth = 0.016708634 - 0.000042037 * T; // Eccentricity of Earth's orbit
(1.914602 - 0.004817 * T - 0.000014 * T * T) * Math.Sin(Deg2Rad(meanAnom)) +
(0.019993 - 0.000101 * T) * Math.Sin(Deg2Rad(2 * meanAnom)) +
0.000289 * Math.Sin(Deg2Rad(3 * meanAnom));
double sunTrueLong = meanLong + sunEqCenter; // Sun's true longitude (degrees)
double sunAppLong = sunTrueLong - 0.00569 - 0.00478 * Math.Sin(Deg2Rad(125.04 - 1934.136 * T));
// 3. Calculate the Sun's declination
double meanObliqEcliptic = 23.439291 - 0.0130042 * T; // Mean obliquity of the ecliptic
double obliqCorr = meanObliqEcliptic + 0.00256 * Math.Cos(Deg2Rad(125.04 - 1934.136 * T));
// Sun's declination (delta)
double sinDec = Math.Sin(Deg2Rad(obliqCorr)) * Math.Sin(Deg2Rad(sunAppLong));
double declination = Rad2Deg(Math.Asin(sinDec));
// 4. Calculate the Sun's right ascension
double y = Math.Cos(Deg2Rad(obliqCorr)) * Math.Sin(Deg2Rad(sunAppLong));
double x = Math.Cos(Deg2Rad(sunAppLong));
double rightAscension = Rad2Deg(Math.Atan2(y, x));
// Ensure RA is in [0, 360)
if (rightAscension < 0) rightAscension += 360.0;
// 5. Calculate the Sun's local hour angle
// First, compute "Greenwich Mean Sidereal Time" (GMST) in degrees
double GMST = 280.46061837 + 360.98564736629 * (julianDay - 2451545.0)
+ 0.000387933 * T * T - (T * T * T / 38710000.0);
// Reduce GMST to [0, 360)
if (GMST < 0) GMST += 360.0;
// Local Sidereal Time (LST) in degrees
if (LST < 0) LST += 360.0;
// Hour angle (HA) in degrees
double HA = LST - rightAscension;
if (HA < -180) HA += 360.0;
if (HA > 180) HA -= 360.0;
// 6. Convert to altitude and azimuth
double latRad = Deg2Rad(lat);
double decRad = Deg2Rad(declination);
double HARad = Deg2Rad(HA);
double sinAlt = Math.Sin(latRad) * Math.Sin(decRad) +
Math.Cos(latRad) * Math.Cos(decRad) * Math.Cos(HARad);
double alt = Rad2Deg(Math.Asin(sinAlt));
double cosAz = (Math.Sin(decRad) - Math.Sin(latRad) * sinAlt) /
(Math.Cos(latRad) * Math.Cos(Deg2Rad(alt)));
double az = Math.Acos(cosAz);
double azDeg = Rad2Deg(az);
// We need to determine the correct quadrant for azimuth
// By convention: 0 = North, 90 = East, 180 = South, 270 = West
// We'll adjust for the hour angle
/// Compute the Julian Day for the given DateTime (UTC-based).
private double GetJulianDay(DateTime date)
// Convert to UTC if it's not already
DateTime dateUtc = date.ToUniversalTime();
int month = dateUtc.Month;
double day = dateUtc.Day +
dateUtc.Minute / 1440.0 +
dateUtc.Second / 86400.0;
double julianDay = Math.Floor(365.25 * (year + 4716))
+ Math.Floor(30.6001 * (month + 1))
private double Deg2Rad(double deg) => Math.PI * deg / 180.0;
private double Rad2Deg(double rad) => 180.0 * rad / Math.PI;