Can't get additional lights from URP Forward+

Hey everyone, stupid question, but I’ve been trying for an hour and haven’t been able to figure it out on my own.
Anyone know how to properly get the light power and distance of additional lights?
I’ve got a really simple eye scelera shader that I just want to grab the light colour and distance, ignoring angles and shadows and everything. It should effectively be treating all additional lights as if they were point lights with shadows disabled (and also completely disregard shadows all together)
However, I haven’t been able to get my shader to get any additional lights at all. Here’s where it’s at at the moment:

Shader "Custom/EyeScelera"
{
    Properties
    {
        _BaseTex("Base Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "UniversalMaterialType" = "Lit"}

        Pass
        {
            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            // Declare all material properties in a single CBUFFER for SRP Batcher compatibility
            CBUFFER_START(UnityPerMaterial)
                TEXTURE2D(_BaseTex);
                SAMPLER(sampler_BaseTex);
            CBUFFER_END

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                half3 lightAmount : TEXCOORD2;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;

                // Transform object space to clip space
                OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);

                // Calculate lighting
                Light mainLight = GetMainLight();

                // Just get the brightness of the light, don't bother calculating normals or whatever
                OUT.lightAmount = mainLight.color;
    
                uint totalAdditional = GetAdditionalLightsCount();
                float3 positionWs = GetVertexPositionInputs(IN.positionOS.xyz).positionWS;

			    LIGHT_LOOP_BEGIN(totalAdditional)
				    Light light = GetAdditionalLight(lightIndex, positionWs);
				    OUT.lightAmount += light.color * light.distanceAttenuation;
			    LIGHT_LOOP_END

                // Pass UV for texture sampling
                OUT.uv = IN.uv;

                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                // Sample the base texture
                half3 baseColor = SAMPLE_TEXTURE2D(_BaseTex, sampler_BaseTex, IN.uv).rgb;

                // Combine the base texture with lighting
                half3 finalColor = baseColor * IN.lightAmount;

                return half4(finalColor, 1.0);
            }

            ENDHLSL
        }
    }
}

Here’s what’s actually happening in my scene, though:

I know my code should have a lot more #ifdefs and stuff, I just tried to strip away all the possible culprits and really nail down what is going on.

If I manually specified GetAdditionalLight() and just hard coded indexes, it would successfully get those additional lights (in no particular order), and I can confirm the main issue here seems to be the that GetAdditionalLightsCount is consistently returning 0 even though there’s undoubtedly additional lights there.

I’d also appreciate any tips on how to get ambient light (like light probes, and the environmental lighting) data, cos I could’ve really find anything concrete on that either.

Anyways, it’s really late here so I gotta give up for the night. If anybody more experienced than me can see the obvious thing I must be doing wrong, please let me know

Hi :slight_smile:

For iterating over lights in Forward+, you are on the right track to use the macros LIGHT_LOOP_BEGIN and LIGHT_LOOP_END. This is needed because Forward+ uses a different style of loop, and works a bit differently with respect to light indices. It requires a local variable InputData inputData , which we know is not exactly nice and we would like to change at some point.

Could you try to add this right before the light loop?

InputData inputData = (InputData)0;
inputData.positionWS = positionWS;
LIGHT_LOOP_BEGIN(totalAdditional)
...

Best,
Oliver

Sorry, I had the wrong material in the slot, it had been reset across reopening unity.
Unfortunately, it’s still doing exactly the same thing it was doing before.
I’m using Unity 6000.0.27f1, I’ll try installing the latest version now.

Edit: Confirmed not working in 6000.0.29f1.

Figured it out.
Adding “#pragma multi_compile _ _FORWARD_PLUS” to the beginning made it start working. Here’s what it looks like now:

Shader "Custom/EyeScelera"
{
    Properties
    {
        _BaseTex("Base Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "UniversalMaterialType" = "Lit"}

        Pass
        {
            HLSLPROGRAM

		    #pragma multi_compile _ _FORWARD_PLUS

            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            // Declare all material properties in a single CBUFFER for SRP Batcher compatibility
            CBUFFER_START(UnityPerMaterial)
                TEXTURE2D(_BaseTex);
                SAMPLER(sampler_BaseTex);
            CBUFFER_END

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                half3 lightAmount : TEXCOORD2;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;

                // Transform object space to clip space
                OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);

                // Calculate lighting
                Light mainLight = GetMainLight();

                // Just get the brightness of the light, don't bother calculating normals or whatever
                OUT.lightAmount = mainLight.color;
    
                uint totalAdditional = GetAdditionalLightsCount();
                float3 positionWS = GetVertexPositionInputs(IN.positionOS.xyz).positionWS;
                uint meshRenderingLayers = GetMeshRenderingLayer();
    
                InputData inputData = (InputData)0;
                inputData.positionWS = positionWS;

			    LIGHT_LOOP_BEGIN(totalAdditional)
				    Light light = GetAdditionalLight(lightIndex, positionWS);
				    OUT.lightAmount += light.color * light.distanceAttenuation;
			    LIGHT_LOOP_END

                // Pass UV for texture sampling
                OUT.uv = IN.uv;

                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                // Sample the base texture
                half3 baseColor = SAMPLE_TEXTURE2D(_BaseTex, sampler_BaseTex, IN.uv).rgb;

                // Combine the base texture with lighting
                half3 finalColor = baseColor * IN.lightAmount;

                return half4(finalColor, 1.0);
            }

            ENDHLSL
        }
    }
}
4 Likes

So the current documentation of custom lighting is incomplete in that it does not mention that the described solution for additional lights does not work for Forward+, only for Forward and Deferred?

Documentation in mind:

Because the described there method works nicely as a custom function for shadergraph for both Forward and Deferred but when switching to Forward+ it starts blinking additional light colours depending on the viewing angle.

The mentioned shadergraph custom function:

void AdditionalLights_float(float3 worldPos, float3 worldNormal, out float3 color)
{
  color = 0;

  #ifndef SHADERGRAPH_PREVIEW

  int lightCount = GetAdditionalLightsCount();
  for (int i = 0; i < lightCount; i++) {
      Light light = GetAdditionalLight(i, worldPos);
      float3 lightCol = light.color * light.distanceAttenuation;
      color += LightingLambert(lightCol, light.direction, worldNormal);
  }

  #endif
}
1 Like

Also, correct me if I’m wrong, isn’t the way it uses GetVertexNormalInputs incorrect? I was under the impression that you had to pass through the object-space normals, not the object-space position. It says so here.
My normals were misaligned because I had modelled my shader off of the example here.

Hello,

We’ve updated the documentation and added the following page that describes how to iterate over additional lights in a URP shader:

The shader example on the page works in both the Forward+ and Forward rendering paths.

Note: if you want to use this example in Unity 6000.1, replace the FORWARD_PLUS keyword with CLUSTER_LIGHT_LOOP.

1 Like