HDRP: Possible to gradually bring in Volumetric Fog gradually?

I was trying to add an area where volumetric lighting would be in effect,and I was hoping I would be able to gradually increase the effect the further the player got into the volume. I see that Density Volume has a Blend Distance, but it doesn’t have a small an effect as I was expecting. I see a very noticeable pop in lighting when I enter the density volume. For example, here’s the scene when I’m just outside the volume:

4585489--427273--upload_2019-5-27_16-14-16.png

And if I get one pixel closer, suddenly the volumetric lighting is basically at full strength, with the point light in the white sphere having a strong glow to it, and everything else getting a bit darker:

4585489--427276--upload_2019-5-27_16-15-9.png

I’m using the following Density Volume, as Scene settings:

I tried adjusting the various settings, but the moment I get anywhere within the Volume, the lighting changes dramatically. Is there any way to ease into the volumetric effect more?

Try using a normal light source like a point light instead of the density volume.
I think it has better options to get a certain look.

Thanks. I guess I was using Density Volume under 4.x, because maybe that’s the only way I could get light rays to work at that time, but under 5.x I see they weren’t actually needed anymore. Or maybe I never needed it and just didn’t do it right under 4.x in the first place.

Anyway, I got rid of the density volume. This doesn’t address the issue alone, though, since the volumetric lighting will still “pop” when entering a volume. But I figured that’s because the global volume didn’t have any volumetric fogs settings on it, so there was nothing to blend. A working solution to a gradual increase in volumetric involves having either a global volume, or a larger volume, with volmetric lighting enables (but at very low strength), so that entering the inner volume blends gradually into it.
I have a fairly small number of areas that use volumetric lighting, so I think my general approach will be to use two nested volumes, where the outer one has volumetric lighting enabled, but with only a minimal effect, and the inner volume having the full effect. This gives a pretty nice gradually ramp up.

I initially thought I got blending looking good, but I was actually just wrong, and mislead by the built-in blending that occurs in volumetric lighting when you get very far from the light. I mistook that for some setting on an outer volume. So, I didn’t actually resolve the blending issue. However, at this point I’ve just placed my volume in such a way that it isn’t noticeable.

So the general question remains: If you wanted to have volumetric lighting fade in as you enter a volume, instead of all popping in at once, how would one do that? It seems that the outer volume and inner volume idea doesn’t work, because volumetric lighting seems to be fairly binary. I haven’t found any settings in the Volumetric Lighting component that diminishes the effect to not noticeable, so I can’t therefore blend that into the full effect on an inner volume.

The setting in the main Volumetric Fog settings that can be attached to Volumes is called ‘Base Fog Distance’ (I think it used to be called something else in earlier HDRP). Setting that to a really small value like 1 or 10 will give dense fog, setting it to something really high like 10000 will give you barely visible fog.

The same setting is just called ‘Fog Distance’ for Density Volumes. Density Volumes also have the ability to use a 3D texture which alters the density of the fog within different positions within the density volume itself.

Generally I would not put a full Volume and a Density Volume in the same GameObject, if you need Density Volumes then just create those on their own using menu options such as GameObject->Rendering->Density Volume, and make sure they are in an area where the volumetric lighting is switched on (via a global or appropriately positioned local Volume)

I have not played with either main Volume blending or Density Volume blend distance options so I cannot answer some of your specific questions, but there is some stuff in the docs about that (eg some way down this page https://github.com/Unity-Technologies/ScriptableRenderPipeline/wiki/Volumetric-Fog )

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. :wink:

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.

3 Likes