2
\$\begingroup\$

I'm trying to write a fairly basic shader but I keep running into lighting issues with Unity. My first problem was trying to figure out which variable stored the light's position in world space. (I'm only working with one light, at least at the moment).

I finally found something that responds to movement of my light source: unity_LightPosition[0]. However the shader still didn't do what I wanted it to do, so I did some debugging:

I have a basic vertex and fragment shader and what I'm currently doing in the fragment shader is:

float4 c = float4(0, 0, 0, 1);
float3 lp = normalize(unity_LightPosition[0]);
c.rgb = lp;
return c;

This should just set the color of my object to the normalized position of the light source, correct? What I see in my scene is my object changes colors when I move my camera around it (I'm not moving the light source at all, it is a separate entity). The color also seems to increase in strength as I zoom in.

To me, it seems as if unity_LightPosition[0] is related to my camera's position, as well as orientation and zoom level, yet the documentation I've found says it should represent the position of the light in world space.

I tried switching to using the following:

float3 lp = float4(
    unity_4LightPosX0[0],
    unity_4LightPosY0[0],
    unity_4LightPosZ0[0],
    1.0
);

...but that only colors my object red regardless of the light's actual position.

Additionally, I've set the LightMode tag to Vertex.

It seems like simply getting the world position of the light source shouldn't be this difficult. What am I not doing correctly?

EDIT: I've been playing around with the camera and light position and I think that unity_LightPosition[0] actually puts out position in the projection space. If I place the light source in the lower-left corner of the screen, the object is black. Upper-left the object is green (0, 1), upper-right is orange (1, 1), and lower-right is red (1, 0). When I place it in the lower-left (0, 0) and move the camera in front of it, it becomes blue. This is consistent with the depth value behavior. (0, 0, >0) would appear as blue.

Now, how to get it from projection back to world?...or should I use a different variable?

\$\endgroup\$
  • \$\begingroup\$ which kind of light are you using? directional/spotlight/pointlight? important or not important (aka per pixel or per vertex)? \$\endgroup\$ – Heisenbug Jun 16 '14 at 17:50
  • \$\begingroup\$ I'm using a point light, set to Important. I could also feasibly do this with a directional light if that would be easier. \$\endgroup\$ – celestialorb Jun 16 '14 at 17:51
1
\$\begingroup\$

I can give you an answer for what concern forward rendering and Cg shaders.

In the shader first pass (BasePass) Unity set uniforms value of the 1 important directional light and up to 4 vertex lights.

Base Pass (Directional per pixel light) uniforms:

  • _WorldSpaceLightPos0 : position in world space of the first directional light
  • _LightColor0 : color of the first directional light

The additional per vertex light orientations can be accessed using:

  • unity_4LightPosX0 : x component of the world space positions on the 4 per vertex lights
  • unity_4LightPosY0 : y component
  • unity_4LightPosZ0 : z component
  • unity_4LightAtten0 : attenuation factors

There are several references on the web suggesting using unity_4LightPosX0 uniform values. I don't know, maybe they work on different rendering paths. For what concern the forward base pass the uniforms above should work correctly.

I found out that 2 good ways to understand how rendering pipeline works are:

  • read all .gcinc files inside unity Data/CGIncludes folder. They contains some macros and helper functions that can both help you writing shaders and show you which values are used for what (documentation often isn't enough).
  • compile builtin surface shaders with #pragma debug directive. You can inspect the generated Cg code for default shaders.

EDIT

I'm not sure for what concern pointlights, but for spotlights Unity uses an additional render pass (ForwardAdd) if you want to render them per pixel. So for example if you try to access _LightMatrix0 or _WorldSpaceLightPos0 in the ForwardBase pass the values aren't always consistent.

So I guess that if you want to display point or spot light positions of an important light per pixel you should do that inside a second pass in the shader with the following tag: Tags { "LightMode" = "ForwardAdd" }. (Or alternatively manually settings uniforms values: I'm doing this for rendering both lightmaps and spotlights in a single pass shader).

\$\endgroup\$
  • \$\begingroup\$ Thanks! I tried using the unity_4LightPos variables earlier, but they didn't seem to work. Perhaps I was still using the Vertex tag. I used ForwardBase with it and my object correctly responds to the position of the light. :) \$\endgroup\$ – celestialorb Jun 16 '14 at 19:59
2
\$\begingroup\$

I've just encountered the same issue with _WorldSpaceLightPos0, which was sometimes (0,0,0,0) and it depended on camera (not light and not mesh) position. It turned out that I forgot to put lighting mode tag

Tags { "LightMode" = "ForwardBase" }

into my PASS section

\$\endgroup\$
1
\$\begingroup\$

unity_LightPosition is in Model-View space. To convert it into world space you'll have to multiply it by the inverse matrix of UNITY_MATRIX_MV.

Here's some code that might be helpful in inverting a matrix: http://answers.unity3d.com/questions/218333/shader-inversefloat4x4-function.html

Example:

float4 worldLightPos = mul( inverse(UNITY_MATRIX_MV), unity_LightPosition[i]);

Note, that matrix inversion is a resource-intensive process, and it's better to perform it in C#/JS code, and supply to the shader via a material property.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.