unity-game-engineshaderfragment-shaderlightgeometry-shader

Correctly calculating attenuation of point and spot lights in Unity using a ForwardAdd pass


I've encountered a problem where in my custom Geometry shader, I can't seem to nail the correct attenuation. This causes all point- and spot lights to have a square of light around them. The color is correct, so is the range, but for some reason the distance does not want to change (which causes the attenuation to be off as well).

This is my forward pass:

Pass {
    Tags 
    {
        "LightMode" = "ForwardAdd"
    }

    Cull Off
    Blend One One

    CGPROGRAM
    
    #pragma hull hull
    #pragma domain domain

    #pragma target 4.6

    #pragma vertex vert
    #pragma geometry geo
    #pragma fragment fragAdd

    #include "UnityCG.cginc"
    uniform float4 _LightColor0; 
    uniform float4x4 unity_WorldToLight;
    uniform sampler2D _LightTextureB0; 

    fixed4 fragAdd(geometryOutput i, fixed facing: VFACE) : COLOR
    {
        float3 normalDirection = normalize(i.normal);
        
        float3 viewDirection = normalize(_WorldSpaceCameraPos - i.world.xyz);
        float3 lightDirection;
        float attenuation;
        
        if (0.0 == _WorldSpaceLightPos0.w)
        {
            attenuation = 1.0;
            lightDirection = normalize(_WorldSpaceLightPos0.xyz);
        } 
        else
        {
            float3 vertexToLightSource = _WorldSpaceLightPos0.xyz - i.world.xyz;
            lightDirection = normalize(vertexToLightSource);
            
            float distance = length(vertexToLightSource); 
            attenuation = tex2D(_LightTextureB0, float2(distance, distance)).a;
        }
        
        float3 diffuseReflection = attenuation * _LightColor0.rgb;

        return float4(diffuseReflection, 1.0);
    }

    ENDCG
}

Which in term creates this as a result (light has an intensity of 1): Since I don't have 10 reputation points, the results are showcased here

The light is visible, the color is correct and the range is about right, but the falloff is nowhere to be seen. I've already checked if the vertexToLightSource is off, but that seems to be correct.

If someone could help me then that'd be greatly appreciated.

I've tried to directly return the distance to see if it changes based on the distance between the light and the generated geometry, but it did not change and stayed the same, no matter where the light or geometry moved. Divided the distance also does not seem to do the trick.

The vertexToLightSource has the right position, since this does change when moving the light around the geometry.


Solution

  • In the end, I fixed it using the UNITY_LIGHT_ATTENUATION macro, instead of calculating the light attenuation myself. In case someone ends up at this question, here's the solution with UNITY_LIGHT_ATTENUATION.

    #include "UnityCG.cginc"
    #include "Lighting.cginc"
    #include "AutoLight.cginc"
    
    fixed4 frag(geometryOutput i, fixed facing: VFACE) : COLOR
    {
     UNITY_LIGHT_ATTENUATION(attenuation, i, i.world.xyz);
     float3 diffuseReflection = attenuation * _LightColor0.rgb * tex2D(_GroundTexture, i.uv.zw);
    
     return float4(diffuseReflection, 1.0);
    }
    

    More info found in this (Unity forums light-attenuation-function.71168)