Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How's _LightColor0.rgb calculated in Unity (directional)? Does Unity multiply PI on light intensity?

Discussion in 'General Graphics' started by bobozzz, May 3, 2017.

  1. bobozzz

    bobozzz

    Joined:
    May 27, 2015
    Posts:
    38
    I am curious about how's _LightColor0.rgb generated by Unity, so I did some testing:

    I added my own light variables in the shader, DirLightColor and DirLightIntensity. I set them as the same value as the directional light I set up in the scene, and then switch between the light color I multiplied and LightColor0.rgb which is calculated by Unity. All the other stuff are exactly the same. I thought the result should be same.

    Light MainLight()
    {
    Light l;
    //l.color = _LightColor0.rgb;
    l.color = _DirLightColor.xyz * _DirLightIntensity;
    l.dir = _WorldSpaceLightPos0.xyz;
    return l;
    }


    The weird thing happened! The look is not the same. Especially when I increase both intensity to 8 (maximum), the LightColor0 will be so much brighter than 8, 8, 8 (I set both color to white).

    Then I am thinking if it's because Unity is mapping the value from (0 - 8) to (0 - 100), so I set light intensity (Unity) to 4 and DirLightIntensity (my test value) to 50. However, it's still different. It seems like Unity is doing some non-linear mapping? Or it's mapping to other value, not 100.

    After that I did some research on BRDF, and then I am thinking maybe it's because Unity multiplied PI (3.14) on the LightColor0. Then I change the shader to:

    l.color = _DirLightColor.xyz * _DirLightIntensity * PI;

    It's still not the same.

    So I wonder how does Unity calculate LightColor0?

    After artist set the light intensity value in the panel, will Unity multiply PI on this value before it's used in the lighting calculation?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,236
    If your _LightColor0 isn't matching, then you're likely using linear color space. In that case to replicate the value from script you would just need to do:
    Color lightColor = light.color * light.intensity;
    material.SetColor("_DirLightColor", lightColor);


    And that's it! However there's some magic in that SetColor function. Unity will automatically convert that gamma space color into linear space if needed. If you were to pass the color as a vector, skipping the "magic" of SetColor, you would need to do:
    Color lightColor = light.color * light.intensity;
    material.SetVector("_DirLightColor", lightColor.linear);


    The result of converting from gamma space to linear means a white light with an intensity of 8.0 will actually result in a value of 131.447, 131.447, 131.447 in the shader.

    Now if you're passing the color of the light as a color (using SetColor) straight with out multiplying it by intensity first, and the intensity as a float (using SetFloat) then to get the same result as _LightColor0 you'll need to convert the color back to gamma space in the shader, multiply it by the intensity value, and covert back to linear.

    Try this shader code:
    half3 gammaDirLightColor = LinearToGammaColor(_DirLightColor.rgb);
    half3 dirLightColor = GammaToLinearColor(_DirLightColor.rgb * _DirLightIntensity);


    That might not match perfectly still, since those conversion functions as written in UnityCG.cginc are approximations, matched to 0.0 to 1.0 ranges, not intended for ranges outside that.
     
    Last edited: May 3, 2017
    INeatFreak and bobozzz like this.
  3. bobozzz

    bobozzz

    Joined:
    May 27, 2015
    Posts:
    38
    Thank you for your reply. Yes I am using linear color space. I test and now it's all matched! So Unity inspector (color, intensity etc.) is in gamma color space!

    That also means, PI isn't multiplied on the light color.

    I read from this article:

    https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/#comments

    "This mean that for artists convenience, the value they enter as brightness in light’s settings is in fact the result of the light brightness multiply by (the energy conserving constant of Lambertian BRDF) . When artists put 1 in brightness, in reality they set a brightness of ."

    So this is not the case for Unity?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,236
    Here's a little snippet from UnityStandardBRDF.cginc
    Code (CSharp):
    1.     // Specular term
    2.     // HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm!
    3.     // BUT 1) that will make shader look significantly darker than Legacy ones
    4.     // and 2) on engine side "Non-important" lights have to be divided by Pi too in cases when they are injected into ambient SH
    And a little while later:
    Code (CSharp):
    1.     half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
     
  5. bobozzz

    bobozzz

    Joined:
    May 27, 2015
    Posts:
    38
    I saw the code in UnityStandardBRDF.cginc.

    I feel what it actually did here (not divide diffuseTerm by Pi and multiply PI on specularTerm) is theoretically correct.

    It exactly follows these equations:

    For diffuse term, PI cancel each other out, so not dividing diffuseTerm by PI is correct.

    For specular term, there is no PI in fspec to cancel out the PI, so we should multiply PI on specularTerm.

    What confused me is the way it explains in the comment (theoretically we should ... but ...) sounds like what it did is not theoretically correct, instead, "divide diffuseTerm by Pi and not multiply PI on specularTerm" is theoretically correct..

    Sorry for asking these trivial details. I am implementing some BRDFs by myself and this PI thing is really confusing me :confused:
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,236
    The whole Pi thing with lighting has always felt like mathematical machinations on finding the "one true representation" for something that is ultimately an arbitrary definition.

    Is a brightness of 1 mean a lambertian diffuse object has a peak brightness of "1", or does it mean a specular highlight is one in some very specific, and uncommon scenario? From an artists point of view my expectation is a lambertian diffuse and an arbitrary brightness of "1" should result in something fairly close to a diffuse with a peak brightness of "1" like it has for the last several decades of computer graphics, so I would argue Unity's implementation is the correct one and not the other way around.

    Again, "1" is an arbitrary value, and they're algebraically identical whether you divide diffuse by Pi or multiply specular by Pi. It might make more sense if you're entire pipeline is going fully physically based, but in that case you shouldn't be using arbitrary brightness values anyway and should be moving to real world lumens.
     
    wolfand13 likes this.
  7. bobozzz

    bobozzz

    Joined:
    May 27, 2015
    Posts:
    38
    I kind of understand what Unity wrote in the comment means. If I am understanding you correctly.

    So "divide diffuseTerm by Pi and not multiply PI on specularTerm" BRDF term is physically based, but that also means real world radiometric measured light intensity is expected. If we still use arbitrary brightness values, the result will be dark. That's why Unity pre-multiply Pi (which BRDF shouldn't have) in the BRDF to let artist directly use arbitrary brightness values in the inspector.
     
  8. MUGIK

    MUGIK

    Joined:
    Jul 2, 2015
    Posts:
    449
    What I like the most is the difference between URP and BiRP :)
    I love spending several hours on debugging to find out such Unity's secrets <3

    Code (CSharp):
    1. public static Color GetMainLightColorForShader(Light light, bool isUrp)
    2. {
    3.     var lightColor = light.color;
    4.    
    5.     if (light.useColorTemperature)
    6.     {
    7.         var kelvinColor = Mathf.CorrelatedColorTemperatureToRGB(light.colorTemperature);
    8.         lightColor *= kelvinColor;
    9.     }
    10.    
    11.     if (isUrp)
    12.     {
    13.         lightColor = QualitySettings.activeColorSpace == ColorSpace.Gamma ? lightColor : lightColor.linear;
    14.         lightColor *= light.intensity;
    15.     }
    16.     else
    17.     {
    18.         lightColor *= light.intensity;
    19.         lightColor = QualitySettings.activeColorSpace == ColorSpace.Gamma ? lightColor : lightColor.linear;
    20.     }
    21.     return lightColor;
    22. }