Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Implementing linear falloff lights in URP shaders

Discussion in 'Shaders' started by ChanzDog, Apr 1, 2020.

  1. ChanzDog

    ChanzDog

    Joined:
    Sep 28, 2017
    Posts:
    43
    I am writing a game client which supports an old game format. The lights in this time (1999) most likely used a linear falloff. I am using the Universal Render Pipeline and already have custom shaders created but they are using the standard Lighting.hlsl.

    Here are my questions:
    1. Does linear falloff mean that the light is at brightness 1 at 0 distance and brightness 0 at the range defined by the radius?
    2. Does it make sense that a game from 1999 using a fixed function DirectX pipeline would use linear falloff lights?
    3. Does the function
    DistanceAttenuation
    in Lighting.hlsl control this falloff by default?
    4. What is the actual formula for linear falloff? My guess is it's brightnessAtDistance = brightness * (1.0 - distance / radius). How do I add this to the shader? Where should I implement this? How does this relate to attenuation?

    Any direction or examples of it being done would be helpful. And if it's relevant to the URP, that's even better.

    Thanks!
     
  2. Spaniel

    Spaniel

    Joined:
    Dec 23, 2012
    Posts:
    52
  3. Spaniel

    Spaniel

    Joined:
    Dec 23, 2012
    Posts:
    52
    I think I almost have it. I bypass the
    DistanceAttenuation
    function completely and I just need to divide the distance from the light by the light range. And then subtract is from 1 and ensure it's clamped between 0 and 1.
    It should look like this:

    half attenuation = clamp(1.0f - (distance / range), 0.0, 1.0)



    I am at a loss however for how to get the light range. The function I am doing this inside is `
    Light GetAdditionalPerObjectLight(int perObjectLightIndex, float3 positionWS)`. I see some distance calculations but I am unsure if these are directly related to range.

    I just need to find the light's range in world coordinates. I have confirmed it works when I hardcode the range.
     
    thelebaron likes this.
  4. Spaniel

    Spaniel

    Joined:
    Dec 23, 2012
    Posts:
    52
    This is the function I am currently looking which calculates the additional light. I can't for the life of me figure out how to get the range for each light. I thought it might be
    distanceAndSpotAttenuation.w
    and thought it might be squared as well so I used the
    Code (CSharp):
    1. sqrt()
    function on it, but no luck.

    Code (CSharp):
    1. // Fills a light struct given a perObjectLightIndex
    2. Light GetAdditionalPerObjectLight(int perObjectLightIndex, float3 positionWS)
    3. {
    4.     // Abstraction over Light input constants
    5. #if USE_STRUCTURED_BUFFER_FOR_LIGHT_DATA
    6.     float4 lightPositionWS = _AdditionalLightsBuffer[perObjectLightIndex].position;
    7.     half3 color = _AdditionalLightsBuffer[perObjectLightIndex].color.rgb;
    8.     half4 distanceAndSpotAttenuation = _AdditionalLightsBuffer[perObjectLightIndex].attenuation;
    9.     half4 spotDirection = _AdditionalLightsBuffer[perObjectLightIndex].spotDirection;
    10.     half4 lightOcclusionProbeInfo = _AdditionalLightsBuffer[perObjectLightIndex].occlusionProbeChannels;
    11. #else
    12.     float4 lightPositionWS = _AdditionalLightsPosition[perObjectLightIndex];
    13.     half3 color = _AdditionalLightsColor[perObjectLightIndex].rgb;
    14.     half4 distanceAndSpotAttenuation = _AdditionalLightsAttenuation[perObjectLightIndex];
    15.     half4 spotDirection = _AdditionalLightsSpotDir[perObjectLightIndex];
    16.     half4 lightOcclusionProbeInfo = _AdditionalLightsOcclusionProbes[perObjectLightIndex];
    17. #endif
    18.  
    19.     // Directional lights store direction in lightPosition.xyz and have .w set to 0.0.
    20.     // This way the following code will work for both directional and punctual lights.
    21.     float3 lightVector = lightPositionWS.xyz - positionWS * lightPositionWS.w;
    22.     float distanceSqr = max(dot(lightVector, lightVector), HALF_MIN);
    23.  
    24.     half3 lightDirection = half3(lightVector * rsqrt(distanceSqr));
    25.     half attenuation = DistanceAttenuation(distanceSqr, distanceAndSpotAttenuation.xy) * AngleAttenuation(spotDirection.xyz, lightDirection, distanceAndSpotAttenuation.zw);
    26.  
    27.     Light light;
    28.     light.direction = lightDirection;
    29.     light.distanceAttenuation = attenuation;
    30.     light.shadowAttenuation = AdditionalLightRealtimeShadow(perObjectLightIndex, positionWS);
    31.     light.color = color;
    32.  
    33.     // In case we're using light probes, we can sample the attenuation from the `unity_ProbesOcclusion`
    34. #if defined(LIGHTMAP_ON) || defined(_MIXED_LIGHTING_SUBTRACTIVE)
    35.     // First find the probe channel from the light.
    36.     // Then sample `unity_ProbesOcclusion` for the baked occlusion.
    37.     // If the light is not baked, the channel is -1, and we need to apply no occlusion.
    38.  
    39.     // probeChannel is the index in 'unity_ProbesOcclusion' that holds the proper occlusion value.
    40.     int probeChannel = lightOcclusionProbeInfo.x;
    41.  
    42.     // lightProbeContribution is set to 0 if we are indeed using a probe, otherwise set to 1.
    43.     half lightProbeContribution = lightOcclusionProbeInfo.y;
    44.  
    45.     half probeOcclusionValue = unity_ProbesOcclusion[probeChannel];
    46.     light.distanceAttenuation *= max(probeOcclusionValue, lightProbeContribution);
    47. #endif
    48.  
    49.     return light;
    50. }
    Just to be clear, I want to get the range in world coordinates of the light currently being addressed. The value that corresponds with:

    upload_2020-5-16_1-50-20.png
     
  5. Spaniel

    Spaniel

    Joined:
    Dec 23, 2012
    Posts:
    52
    Figured it out.


    float range = rsqrt(distanceAndSpotAttenuation.x);
     
  6. tremault

    tremault

    Joined:
    Oct 16, 2015
    Posts:
    58
    Hi, it looks like you were trying to do the exact thing I'm currently trying to do (and floundering).
    Can I ask how you implemented this?
    I tried editing the lighting hlsl file in the urp shaderlibrary but it reverts back to default.
    I've tried snippets of the code you posted in my shadergraph custom function but I'm not really sure what I'm doing and I end up with missing variables.

    I really wish there was decent documentation for this URP, they say it's fully scriptable but then they don't tell you how to do anything.
     
    SpikeEscape likes this.
  7. SpikeEscape

    SpikeEscape

    Joined:
    Mar 14, 2019
    Posts:
    5
    I've just managed to get my own attenuation working in my project by following this tutorial: https://www.reddit.com/r/gamedev/comments/pf9nr2/i_wrote_a_tutorial_about_adding_custom_lighting/

    The video itself will walk you through making a custom shader in shadergraph which will run your own lighting calculations, in the comments of this reddit thread there is a question by Gigazelle in which the response explains in enough detail how to implement your own attenuation inside of custom rewrites of the functions GetMainLight() and GetAdditionalLight()

    Good luck to you!
     
    nekowei and tremault like this.
  8. GeorgeBGreen

    GeorgeBGreen

    Joined:
    Dec 8, 2015
    Posts:
    1

    Hey there! Spaniel's line above is basically correct, but I did need to do a little more digging into the lighting.hlsl file to figure out how to use it.

    If you stick this into the custom lighting hlsl you're writing, you're gonna get the range for light 0.

    Code (CSharp):
    1. #if USE_STRUCTURED_BUFFER_FOR_LIGHT_DATA
    2.         float range = rsqrt(_AdditionalLightsBuffer[0].attenuation);
    3. #else
    4.         float4 lightPositionWS = _AdditionalLightsPosition[lightIndexInBuffer];
    5. #endif
    Here's how to iterate through all as well, note that you also need to use GetPerObjectLightIndex to get the correct index.
    I'm also grabbing the light worldspace position here, maybe that's helpful to someone as well!

    Code (CSharp):
    1. uint numAdditionalLights = GetAdditionalLightsCount();
    2. for (uint lightI = 0; lightI < numAdditionalLights; lightI++)
    3. {
    4.         uint lightIndexInBuffer = GetPerObjectLightIndex(lightI);
    5.  
    6. #if USE_STRUCTURED_BUFFER_FOR_LIGHT_DATA
    7.         float4 lightPositionWS = _AdditionalLightsBuffer[lightIndexInBuffer].position;
    8.         float range = rsqrt(_AdditionalLightsBuffer[lightIndexInBuffer].attenuation);
    9. #else
    10.         float4 lightPositionWS = _AdditionalLightsPosition[lightIndexInBuffer];
    11.         float range = rsqrt(_AdditionalLightsAttenuation[lightIndexInBuffer]);
    12. #endif
    13. }
     
    PutridEx likes this.