Attenuation and shadows for pixel lights
Reference Manual > ShaderLab Reference > Advanced ShaderLab topics > Unity's Rendering Pipeline > Attenuation and shadows for pixel lights

Attenuation and shadows for pixel lights

The different light types in Unity's pixel lighting pipeline are implemented using texture lookups to do the attenuation:

Additionally, if a pixel light casts shadows, and the object receives shadows, then the shadow map is looked up one or more times:

Supporting all these combinations can quickly get out of hand! For this reason, Unity uses a multi compilation scheme, where shader author has to use several macros to compute the light attenuation & shadows, and all the needed permutations will be generated by Unity.

Attenuation in fragment programs

When using fragment programs, Unity helps setting these combinations up by providing some macros and definitions in AutoLight.cginc and UnityCG.cginc Cg include files (for GLSL: AutoLight.glslinc and UnityCG.glslinc). Then what you do is:

This is pretty complex, so a full shader example is in order. This shader fully shows how to use compute light attenuation and shadows in a fragment program. The rest of the shader is kept minimal - it exposes just a color and computes a simple diffuse lighting per-vertex.

Shader "Light Attenuation" {
Properties {
    _Color ("Main Color", Color) = (1,1,1,0.5)
}

Category {
    Blend AppSrcAdd AppDstAdd
    Fog { Color [_AddFog] }

    // Fragment program cards
    SubShader {
        // Ambient pass
        Pass {
            Tags {"LightMode" = "PixelOrNone"}
            Color [_PPLAmbient]
            SetTexture [_Dummy] {constantColor [_Color] Combine primary DOUBLE, constant}
        }
        // Vertex lights
        Pass {
            Tags {"LightMode" = "Vertex"}
            Lighting On
            Material {
                Diffuse [_Color]
                Emission [_PPLAmbient]
            }
            SetTexture [_Dummy] {constantColor [_Color] Combine primary DOUBLE, constant}
        }
        // Pixel lights
        Pass {
            Tags { "LightMode" = "Pixel" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_builtin
#pragma fragmentoption ARB_fog_exp2
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
#include "AutoLight.cginc"

// Define the structure
struct v2f {
    V2F_POS_FOG;
    LIGHTING_COORDS // <= note no semicolon!
    float4 color : COLOR0;
};

// Vertex program
v2f vert (appdata_base v)
{
    v2f o;
    PositionFog( v.vertex, o.pos, o.fog );

    // compute a simple diffuse per-vertex
    float3 ldir = normalize( ObjSpaceLightDir( v.vertex ) );
    float diffuse = dot( v.normal, ldir );
    o.color = diffuse * _ModelLightColor0;

    // compute&pass data for attenuation/shadows
    TRANSFER_VERTEX_TO_FRAGMENT(o);
    return o;
}

// Fragment program
float4 frag (v2f i) : COLOR
{
    // Just multiply interpolated color with attenuation
    return i.color * LIGHT_ATTENUATION(i) * 2;
}
ENDCG
        }
    }
}

Fallback "VertexLit"
}

Given such a shader, Unity will compile dozens of combinations required to support different light types, with or without cookies and with or without shadows. It will also compile to OpenGL programs and Direct3D 9 shaders. All you have to do is use these light attenuation macros, and the dirty details will be handled for you!

Attenuation for older hardware

Writing pixel lit shaders for older hardware (that does not support fragment programs) is more involved due to resource constraints and the fact that these shaders have to be written in an assembly-like language. In this case shadows are not supported (again, because of hardware restrictions). Most often you end up writing separate passes that support 0, 1 or 2 light attenuation textures.

For example, the shader above written for ATI Fragment Shader cards (Radeon 8500 and up) would be like the example below. Ambient and Vertex passes are still the same in this case, but there are separate Pixel passes for 0, 1 and 2 attenuation textures, with different shader programs accordingly. In a real shader you'd implement both SubShaders in a single shader, and possibly a couple more for even older video cards (gotta love all the different cards out there, right?).

Shader "Light Attenuation" {
Properties {
    _Color ("Main Color", Color) = (1,1,1,0.5)
}

CGINCLUDE
// This block will be pasted into all later Cg program blocks
#include "UnityCG.cginc"
float4 Lighting( appdata_base v )
{
    // compute a simple diffuse per-vertex
    float3 ldir = normalize( ObjSpaceLightDir( v.vertex ) );
    float diffuse = dot( v.normal, ldir );
    return diffuse * _ModelLightColor0 * 2;
}
ENDCG

Category {
    Blend AppSrcAdd AppDstAdd
    Fog { Color [_AddFog] }

    // ATI Fragment shader cards
    SubShader {
        // Ambient pass
        Pass {
            Name "BASE"
            Tags {"LightMode" = "PixelOrNone"}
            Color [_PPLAmbient]
            SetTexture [_Dummy] {constantColor [_Color] Combine primary DOUBLE, constant}
        }
        // Vertex lights
        Pass {
            Tags {"LightMode" = "Vertex"}
            Lighting On
            Material {
                Diffuse [_Color]
                Emission [_PPLAmbient]
            }
            SetTexture [_Dummy] {constantColor [_Color] Combine primary DOUBLE, constant}
        }

        // Lights with 0 light textures
        Pass {
            Tags {
                "LightMode" = "Pixel"
                "LightTexCount" = "0"
            }
CGPROGRAM
#pragma vertex vert
struct v2f {
    V2F_POS_FOG;
    float4 color : COLOR0;
};
v2f vert (appdata_base v)
{
    v2f o;
    PositionFog( v.vertex, o.pos, o.fog );
    o.color = Lighting( v );
    return o;
}
ENDCG
            Program "" {
                SubProgram {
                    "!!ATIfs1.0
                    StartOutputPass;
                        MOV r0, color0; # just output color
                    EndPass;
                    "
                }
            }
        }

        // Lights with 1 light texture
        Pass {
            Tags {
                "LightMode" = "Pixel"
                "LightTexCount" = "1"
            }

CGPROGRAM
#pragma vertex vert
uniform float4x4 _SpotlightProjectionMatrix0;
struct v2f {
    V2F_POS_FOG;
    float4 color : COLOR0;
    float4 LightCoord0 : TEXCOORD0; // one light texcoord
};
v2f vert (appdata_base v)
{
    v2f o;
    PositionFog( v.vertex, o.pos, o.fog );
    o.color = Lighting( v );
    o.LightCoord0 = mul(_SpotlightProjectionMatrix0, v.vertex); // light texcoord
    return o;
}
ENDCG
            Program "" {
                SubProgram {
                    "!!ATIfs1.0
                    StartOutputPass;
                        SampleMap r0, t0.str; # attenuation
                        MUL r0, color0, r0.a; # multiply with color
                    EndPass;
                    "
                }
            }
            SetTexture[_LightTexture0] {}
        }

        // Lights with 2 light textures
        Pass {
            Tags {
                "LightMode" = "Pixel"
                "LightTexCount" = "2"
            }
CGPROGRAM
#pragma vertex vert
uniform float4x4 _SpotlightProjectionMatrix0;
uniform float4x4 _SpotlightProjectionMatrixB0;
struct v2f {
    V2F_POS_FOG;
    float4 color : COLOR0;
    float4 LightCoord0 : TEXCOORD0; // two light texcoords
    float4 LightCoordB0 : TEXCOORD1;
};
v2f vert (appdata_base v)
{
    v2f o;
    PositionFog( v.vertex, o.pos, o.fog );
    o.color = Lighting( v );
    o.LightCoord0 = mul(_SpotlightProjectionMatrix0, v.vertex);
    o.LightCoordB0 = mul(_SpotlightProjectionMatrixB0, v.vertex);
    return o;
}
ENDCG
            Program "" {
                SubProgram {
                    "!!ATIfs1.0
                    StartOutputPass;
                        SampleMap r0, t0.stq_dq; # attenuation1
                        SampleMap r1, t1.stq_dq; # attenuation2
                        MUL r0, color0, r0.a;
                        MUL r0, r0, r1.a;
                    EndPass;
                    "
                }
            }
            SetTexture[_LightTexture0] {}
            SetTexture[_LightTextureB0] {}
        }
    }
}

Fallback "VertexLit"
}