Files
2024-11-20 15:21:28 +01:00

513 lines
20 KiB
C#

using UnityEngine;
using UnityEngine.Profiling;
using System;
namespace OccaSoftware.Altos
{
public static class StaticTimeOfDayManager
{
public static Material activeSkyboxMaterial;
public static Action<float> UpdateSunIntensityEvent;
public static Action<float> UpdateTimeOfDayEvent;
public static void OnSunLightIntensityChangeEvent(float sunBrightnessRelative)
{
if(UpdateSunIntensityEvent != null)
UpdateSunIntensityEvent(sunBrightnessRelative);
}
public static void OnTimeOfDayChange(float newTimeOfDay)
{
if (UpdateTimeOfDayEvent != null)
UpdateTimeOfDayEvent(newTimeOfDay);
}
public static float SetLightIntensity(Light light, float peakIntensity, float horizonAngle)
{
float lightAngle = light.transform.eulerAngles.x;
lightAngle = lightAngle > 180f ? lightAngle - 360f : lightAngle;
if (lightAngle >= horizonAngle)
{
return peakIntensity;
}
if (lightAngle < -horizonAngle)
{
return 0f;
}
if (lightAngle >= -horizonAngle && lightAngle < horizonAngle)
{
float t = Helpers.Remap(lightAngle, -horizonAngle, horizonAngle, 0f, 1f);
return Mathf.Lerp(0f, peakIntensity, t);
}
return light.intensity;
}
}
[ExecuteAlways]
public class TimeOfDayManager : MonoBehaviour
{
public SkyboxDefinitionScriptableObject skyboxDefinition = null;
private Material skyboxMaterial = null;
public Light sun = null;
private int periodIndex = 0;
private int nextPeriodIndex = 0;
private float timeHours = 0f;
private Color fogColor = Color.white;
private Color zenithAmbient = Color.white;
private bool setupCorrect = false;
private float cachedTimeOfDay = 0f;
public float SunLightIntensityRelative
{
get
{
if (sun == null || skyboxDefinition == null)
return 0;
return Helpers.Remap(sun.intensity, 0f, skyboxDefinition.sunLightIntensity, 0f, 1f);
}
}
public void Awake()
{
InitializeSkybox();
}
public void InitializeSkybox()
{
//Debug.Log("Initializing Skybox...");
// Error Handling
setupCorrect = ValidateSetup();
}
private bool ValidateSetup()
{
#if !UNITY_2021_3_OR_NEWER
Debug.LogError("This version of Altos is designed for Unity 2021.3 or newer. Please upgrade your Unity Editor to ensure that Altos is compatible with your Unity version.");
#endif
if (skyboxDefinition == null)
{
Debug.Log("Must set Skybox Definition");
return false;
}
if (skyboxMaterial == null)
{
//Debug.Log("Setting up Skybox Material...");
skyboxMaterial = GetSkyboxMaterial();
StaticTimeOfDayManager.activeSkyboxMaterial = skyboxMaterial;
if (skyboxMaterial == null)
{
Debug.Log("Failed to set up Skybox Material. Exiting.");
return false;
}
}
if (RenderSettings.skybox != skyboxMaterial)
{
//Debug.Log("Setting up Skybox in Lighting settings...");
SetLightingEnvironment(skyboxMaterial);
if (RenderSettings.skybox != skyboxMaterial)
{
Debug.Log("Failed to set up Skybox in Lighting settings. Exiting.");
return false;
}
}
if (sun == null)
{
Debug.Log("Sun Lamp is not set. Exiting.");
return false;
}
RenderSettings.sun = sun;
return true;
}
private void OnDestroy()
{
if (RenderSettings.skybox = skyboxMaterial)
RenderSettings.skybox = null;
}
void Start()
{
if (setupCorrect)
{
//Debug.Log("Skybox Initialized Successfully.");
InitialSetup();
}
}
public void InitialSetup()
{
if (setupCorrect)
{
skyboxDefinition.activeTimeOfDay = skyboxDefinition.timeOfDay;
cachedTimeOfDay = skyboxDefinition.activeTimeOfDay;
HandleSunLight();
SetPeriod();
SetSkyColors();
UpdateActiveShaderProperties();
UpdateStaticShaderProperties();
}
}
private Material GetSkyboxMaterial()
{
return new Material(Shader.Find("Shader Graphs/Skybox Shader_OS"));
}
private void SetLightingEnvironment(Material skybox)
{
RenderSettings.skybox = skybox;
}
public void SetSkyboxDefinition(SkyboxDefinitionScriptableObject skyboxDefinition)
{
this.skyboxDefinition = skyboxDefinition;
}
public void SetSunLamp(Light sun)
{
this.sun = sun;
}
public void UpdateStaticShaderProperties()
{
skyboxMaterial.SetFloat(ShaderParams.sunSize, skyboxDefinition.sunSize);
skyboxMaterial.SetColor(ShaderParams.sunColor, skyboxDefinition.sunColor);
skyboxMaterial.SetFloat(ShaderParams.sunInfluenceSize, skyboxDefinition.sunInfluenceSize);
skyboxMaterial.SetFloat(ShaderParams.sunInfluenceIntensity, skyboxDefinition.sunInfluenceIntensity);
skyboxMaterial.SetTexture(ShaderParams.cloudTex1, skyboxDefinition.cloudTexture1);
skyboxMaterial.SetTexture(ShaderParams.cloudTex2, skyboxDefinition.cloudTexture2);
skyboxMaterial.SetVector(ShaderParams.cloudTex1ZenithTiling, skyboxDefinition.texture1ZenithTiling);
skyboxMaterial.SetVector(ShaderParams.cloudTex2ZenithTiling, skyboxDefinition.texture2ZenithTiling);
skyboxMaterial.SetVector(ShaderParams.cloudTex1HorizonTiling, skyboxDefinition.texture1HorizonTilingSterile);
skyboxMaterial.SetVector(ShaderParams.cloudTex2HorizonTiling, skyboxDefinition.texture2HorizonTilingSterile);
skyboxMaterial.SetFloat(ShaderParams.cloudSharpness, skyboxDefinition.cloudSharpness);
skyboxMaterial.SetColor(ShaderParams.cloudColor, skyboxDefinition.cloudColor);
skyboxMaterial.SetFloat(ShaderParams.cloudOpacity, skyboxDefinition.cloudOpacity);
skyboxMaterial.SetColor(ShaderParams.cloudShadingColor, skyboxDefinition.cloudShadingColor);
skyboxMaterial.SetFloat(ShaderParams.cloudShadingThreshold, skyboxDefinition.cloudShadingThreshold);
skyboxMaterial.SetFloat(ShaderParams.cloudShadingSharpness, skyboxDefinition.cloudShadingSharpness);
skyboxMaterial.SetFloat(ShaderParams.cloudShadingStrength, skyboxDefinition.cloudShadingStrength);
skyboxMaterial.SetFloat(ShaderParams.cloudSpeed, skyboxDefinition.cloudSpeed);
skyboxMaterial.SetFloat(ShaderParams.cloudThreshold, 1f - skyboxDefinition.cloudiness);
skyboxMaterial.SetFloat(ShaderParams.alternateUVAtZenith, skyboxDefinition.alternateUVAtZenith);
skyboxMaterial.SetFloat(ShaderParams.sunInfluence, skyboxDefinition.sunCloudInfluence);
skyboxMaterial.SetFloat(ShaderParams.skyColorInfluence, skyboxDefinition.skyColorCloudInfluence);
skyboxMaterial.SetFloat(ShaderParams.blueNoiseStrength, skyboxDefinition.ditherStrength);
skyboxMaterial.SetFloat(ShaderParams.fogHeightPower, skyboxDefinition.fogHeightPower);
Shader.SetGlobalFloat(ShaderParams.depthFogStart, skyboxDefinition.fogStart);
Shader.SetGlobalFloat(ShaderParams.depthFogEnd, skyboxDefinition.fogEnd);
Shader.SetGlobalFloat(ShaderParams.depthFogDithering, skyboxDefinition.fogDithering);
}
private static class ShaderParams
{
public static int sunDirection = Shader.PropertyToID("_SUN_DIRECTION");
public static int horizonColor = Shader.PropertyToID("_HORIZONCOLOR");
public static int zenithColor = Shader.PropertyToID("_ZENITHCOLOR");
public static int groundColor = Shader.PropertyToID("_GROUNDCOLOR");
public static int cloudThreshold = Shader.PropertyToID("_CLOUDTHRESHOLD");
public static int cloudSpeed = Shader.PropertyToID("_CLOUDTEXTURESPEED");
public static int nightLuminance = Shader.PropertyToID("_CLOUD_NIGHT_LUMINANCE_MULTIPLIER");
public static int fogColor = Shader.PropertyToID("_FOG_COLOR");
public static int volumetricsFogColor = Shader.PropertyToID("_VOLUMETRICS_FOG_COLOR");
public static int zenithAmbientColor = Shader.PropertyToID("_ZENITH_AMBIENT_COLOR");
public static int depthFogColor = Shader.PropertyToID("_DEPTH_FOG_COLOR");
public static int depthFogStart = Shader.PropertyToID("_DEPTH_FOG_START");
public static int depthFogEnd = Shader.PropertyToID("_DEPTH_FOG_END");
public static int depthFogDithering = Shader.PropertyToID("_DEPTH_FOG_DITHERING");
public static int sunSize = Shader.PropertyToID("_SUNSIZE");
public static int sunColor = Shader.PropertyToID("_SUNCOLOR");
public static int sunInfluenceSize = Shader.PropertyToID("_SUNINFLUENCESIZE");
public static int sunInfluenceIntensity = Shader.PropertyToID("_SUNINFLUENCEINTENSITY");
public static int sunIntensity = Shader.PropertyToID("_SUN_INTENSITY");
public static int cloudTex1 = Shader.PropertyToID("_CLOUDTEXTURE1");
public static int cloudTex2 = Shader.PropertyToID("_CLOUDTEXTURE2");
public static int cloudTex1ZenithTiling = Shader.PropertyToID("_TEXTURE1ZENITHTILING");
public static int cloudTex2ZenithTiling = Shader.PropertyToID("_TEXTURE2ZENITHTILING");
public static int cloudTex1HorizonTiling = Shader.PropertyToID("_TEXTURE1HORIZONTILING");
public static int cloudTex2HorizonTiling = Shader.PropertyToID("_TEXTURE2HORIZONTILING");
public static int cloudSharpness = Shader.PropertyToID("_CLOUDSHARPNESS");
public static int cloudColor = Shader.PropertyToID("_CLOUDCOLOR");
public static int cloudOpacity = Shader.PropertyToID("_CLOUDOPACITY");
public static int cloudShadingColor = Shader.PropertyToID("_CLOUDSHADINGCOLOR");
public static int cloudShadingThreshold = Shader.PropertyToID("_CLOUDSHADINGTHRESHOLD");
public static int cloudShadingSharpness = Shader.PropertyToID("_CLOUDSHADINGSHARPNESS");
public static int cloudShadingStrength = Shader.PropertyToID("_CLOUDSHADINGSTRENGTH");
public static int alternateUVAtZenith = Shader.PropertyToID("_ALTERNATEUVATZENITH");
public static int sunInfluence = Shader.PropertyToID("_SUNINFLUENCE");
public static int skyColorInfluence = Shader.PropertyToID("_SKYCOLORINFLUENCE");
public static int blueNoiseStrength = Shader.PropertyToID("_BLUENOISESTRENGTH");
public static int fogHeightPower = Shader.PropertyToID("_FOG_HEIGHT_POWER");
}
public void UpdateActiveShaderProperties()
{
skyboxMaterial.SetColor(ShaderParams.fogColor, fogColor);
Shader.SetGlobalColor(ShaderParams.volumetricsFogColor, fogColor);
Shader.SetGlobalColor(ShaderParams.zenithAmbientColor, zenithAmbient);
Shader.SetGlobalColor(ShaderParams.depthFogColor, fogColor);
if(sun != null)
Shader.SetGlobalVector(ShaderParams.sunDirection, sun.transform.forward);
}
// Update is called once per frame
public void Update()
{
Profiler.BeginSample("TimeOfDayManager: Update Execution");
if (!setupCorrect)
return;
if (skyboxDefinition == null)
return;
if (skyboxMaterial == null)
{
skyboxMaterial = GetSkyboxMaterial();
SetLightingEnvironment(skyboxMaterial);
}
if (Application.isPlaying)
{
SetTimeOfDay();
}
else
{
HandleSunLight();
SetPeriod();
SetSkyColors();
}
if(Mathf.Abs(skyboxDefinition.activeTimeOfDay - cachedTimeOfDay) > 0.01f)
{
cachedTimeOfDay = skyboxDefinition.activeTimeOfDay;
HandleSunLight();
SetPeriod();
SetSkyColors();
}
UpdateActiveShaderProperties();
UpdateStaticShaderProperties();
Profiler.EndSample();
}
private void SetTimeOfDay()
{
if (skyboxDefinition.activeTimeOfDay > 24f)
{
skyboxDefinition.activeTimeOfDay = skyboxDefinition.activeTimeOfDay - 24f;
}
if(skyboxDefinition.realSecondsToGameHours > 0f)
{
float t = Time.deltaTime * skyboxDefinition.realSecondsToGameHours; // One second in real life is one hour in-game. This can be modified with the realSecondsToGameHours variable (e.g., if realSecondsToGameHours is set to 2, then 1 second in real life corresponds to 2 hours in-game).
skyboxDefinition.activeTimeOfDay += t;
timeHours += t; // Tracking timehours separately for sampling perlin noise (we don't reset it at 24).
}
}
void HandleSunLight()
{
if (sun == null)
return;
SetSunLightDirection();
float oldIntensity = sun.intensity;
sun.intensity = StaticTimeOfDayManager.SetLightIntensity(sun, skyboxDefinition.sunLightIntensity, 20f);
if(sun.intensity != oldIntensity)
{
StaticTimeOfDayManager.OnSunLightIntensityChangeEvent(sun.intensity / skyboxDefinition.sunLightIntensity);
}
float cloudLuminance = Helpers.Remap(sun.intensity, 0f, skyboxDefinition.sunLightIntensity, skyboxDefinition.cloudNightLuminanceMultiplier, 1f);
skyboxMaterial.SetFloat(ShaderParams.nightLuminance, cloudLuminance);
skyboxMaterial.SetFloat(ShaderParams.sunIntensity, SunLightIntensityRelative);
}
// Basically, the light direction is derived from the current time of day such that the sun is perfectly horizontal at 6am and 6pm for a 12hr "day" cycle and 12hr "night" cycle.
// Not currently configurable.
private void SetSunLightDirection()
{
if (sun == null)
return;
float remappedTimeOfDay = skyboxDefinition.activeTimeOfDay / 24f;
float sunAngle = (remappedTimeOfDay * 360f) - 90f;
sun.transform.rotation = Quaternion.Euler(sunAngle, skyboxDefinition.sunBaseRotationY, 0f); // Sun rotation is based on time of day
}
// The SetSkyColors function depends on this to set the correct period.
// Normally, this is straightforward. Transitioning from one day to the next is the tricky bit.
private void SetPeriod()
{
if (Application.isPlaying)
{
HandlePeriod(skyboxDefinition.activeTimeOfDay);
}
else
{
HandlePeriod(skyboxDefinition.timeOfDay);
}
nextPeriodIndex = periodIndex + 1;
if (nextPeriodIndex >= skyboxDefinition.periodsOfDay.Count)
{
nextPeriodIndex = 0;
}
}
void HandlePeriod(float time)
{
if (time < skyboxDefinition.periodsOfDay[0].startTime)
{
periodIndex = skyboxDefinition.periodsOfDay.Count - 1;
}
else
{
for (int i = 0; i < skyboxDefinition.periodsOfDay.Count; i++)
{
if (time >= skyboxDefinition.periodsOfDay[i].startTime)
{
periodIndex = i;
}
}
}
}
// Sets the Horizon and Zenith colors based on the current time of day.
// As we approach the next time of day, we transition smoothly to the Horizon and Zenith colors for that time of day.
private void SetSkyColors()
{
if (Application.isPlaying)
{
HandleSkyColor(skyboxDefinition.activeTimeOfDay);
}
else
{
HandleSkyColor(skyboxDefinition.timeOfDay);
}
}
private void HandleSkyColor(float time)
{
if (time >= skyboxDefinition.periodsOfDay[nextPeriodIndex].startTime - skyboxDefinition.hoursForTransition && time < skyboxDefinition.periodsOfDay[nextPeriodIndex].startTime)
{
float t = Helpers.Remap(time, skyboxDefinition.periodsOfDay[nextPeriodIndex].startTime - skyboxDefinition.hoursForTransition, skyboxDefinition.periodsOfDay[nextPeriodIndex].startTime, 0, 1);
Color tempHorizon = Color.Lerp(skyboxDefinition.periodsOfDay[periodIndex].horizonColor, skyboxDefinition.periodsOfDay[nextPeriodIndex].horizonColor, t);
skyboxMaterial.SetColor(ShaderParams.horizonColor, tempHorizon);
Color tempZenith = Color.Lerp(skyboxDefinition.periodsOfDay[periodIndex].zenithColor, skyboxDefinition.periodsOfDay[nextPeriodIndex].zenithColor, t);
skyboxMaterial.SetColor(ShaderParams.zenithColor, tempZenith);
Color tempGround = Color.Lerp(skyboxDefinition.periodsOfDay[periodIndex].groundColor, skyboxDefinition.periodsOfDay[nextPeriodIndex].groundColor, t);
skyboxMaterial.SetColor(ShaderParams.groundColor, tempGround);
fogColor = tempHorizon;
zenithAmbient = Color.Lerp(tempHorizon, tempZenith, 0.5f);
}
else
{
skyboxMaterial.SetColor(ShaderParams.horizonColor, skyboxDefinition.periodsOfDay[periodIndex].horizonColor);
skyboxMaterial.SetColor(ShaderParams.zenithColor, skyboxDefinition.periodsOfDay[periodIndex].zenithColor);
skyboxMaterial.SetColor(ShaderParams.groundColor, skyboxDefinition.periodsOfDay[periodIndex].groundColor);
fogColor = skyboxDefinition.periodsOfDay[periodIndex].horizonColor;
zenithAmbient = Color.Lerp(skyboxDefinition.periodsOfDay[periodIndex].horizonColor, skyboxDefinition.periodsOfDay[periodIndex].zenithColor, 0.5f);
}
fogColor = Color.Lerp(skyboxDefinition.baseFogColor, fogColor, skyboxDefinition.fogColorBlend);
}
}
// Internal class used to include all PeriodOfDay information.
// I plan to replace this later with ScriptableObjects for ease of use.
[System.Serializable]
public class PeriodOfDay
{
[SerializeField]
[Tooltip("(Optional) Descriptive Name")]
public string description;
[SerializeField, Range(0f, 24f)]
[Tooltip("Set the Start Time for this Period of Day")]
public float startTime;
[SerializeField, ColorUsage(false, true)]
[Tooltip("Set the Horizon Color for this Period of Day")]
public Color horizonColor;
[SerializeField, ColorUsage(false, true)]
[Tooltip("Set the Zenith Color for this Period of Day")]
public Color zenithColor;
[SerializeField, ColorUsage(false, true)]
[Tooltip("Set the Ground Color for this Period of Day")]
public Color groundColor;
public PeriodOfDay(string desc, float start, Color horizon, Color zenith, Color ground)
{
description = desc;
startTime = start;
horizonColor = horizon;
zenithColor = zenith;
groundColor = ground;
}
}
}