Deferred Lighting - How get light-space positions?

I’m trying to do some custom light attenuation in the deferred pipeline. Generally it involves reading a spotlight’s cookie in a unique way.

In order to do this, I need to get the light-space coordinates of a fragment. Trouble is, it appears _LightMatrix0 doesn’t mean the same thing as it does in the forward path (despite what the documentation says).

In other words, this line…

mul(_LightMatrix0, float4(wpos, 1));

…doesn’t look like it’s producing light-space coordinates for wpos.

Any thoughts?

EDIT:
As of Unity 5.4: LightMatrix0 was renamed to unity_WorldToLight

It works for me. Can you give more details? Where are you using _LightMatrix0, what light type, how you compute cookie coordinates?

Deferred Pipeline, Spotlight.

Well, for starters, I need to multiply the output of

mul(_LightMatrix0, float4(wpos, 1));

…by -1 to get close to what I’m looking for.

The z-coordinate is in unity units (great!), but the x, y coordinates are normalized to the spot angle. So [1, 0, 0] maps to a position at the limit of the spot-lights angle. which doesn’t help me. I need light-space coordinates.

For cookie coordinates, I need to figure out the angle from the local x-axis ( [1, 0, 0] in light space ) to the xy vector ( [x, y, 0] in light space ). To get a “horizontal” angle, this will map to my u coordinate in the light’s cookie. Easy enough with the result.

But, for the vertical angle ( Angle between [0, 0, 1] and [x, y, z] in light space ). This will map to my v coordinate in the light’s cookie.

The end goal is I need to be able to compare the forward distance to lateral (x, y) distance, since the units are not consistent, I can’t use them.

Ok, for spot light, _LightMatrix0 is a projection matrix that is used to project 2d texture inside spot light’s cone (it has different meaning for other light types). x,y coordinates are from 0 to 1 inside the cone. You can do something like this to get the cookie color:

float4 cookieUv = mul(_LightMatrix0, float4(wpos, 1));
cookieUv.xy /= cookieUv.w;
float4 cookieColor = tex2D(_Cookie, cookieUv.xy);

This should be the same for forward and deferred. It looks like you want a matrix that will transform from world space to light space. Without the projection? I don’t think there is one.

Projection is fine, as long coordinates in x, y, z are all relative to each other.

mul(_LightMatrix0,float4( worldPos, 1)) in the forward pipeline gets me coordinates that are all proportional to the range of the spotlight.
Forward Shader logic (In ForwardAdd pass of the fragment shader)

#ifdef SPOT
    return mul(_LightMatrix0,float4( worldPos, 1));
#endif

(This I can use, since the x, y and x values are all normalized and can be compared to each other to get angles)

mul(_LightMatrix0, float4(wpos, 1)) * -1 in the deferred pipeline gets me coordinates that are all proportional to the angle of the spotlight, or, a projection in other words. In addition, I have to multiply the result by -1.
Deferred Shader logic (In CalculateLight() of Internal-DeferredShading)

float4 lightSpacePos = mul(_LightMatrix0, float4(wpos, 1)); 
    return half4(lightSpacePos.x, lightSpacePos.y, 0, 0) * -1;

(This I can’t use, I can use the x, y coordinates to get angle yes, but without the spot angle of the light I have no way of comparing z against the x, y coordinates)

See the attached image. Left quad = lit w/ a deferred pipeline, Right quad = lit w/ a forward pipeline. Both of them use lighting shaders that simply return the bolded code snippets as colours (light-space coordinates) if the matrix was identical, I’d expect there to be no seam/differences between the quads.

Btw, thanks for your help so far. I’m self-taught on a lot of this stuff so forgive me if I’m getting lost on concepts like projection.

You are right. For reasons that are beyond me, the projection matrix is different in forward and deferred rendering. I don’t have time to reverse engineer why and I guess it doesn’t really matter in the end since you can’t change it anyway.

For deferred pipeline, you shouldn’t multiply the result by -1, but divide it by w component to get out of the homogeneous space. After that, x and y coordinates are in range [0, 1] inside the spot angle. Near and far plane of the projection is 0 and 1, so z coordinate has no relation to range. I’m afraid you can’t get what you want without knowing spot angle and/or range.

For reference, here’s how Unity handles cookie in both forward and deferred pipeline:

// FORWARD -----------------------------------------------------------------------------
uniform sampler2D _LightTexture0;
uniform unityShadowCoord4x4 _LightMatrix0;
uniform sampler2D _LightTextureB0;
inline fixed UnitySpotCookie(unityShadowCoord4 LightCoord)
{
    return tex2D(_LightTexture0, LightCoord.xy / LightCoord.w + 0.5).w;
}
inline fixed UnitySpotAttenuate(unityShadowCoord3 LightCoord)
{
    return tex2D(_LightTextureB0, dot(LightCoord, LightCoord).xx).UNITY_ATTEN_CHANNEL;
}
#define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
    unityShadowCoord4 lightCoord = mul(_LightMatrix0, unityShadowCoord4(worldPos, 1)); \
    fixed destName = (lightCoord.z > 0) * UnitySpotCookie(lightCoord) * UnitySpotAttenuate(lightCoord.xyz) * SHADOW_ATTENUATION(input);

// DEFERRED ------------------------------------------------------------------------
float3 tolight = _LightPos.xyz - wpos;
half3 lightDir = normalize (tolight);
       
float4 uvCookie = mul (_LightMatrix0, float4(wpos,1));
// negative bias because http://aras-p.info/blog/2010/01/07/screenspace-vs-mip-mapping/
float atten = tex2Dbias (_LightTexture0, float4(uvCookie.xy / uvCookie.w, 0, -8)).w;
atten *= uvCookie.w < 0;
float att = dot(tolight, tolight) * _LightPos.w;
atten *= tex2D (_LightTextureB0, att.rr).UNITY_ATTEN_CHANNEL;

Understood on the dividing by w.

I can force a global range and angle on the lights given how the project works and ensure the shader is using the same value, not ideal, but it will work. Hoping I wouldn’t have to go there, but thems the breaks.

Thanks again for the help.

No problem. I wish I could help you more. At least we both learned something today…