Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Showcase Custom Dynamic Lighting Attenuation

Discussion in 'Universal Render Pipeline' started by Jesus, Mar 4, 2024.

  1. Jesus

    Jesus

    Joined:
    Jul 12, 2010
    Posts:
    504
    Info: Unity 2023.2.11f1, URP 16.0.5 (copied to local Packages folder).

    I'm looking to adjust the attenuation for point lights in two ways. Firstly to change the attenuation distance by a scalar, and secondly to change the attenuation exponent.

    I've been digging around in the lighting.hlsl file for a while and either I'm more blind than usual or I'm missing where this can be changed. The goal is to have those 2 values be changeable in realtime. Where should these be set (or multiple places, don't need to do it separately for Forward/Def?), and what's the best way to pass those values along?

    If we assume a super-simple point light attenuation as NdotL * (1 / pow(distance, 2)), you get this:
    Lighting 1D 2E.png

    The first change is to the distance, scaling that factor by A. In this case, a factor of 1/3. So what you see is NdotL * (1 / pow(distance / 3), 2). Lighting 3D 2E.png

    The second change is an adjustment to the attenuation exponent. In these 2 pics, I use exponent values of 1 and 3.

    NdotL * (1 / pow(distance, 1))
    Lighting 1D 1E.png
    NdotL * (1 / pow(distance, 3))
    Lighting 1D 3E.png

    Demo pics done with an unlit Shader Graph. I don't really want to do that since it means everything that relies on that in particular (and I'd have to hand-re-create Unity's BRDF lighting model!), and I'd rather make small changes to the lighting calcs in one place and have everything else run Unity's 'default' lighting model.
     
  2. Jesus

    Jesus

    Joined:
    Jul 12, 2010
    Posts:
    504
    OK, so I've got the control I need for the lighting attenuation scalar and exponent, just need to pass some values to it.

    For those interested, the code is in RealtimeLights.hlsl in the Universal RP / Shader Library folder.


    float DistanceAttenuation(float distanceSqr, half2 distanceAttenuation)
    {
    // We use a shared distance attenuation for additional directional and puctual lights
    // for directional lights attenuation will be 1

    // get a safe distance attenuation here, doesn't like being done elsewhere...
    distanceSqr = sqrt(distanceSqr); // attenuation is now linear in meters

    // custom distance scalar
    float scaleFactor = 1.0;
    distanceSqr = distanceSqr * scaleFactor;

    // custom falloff exponent
    float falloffExponent = 2.0;
    distanceSqr = pow(distanceSqr, falloffExponent); // now, to control the exponent


    float lightAtten = rcp(distanceSqr);
    float2 distanceAttenuationFloat = float2(distanceAttenuation);
    // Use the smoothing factor also used in the Unity lightmapper.
    half factor = half(distanceSqr * distanceAttenuationFloat.x); // default one
    half smoothFactor = saturate(half(1.0) - factor * factor);
    smoothFactor = smoothFactor * smoothFactor;
    //smoothFactor = 1.0; // smoothfactor is the fading for max range of point lights. Remove it and the light range does nothing.
    return lightAtten * smoothFactor;
    }
     
  3. Jesus

    Jesus

    Joined:
    Jul 12, 2010
    Posts:
    504
    Well, it looks like I can get there using Shader.SetGlobalFloat to pass the value along to the hlsl file, and adding these lines at the top of the file, line ~10...

    float _LightScalar;
    float _LightAttenuationExponent;
     
    Sluggy likes this.
  4. Jesse_Pixelsmith

    Jesse_Pixelsmith

    Joined:
    Nov 22, 2009
    Posts:
    296
    @Jesus saves...time. I've been looking something to modify the attenuation to achieve more of a comic look while still incorporating lights.

    Attached are two pictures using the default values (scalar of 1 and attenuation exponent of 2), one where the spotlight is low intensity (doesn't blow out colors but really only hits things directly in front of it, and one with a high intensity spotlight, which "hits" a lot more things given its range, but the colors are blown out.

    I modified the scalar to and attenuation exponent to be close to zero (0.01) and produced the third picture, which I'm very happy with for the spotlight.

    One issue I see immediately is the point light now has "jaggies" towards the end of its range. I see this with the spotlight as well if I bring the range in, but since I will always be shining them on some kind of background, I just max the range out to be > than whatever I'm hitting and I get nice crisp lines.

    I'm guessing that with default values you don't see this because normally the falloff is near zero by the time it hits the end of the range.

    My "bandage" fix was to just change the red cave glow point light to a spotlight, and point it at the cave entrance. This works in this instance and gives me the clean lines I want, but it wouldn't work for say a campfire where someone is standing in between it and the camera (should cast a shadow going towards the camera).

    Any idea on how I might avoid the jaggies and still be able to use point lights? Some kind of function that makes it falloff intensely close to the max range, but is otherwise fairly consistent?
     

    Attached Files:

  5. Jesus

    Jesus

    Joined:
    Jul 12, 2010
    Posts:
    504

    I think those are a result of your fall-off, which is now near nonexistent, reaching the 'range' limit of your light, as well as scaling the position of the edge-of-light falloff (see below). Can you check the range of the point light in your scene? That probably also affects things like tiled rendering, since it might use that range rather than when attenuation is zero, and just assumed the light will be near zero by then.

    The line near the bottom of the function deals with fading the edge of the point light at its range:
    half factor = half(distanceSqr * distanceAttenuationFloat.x); // default one

    If you mess with distanceSqr, then you mess with the distance from the light that edge falloff happens.
    I suspect if you feed it an un-altered distanceSqr rather than the altered one, you should see a fade in light intensity over the last ~10% of the light range, even with point lights.
     
    Last edited: Mar 18, 2024
  6. Jesus

    Jesus

    Joined:
    Jul 12, 2010
    Posts:
    504
    Note that if you want to CHANGE TO A NEW ATTENUATION FUNCTION (as in, write your own, and leave the existing one, or even just add new parameters to the existing one), you need to change its use in:

    Deferred.hlsl, line 36
    //float attenuation = DistanceAttenuation(distanceSqr, punctualLightData.attenuation.xy)....and so on and so forth
    as well as the one in RealtimeLights.hlsl
    //float attenuation = DistanceAttenuation(distanceSqr, distanceAndSpotAttenuation.xy);

    The Deferred.hlsl was tricky to find, it's in UniversalRP/Shaders/Utils.
     
  7. Jesus

    Jesus

    Joined:
    Jul 12, 2010
    Posts:
    504
    So I tried a few options, see the equations pictured below. In this example, the light intensity is 1, and the range is about 25. Default Unity Cube and Capsule, and Unity Spheres spawned via Shuriken Particle System.

    In the graph below, x axis is distance, y axis is light intensity.
    2024-03-22 10_55_17-Desmos _ Graphing Calculator.png 2024-03-22 10_56_08-Desmos _ Graphing Calculator.png


    Here's the 1/(d^2).

    You can see the point light near the ground emits a very bright light, brighter than intensity 1. Note the red line in the graph above.
    2024-03-22 10_43_18-My project - LogZ Lighting - Windows, Mac, Linux - Unity 2023.2.11f1_ _DX11_.png

    Here's 1/((d+1)^2).

    The bonus point here is that no matter how close you get to the point light, the intensity never is above 1, or in the end result the intensity is never above the color*intensity you set in the Light Inspector. This is the blue line in the graph above.
    2024-03-22 10_44_37-My project - LogZ Lighting - Windows, Mac, Linux - Unity 2023.2.11f1_ _DX11_.png

    And here's (1 - saturate(d/r))^2.

    You should be able to get range from distanceAttenuation.x somehow (???) in the normal function, with just a little re-arranging, that seems to be the range value. This basically scales the light as a gradient from distance 0 to distance (range), then inverse square of that. So if you want point lights to have a much larger area of coverage, and actually do something near their max range, this might be the way to go.

    It's the green line in the graph. It's higher intensity for further out, and never creates super-bright spots where the intensity is over 1 so it should be speckle-proof.
    2024-03-22 11_12_09-My project - LogZ Lighting - Windows, Mac, Linux - Unity 2023.2.11f1_ _DX11_.png
     
  8. Jesus

    Jesus

    Joined:
    Jul 12, 2010
    Posts:
    504
    Here's some other ideas for different curves I found, maybe some of these would be nice to play with?
    https://lisyarus.github.io/blog/graphics/2022/07/30/point-light-attenuation.html

    The variables for those might be set globally, declare them at the top of the hlsl file like in post #3 and set them in a script.

    Anyway, here's a pic of the (1 - saturate(d/r))^2 light in scene. See how it covered the entire range (25 units) of the point light, unlike the normal one which would need to have the intensity set to >10 before it did anything above 20m away.
    2024-03-22 11_18_15-My project - LogZ Lighting - Windows, Mac, Linux - Unity 2023.2.11f1_ _DX11_.png
     
    Last edited: Mar 22, 2024
  9. sanamonie

    sanamonie

    Joined:
    Jun 1, 2023
    Posts:
    1
    Hi, is it possible you could post an image of your DistanceAttenuation function using the saturated distance/distanceAttenuation.x ? I cant seem to get the same results.
     
  10. Jesus

    Jesus

    Joined:
    Jul 12, 2010
    Posts:
    504
    So after a bit of googling apparently light range as a usable number is rsqrt(distanceAndSpotAttenuation.x) (according to this and this). In fact that second link looks pretty useful in general.

    I'll clean up a script and see if I can post it soon.
     
  11. maris_admin

    maris_admin

    Joined:
    May 7, 2021
    Posts:
    9
    Could you post what exactly you changed? I want my spotlight to not be so bright up close and have a more linear dropoff.