Slight Necro here, but as the docs for the Volumetric Fog is missing about everything in terms of changing it through scripts, I though it would be wise to give a quick tip about how can you gradually bring in volumetric fog.
Personally, I prefer to do it through a coroutine. It’s clean and simple to manage while you also avoid overheads during regular framerate when it’s not used. (In simple terms, no need to use any Update() or LateUpdate()).
The following code is what I use for my Volumetric Fog in HDRP:
using System.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering.HDPipeline;
public class WeatherManager : MonoBehaviour
{
public static WeatherManager instance;
private bool isFogActive = true;
private float foglevel = 0f;
private Volume SceneEffectsSettings;
private VolumetricFog SceneFog;
public float HeaviestFogLevel = 3f;
public float LightestFogLevel = 150f;
private IEnumerator FogEvent;
private IEnumerator FogColorEvent;
private float FogValue_Temp = 0f;
public Color[] FogColors;
private int FogColor_Count;
void Awake()
{
if (WeatherManager.instance != null && WeatherManager.instance != this)
{
Destroy(this.gameObject);
}
else
{
WeatherManager.instance = this;
if (!SceneEffectsSettings)
{
SceneEffectsSettings = GameObject.FindGameObjectWithTag("RenderSettings").GetComponent<Volume>();
}
if (SceneEffectsSettings)
{
GetSceneSettingsList();
}
}
}
private void GetSceneSettingsList()
{
SceneEffectsSettings.profile.TryGet<VolumetricFog>(out SceneFog);
FogColor_Count = FogColors.Length;
}
public void ChangeFogSettings(bool ActiveFog, float FogLevel, float Speed)
{
FogLevel = Mathf.Clamp(FogLevel, 0f, 1f);
if (SceneFog != null)
{
if (FogEvent != null)
{
StopCoroutine(FogEvent);
FogEvent = null;
}
FogValue_Temp = Mathf.Lerp(LightestFogLevel, HeaviestFogLevel, FogLevel);
FogEvent = AdjustFog_Event(ActiveFog, FogValue_Temp, 1f/Speed);
StartCoroutine(FogEvent);
}
}
public void ChangeFogColor(int ColorID, float ChangeSpeed)
{
ColorID = Mathf.Clamp(ColorID, 0, FogColor_Count);
if (SceneFog != null)
{
if (FogColorEvent != null)
{
StopCoroutine(FogEvent);
FogColorEvent = null;
}
FogColorEvent = FogColor_Event(ColorID, 1f/ChangeSpeed);
StartCoroutine(FogColorEvent);
}
}
public bool FogActive()
{
return isFogActive;
}
IEnumerator AdjustFog_Event(bool FogStatus, float FogLevel, float Speed)
{
if (FogStatus != isFogActive)
{
if (isFogActive)
{
//Transition toward no fog at all.
while (SceneFog.meanFreePath.value != LightestFogLevel)
{
SceneFog.meanFreePath.value = Mathf.MoveTowards(SceneFog.meanFreePath.value, LightestFogLevel, Time.deltaTime * Speed);
yield return null;
}
SceneFog.active = isFogActive = false;
}
else
{
//Transition toward wanted fog level
SceneFog.meanFreePath.value = LightestFogLevel;
SceneFog.active = isFogActive = true;
while (SceneFog.meanFreePath.value != FogLevel)
{
SceneFog.density.value = Mathf.MoveTowards(SceneFog.meanFreePath.value, FogLevel, Time.deltaTime * Speed);
yield return null;
}
}
}
else
{
//Only change the fog amount, but not the state.
while (SceneFog.meanFreePath.value != FogLevel)
{
SceneFog.meanFreePath.value = Mathf.MoveTowards(SceneFog.meanFreePath.value, FogLevel, Time.deltaTime * Speed);
yield return null;
}
}
}
IEnumerator FogColor_Event(int ColorID, float Speed)
{
while (SceneFog.albedo.value != FogColors[ColorID])
{
SceneFog.albedo.value = Color.Lerp(SceneFog.albedo.value, FogColors[ColorID], Time.deltaTime * Speed);
yield return null;
}
}
}
The only thing I got to do, in the scene, with that code is making sure the Game Object that has the Volumetric fog has the RenderSettings tag. (You can change to it whatever you want). I named the tag that way because, in the HDRP, the game object with most of the visual effects like shadows, procedural sky, AO & Fog is called Rendering Settings.
Heaviest Fog Level is the most dense fog level you want to reach.
Lightest Fog Level is the least dense fog level you want to reach.
It’s a bit ambiguous because it’s actually more like an Units distance than density.
If you would like to have a more transparent “already present” fog effect, then you got to works on the density.value of the volumetric fog and not the meanFreePath.value (which is the Base Fog Distance in the Inspector of the Volumetric Fog). As a matter of fact, the Density is not exposed so that’s one “hidden” gem of the system. You could say that the Density is like the Fog opacity itself while the meanFreePath is the distance where it starts getting dense.
By default, I set the heaviest to 3f and the lightest to 150f as those seems to be the best default margins.
If your game has some sort of scoping/zooming action, you might want to push the lightest a bit further or adjust it through scripts which is why I set them as public for quick access through the instance reference. I don’t own a 4K screen so I can’t tell what value it should also be with 4K resolution.
With that script, you can change the fog settings 2 ways : Distances or Color : as long as the script in the scene, from any other scripts.
WeatherManager.instance.ChangeFogSettings(bool ActiveFog, float FogLevel, float Speed);
The ActiveFog boolean in this function is to be used if you want, in the end, to turn off the fog or turn it on.
It’s a per-situation kind of option such as if you want the fog to slowly disapears as the player is transitioning from outside with fog to an inside without any fog (or outside view) like in a cave or a bunker. You should not turn off the fog while the player have a direct view of the outside’s horizon as it’s clearly displays that the fog is turned off.
It’s still a good option to have even if you keep it as true 99.99% of the time. While this also allow you to turn off the fog instantly, it also allow you a smooth transition between no fog and with fog. For example, let’s say that you got some huge door/gate opening and you want the fog to appears as if it’s “entering” from that door during the transition. Initially, there’s no fog. set this to true and with a speed fast (or slow) enough to fit with the event where the door open and you got yourself a “invasive” volumetric fog moment.
The FogLevel is a float that should range between 0f and 1.0f.
Instead of making you work at remembering what min-max range you want it, it’s simplier to set the rule to between 0 and 1.0 that automatically translate into 0 being “least fog” and 1.0 being “max fog” levels.
The Speed is the typical “how fast you want it to change”. I set it in a reverse way so that while not exactly in seconds (due to coroutines being not exactly in sync with the framerate), it’s still relatively close to the principle of how much time you want it to take to change. So, entering 1f for Speed takes 1-2 seconds for the change to smoothly occurs while entering 10f takes 10-15 seconds.
**WeatherManager.instance.**ChangeFogColor(int ColorID, float Speed);
This one is far more straight forward. ColorID is based on the references that an be set in the Inspector. (You can set it by script too, but it would requires a bit of an addition so that FogColor_Count also updates. FogColor_Count is a double-checkers to keep track of the number of color references without having to always check the array length and uselessly waste memory blocks to the garbage collector.)
If you prefer, you can set up your own color input and change it from a local color reference to an external color input.
I just like it when I set thing up neatly in a way that is easy to edit afterward if needed.
Speed is the same as with Speed from the other function. How fast you want the fog color to change from A to B.
To give an example of how the speed can be used is if the player move from an area that has what seems like a natural white fog to an area filled with flames. As the player move closer to the flaming area, you can smoothly change the fog color and distance/density from a cold white to a hot orange.
You can also use this to setup a momentary dust storm or whatever.
The quality of Coroutine is that it’s clear data process on their own “time”. Impact around variation on the performance are well managed when in a Coroutine.
In the Update or LateUpdate, if frames can’t be skipped and makes things awkward when performance slightly drop with things that are supposed to be smooth like fog transitions. In coroutines, the framerate is relative and a skipped frame is… well skipped because the while() conditional can work even if frames are skipped.
Also, the fact that I uses IEnumerator instances (FogEvent and FogColorEvent) ensure that I only run 1 of each Coroutine at anytime.