# Lighting Intensity and Overbright Color

Unity docs say that the Intensity variable on a light is multiplied by the color. It doesn’t give anymore detail, so I’m left to assume that the _LightColor0 variable in CG contains the result of Intensity * Color. Either I’m doing something wrong in my shader code, or I’m missing something else. _LightColor0 never seems to be greater than 1.0, which is disappointing. I want to be able to have color values way above 1.0 so that I can have super bright lighting that blows the colors out to white realistically.

Can anyone explain how the intensity is supposed to work and how to get directional light values truly way above 1.0?

I’m convinced the light intensity variable in Unity is broken. Proof:

Set directional light color to solid Red (1, 0, 0). In a shader, multiply the lightColor by 0.5. No matter how high you crank light intensity, the resulting color will always be 0.5. That means that light intensity times light color is always capped at 1.0. This is not good behavior, as it renders light intensity value utterly useless.

Can someone please tell me if the error is on my end or if the light intensity variable really is broken. The docs say light intensity cannot be accessed directly in the shader; is this true?

There are two things you’re missing. Firstly, lighting is multiplied by two. So the default situation for a red object with a directional light is:

albedo * lightColor * lightIntensity * 2 = output
(1, 0, 0) * (1, 1, 1) * 0.5 * 2 = (1, 0, 0)

Output is indeed clamped to 1.0, since that’s the reddest that the red pixels on your screen will go. So even if you have a light with the maximum intensity (8) then you’ll still only get red:

(1, 0, 0) * (1, 1, 1) * 8.0 * 2 = (16, 0, 0) => (1, 0, 0)

This brings us to the second thing you’re missing when expecting white: colour is the combination of the red, green, and blue channels, which are orthogonal. This means that pure red will never turn into white simply by being multiplied with a higher intensity. The red channel gets really large before being clamped, but the green and blue ones remain at zero. Sixteen times nothing is still nothing.

However, if you put a non-zero value into your non-red channels, you’ll see them boosted by your super-bright light:

(1, 0.1, 0.1) * (1, 1, 1) * 8.0 * 2 = (16, 1.6, 1.6) => (1, 1, 1)

Daniel,

Where did you get the lightintensity variable from? It doesn’t appear to be accessible in the shader at all. According to Unity docs, all we have access to is _LightColor0.

My point is that _LightColor0 is already clamped to 1.0. At least that’s how it appears from the tests I tried. Maybe I screwed up somehow.

How do I access the intensity variable in the shader?

Ah, I misread your question. However, I’m not seeing the behaviour you describe: if I multiply my albedo texture by 0.0625, I can still get a head-on directional light to bring the results back to the brightness of the original texture.

One thing worth mentioning is that LightColor0 is of type fixed4, which means that on mobile devices its values will be clamped between [-2,2]. This should still allow you to get 4x brightness, though (as opposed to 16x on other platforms).

I always thought the intensity of a light at a given position is inside the attentuation variable. “atten” if you are using Surface Shaders.

Unity’s standard diffuse model-

``````c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten * 2);
``````

Old mobile devices. PowerVR Series 6 doesn’t ever use low precision.

I assumed this too, but it is actually premultiplied into the light value.

I think “most” is a more useful way of putting it than “old”. But yes, one day we won’t have to worry about this limitation.

Never using lowp is viable today if you’re optimizing primarily for how much of your life developing for old devices takes.

The atten value is clamped 0-1 and defines both the shadow value (0 in shadow, 1 in light) and light attenuation (0 light fully attenuated, 1 light at full strength) which are multiplied together.

The brightness of the light is actually premultiplied into _LightColor0.rgb.

Edit: Oops, I see Daniel’s already mentioned that.