Search Unity

Point Light in V-F shader

Discussion in 'Shaders' started by ifurkend, Oct 11, 2017.

  1. ifurkend

    ifurkend

    Joined:
    Sep 4, 2012
    Posts:
    350
    I attempt to write a V-F shader for receiving point light without calculating vertex normal. The resultant color on the mesh changes smoothly per point light intensity, until I change the point light range or their positions. The problem is better demonstrated in the video.



    The left sphere uses built-in diffuse shader; the middle particle system and the right sphere use my own shader.

    Apparently once the point light range hits the mesh's or particle system's bounding boxes, they are instantly lit. I thought the absence of normal were the culprit, so I tried Shade4PointLights from UnityCG.cginc instead, but the result isn't better either. I tried moving the point light function from vertex shader to fragment shader, but no luck.

    Code (CSharp):
    1. Shader "Particles/Receive Point Light" {
    2.     Properties {
    3.         _TintColor ("Tint Color", Color) = (1,1,1,1)
    4.         _MainTex ("Texture", 2D) = "white" {}
    5.         _AmbientPow ("Ambient Power", Range(0,1)) = 0.5 // Can be used in HDR effect. Intensity greater than 2 causes glitch in HDR rendering.
    6.         _Glow ("Intensity", Range(0, 127)) = 1
    7.     }
    8.     SubShader {
    9.         Tags {"LightMode" = "ForwardBase" "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Opaque" }
    10.         LOD 100
    11.         Cull Back
    12.         ZWrite Off
    13.         Lighting On
    14.         //Blend SrcAlpha OneMinusSrcAlpha
    15.  
    16.         Pass {
    17.         CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #include "UnityCG.cginc"
    21.             #include "UnityLightingCommon.cginc"
    22.             #include "UnityShaderVariables.cginc"
    23.             #include "AutoLight.cginc"
    24.             #include "UnityDeferredLibrary.cginc"
    25.  
    26.             half4 _TintColor;
    27.             sampler2D _MainTex;
    28.             half4 _MainTex_ST;
    29.             half _AmbientPow;
    30.             half _Glow;
    31.  
    32.             struct vertIn {
    33.                 float4 pos : POSITION;
    34.                 float4 normal : NORMAL;
    35.                 half2 uv : TEXCOORD0;
    36.                 fixed4 color : COLOR;
    37.             };
    38.  
    39.             struct v2f {
    40.                 float4 pos : SV_POSITION;
    41.                 float4 normal : NORMAL;
    42.                 half2 uv : TEXCOORD0;
    43.                 fixed4 color : COLOR;
    44.                 half4 worldPos : TEXCOORD1;
    45.             };
    46.  
    47.             v2f vert (vertIn v) {
    48.                 v2f o;
    49.                 o.pos = UnityObjectToClipPos(v.pos);
    50.                 o.uv = TRANSFORM_TEX(v.uv,_MainTex);
    51.                 half3 worldNormal = UnityObjectToWorldNormal(v.normal);
    52.                 o.normal = v.normal;
    53.                 o.color = v.color * _TintColor;
    54.                 o.color.rgb += _Glow + ShadeSH9(half4(worldNormal,1)) * _AmbientPow;
    55.                 o.worldPos = mul(unity_ObjectToWorld, v.pos);
    56.                
    57.                 o.color.rgb += unity_LightColor[0].rgb * (1 / distance(float3(unity_4LightPosX0.x, unity_4LightPosY0.x, unity_4LightPosZ0.x), o.worldPos.xyz)) * (1 / unity_4LightAtten0.x);
    58.                 /*
    59.                 o.color.rgb += Shade4PointLights(
    60.                     unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
    61.                     unity_LightColor[0].rgb, unity_LightColor[1].rgb,
    62.                     unity_LightColor[2].rgb, unity_LightColor[3].rgb,
    63.                     unity_4LightAtten0, o.worldPos, v.normal
    64.                 );
    65.                 */
    66.                 return o;
    67.             }
    68.            
    69.             fixed4 frag (v2f f) : SV_Target {
    70.                 fixed4 col = tex2D(_MainTex, f.uv) * f.color;
    71.                 //col.rgb += unity_LightColor[0].rgb * (1 / distance(float3(unity_4LightPosX0.x, unity_4LightPosY0.x, unity_4LightPosZ0.x), f.worldPos.xyz)) * (1 / unity_4LightAtten0.x);
    72.                 /*
    73.                 col.rgb += Shade4PointLights(
    74.                     unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
    75.                     unity_LightColor[0].rgb, unity_LightColor[1].rgb,
    76.                     unity_LightColor[2].rgb, unity_LightColor[3].rgb,
    77.                     unity_4LightAtten0, f.worldPos, f.normal
    78.                 );
    79.                 */
    80.                 return col;
    81.             }
    82.         ENDCG
    83.         }
    84.     }
    85. }
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Yep, this is working as intended.

    The attenuation function used by these lights is different than the one used by the ForwardAdd pass, and has an infinite range. The "LightAtten" value is some value they've calculated to approximate the fall off so the brightness is similar within the actual range of the light. I also seem to remember in the past the light's brightness and falloff where packed into the single attenuation value, with very bright lights not actually being any brighter, just getting a larger range. Luckily today the light's brightness is not part of the atten value.

    Because this has bothered me too, I believe you can extract the range with this:

    float range = (0.005 * sqrt(1000000.0 - unity_4LightAtten0.x)) / sqrt(unity_4LightAtten0.x);
     
    pasxoshorp13 and ifurkend like this.
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    float range = (0.005 * sqrt(1000000 - unity_4LightAtten0.x)) / sqrt(unity_4LightAtten0.x);
    float attenUV = distance(float3(unity_4LightPosX0.x, unity_4LightPosY0.x, unity_4LightPosZ0.x), f.worldPos.xyz) / range;
    float atten = tex2D(_LightTextureB0, (attenUV * attenUV).xx).UNITY_ATTEN_CHANNEL;


    That appears to match Unity's internal lighting atten calculations exactly. That's a lot of sqrts to do though for each light, but might not be too bad if you do the range and "attenUV" calculations in the vertex shader. You can also skip the texture sample with this:

    float atten = saturate(1.0 / (1.0 + 25.0*attenUV*attenUV) * saturate((1 - attenUV) * 5.0));

    It's not a perfect match due to the original being texture based, but you'll be hard pressed to notice a difference in use.
     
    pasxoshorp13, cecarlsen and ifurkend like this.
  4. ifurkend

    ifurkend

    Joined:
    Sep 4, 2012
    Posts:
    350
    Both of your atten formulas work like a charm. I realized the built-in point light range is intentionally large to take vertex normal into consideration, I just had no idea to fake my own point light range.

     
    Last edited: Oct 12, 2017
    astracat111 likes this.
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    For per vertex lighting having a softer tail on the attenuation can certainly be beneficial. The obvious side effect is that sudden "entirely lit" issue.
     
    astracat111 likes this.
  6. ifurkend

    ifurkend

    Joined:
    Sep 4, 2012
    Posts:
    350
    Now that I have added all 4 point lights to the shader, there is a glitch when more than 1 non-important point light is ligliting the object. I guess there is no foolproof way to fix it than changing all point lights to important or switching to deferred...

    Code (CSharp):
    1. Shader "Particles/Alpha Blended Point Lights" {
    2.     Properties {
    3.         _TintColor ("Tint Color", Color) = (1,1,1,1)
    4.         _MainTex ("Texture", 2D) = "white" {}
    5.         _AmbientPow ("Ambient Power", Range(0,1)) = 0.5 // Can be used in HDR effect. Intensity greater than 2 causes glitch in HDR rendering.
    6.         _Glow ("Intensity", Range(0, 127)) = 1
    7.     }
    8.     SubShader {
    9.         Tags {"LightMode" = "ForwardBase" "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
    10.         LOD 100
    11.         Cull Back
    12.         ZWrite Off
    13.         Lighting On
    14.         Blend SrcAlpha OneMinusSrcAlpha
    15.  
    16.         Pass {
    17.         CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #include "UnityCG.cginc"
    21.             #pragma multi_compile_instancing
    22.  
    23.             half4 _TintColor;
    24.             sampler2D _MainTex;
    25.             half4 _MainTex_ST;
    26.             half _AmbientPow;
    27.             half _Glow;
    28.  
    29.             struct vertIn {
    30.                 float4 pos : POSITION;
    31.                 float4 normal : NORMAL;
    32.                 half2 uv : TEXCOORD0;
    33.                 fixed4 color : COLOR;
    34.             };
    35.  
    36.             struct v2f {
    37.                 float4 pos : SV_POSITION;
    38.                 //float4 normal : NORMAL;
    39.                 half2 uv : TEXCOORD0;
    40.                 fixed4 color : COLOR;
    41.                 half4 worldPos : TEXCOORD1;
    42.                 half4 attenUV : TEXCOORD2;
    43.             };
    44.          
    45.             float attenUV (float lightAtten0, float3 _4LightPos, float3 _worldPos) : SV_Target {
    46.                 float range = (0.005 * sqrt(1000000 - lightAtten0)) / sqrt(lightAtten0);
    47.                 return distance(_4LightPos, _worldPos) / range;
    48.             }
    49.          
    50.             float atten (float _attenUV) : SV_Target {
    51.                 return saturate(1.0 / (1.0 + 25.0*_attenUV*_attenUV) * saturate((1 - _attenUV) * 5.0));
    52.             }
    53.          
    54.             float attenTex (sampler2D _LightTextureB, float _attenUV) : SV_Target {
    55.                 return tex2D(_LightTextureB, (_attenUV * _attenUV).xx).UNITY_ATTEN_CHANNEL;
    56.             }
    57.          
    58.             v2f vert (vertIn v) {
    59.                 v2f o;
    60.                 o.pos = UnityObjectToClipPos(v.pos);
    61.                 o.uv.xy = TRANSFORM_TEX(v.uv,_MainTex);
    62.                 o.color = v.color * _TintColor;
    63.                 half3 worldNormal = UnityObjectToWorldNormal(v.normal);
    64.                 o.color.rgb += _Glow + ShadeSH9(half4(worldNormal,1)) * _AmbientPow;
    65.              
    66.                 o.worldPos = mul(unity_ObjectToWorld, v.pos);
    67.              
    68.                 o.attenUV.x = attenUV(unity_4LightAtten0.x, float3(unity_4LightPosX0.x, unity_4LightPosY0.x, unity_4LightPosZ0.x), o.worldPos.xyz);
    69.                 o.attenUV.y = attenUV(unity_4LightAtten0.y, float3(unity_4LightPosX0.y, unity_4LightPosY0.y, unity_4LightPosZ0.y), o.worldPos.xyz);
    70.                 o.attenUV.z = attenUV(unity_4LightAtten0.z, float3(unity_4LightPosX0.z, unity_4LightPosY0.z, unity_4LightPosZ0.z), o.worldPos.xyz);
    71.                 o.attenUV.w = attenUV(unity_4LightAtten0.w, float3(unity_4LightPosX0.w, unity_4LightPosY0.w, unity_4LightPosZ0.w), o.worldPos.xyz);
    72.              
    73.                 //o.color.rgb += _LightColor;//Deferred
    74.              
    75.                 /*
    76.                 o.normal = v.normal;
    77.                 o.color.rgb += Shade4PointLights(
    78.                     unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
    79.                     unity_LightColor[0].rgb, unity_LightColor[1].rgb,
    80.                     unity_LightColor[2].rgb, unity_LightColor[3].rgb,
    81.                     unity_4LightAtten0, o.worldPos, v.normal
    82.                 );
    83.                 */
    84.              
    85.                 return o;
    86.             }
    87.          
    88.             fixed4 frag (v2f f) : SV_Target {
    89.                 float4 _atten;
    90.                 //float _atten.x = attenTex(_LightTextureB0, f.attenUV.x);
    91.                 _atten.x = atten(f.attenUV.x);
    92.                 _atten.y = atten(f.attenUV.y);
    93.                 _atten.z = atten(f.attenUV.z);
    94.                 _atten.w = atten(f.attenUV.w);
    95.              
    96.                 fixed4 col = tex2D(_MainTex, f.uv.xy) * f.color;
    97.                 col.rgb += unity_LightColor[0].rgb * (1 / distance(float3(unity_4LightPosX0.x, unity_4LightPosY0.x, unity_4LightPosZ0.x), f.worldPos.xyz)) * _atten.x;
    98.                 col.rgb += unity_LightColor[1].rgb * (1 / distance(float3(unity_4LightPosX0.y, unity_4LightPosY0.y, unity_4LightPosZ0.y), f.worldPos.xyz)) * _atten.y;
    99.                 col.rgb += unity_LightColor[2].rgb * (1 / distance(float3(unity_4LightPosX0.z, unity_4LightPosY0.z, unity_4LightPosZ0.z), f.worldPos.xyz)) * _atten.z;
    100.                 col.rgb += unity_LightColor[3].rgb * (1 / distance(float3(unity_4LightPosX0.w, unity_4LightPosY0.w, unity_4LightPosZ0.w), f.worldPos.xyz)) * _atten.w;
    101.              
    102.                 return col;
    103.             }
    104.         ENDCG
    105.         }
    106.     }
    107. }
     
    astracat111 and Squall19 like this.
  7. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    I still found this old thread useful today!

    The attenuation formula posted works great, but is for one light only.

    In my use case I needed a replacement for the builtin Shade4PointLights function, with corrected attenuation. I managed to make that work so the function is here in case anyone else who finds this thread (like I did recently) might find it useful.

    Code (csharp):
    1.             float3 Shade4PointLightsCustom (
    2.                 float4 lightPosX, float4 lightPosY, float4 lightPosZ,
    3.                 float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,
    4.                 float4 lightAttenSq,
    5.                 float3 pos, float3 normal)
    6.             {
    7.                 // to light vectors
    8.                 float4 toLightX = lightPosX - pos.x;
    9.                 float4 toLightY = lightPosY - pos.y;
    10.                 float4 toLightZ = lightPosZ - pos.z;
    11.                 // squared lengths
    12.                 float4 lengthSq = 0;
    13.                 lengthSq += toLightX * toLightX;
    14.                 lengthSq += toLightY * toLightY;
    15.                 lengthSq += toLightZ * toLightZ;
    16.                 // don't produce NaNs if some vertex position overlaps with the light
    17.                 lengthSq = max(lengthSq, 0.000001);
    18.  
    19.                 // NdotL
    20.                 float4 ndotl = 0;
    21.                 ndotl += toLightX * normal.x;
    22.                 ndotl += toLightY * normal.y;
    23.                 ndotl += toLightZ * normal.z;
    24.                 // correct NdotL
    25.                 float4 corr = rsqrt(lengthSq);
    26.  
    27.                 ndotl = max (float4(0,0,0,0), ndotl * corr);
    28.  
    29.                 // attenuation
    30.                 // THIS PART IS ALSO MODIFIED
    31.                 //float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
    32.                 // More correct attenuation derived from
    33.                 // https://forum.unity.com/threads/point-light-in-v-f-shader.499717/#post-3250460
    34.                 float4 range = (0.005 * sqrt(1000000 - unity_4LightAtten0)) / sqrt(unity_4LightAtten0);
    35.                 float4 attenUV = sqrt(lengthSq) / range;
    36.                 float4 atten = saturate(1.0 / (1.0 + 25.0 * attenUV * attenUV) * saturate((1 - attenUV) * 5.0));
    37.  
    38.                 float4 diff = ndotl * atten;
    39.                 // final color
    40.                 float3 col = 0;
    41.                 col += lightColor0 * diff.x;
    42.                 col += lightColor1 * diff.y;
    43.                 col += lightColor2 * diff.z;
    44.                 col += lightColor3 * diff.w;
    45.                 return col;
    46.             }
     
    SourceMobai, astracat111 and Rockaso like this.
  8. Rockaso

    Rockaso

    Joined:
    Oct 31, 2016
    Posts:
    85
    Hey thanks a lot!
    Your code was really helpful,
     
    astracat111 likes this.
  9. astracat111

    astracat111

    Joined:
    Sep 21, 2016
    Posts:
    725
    upload_2022-12-15_22-28-4.png

    You guys are absolute geniuses! Thanks, got it working over there!
     

    Attached Files:

  10. Pema-Malling

    Pema-Malling

    Unity Technologies

    Joined:
    Jul 3, 2020
    Posts:
    324
    Hello. Here is an exact formula to calculate the range from attenuation:

    Code (CSharp):
    1. float range = 5.0 * (1.0 / sqrt(unity_4LightAtten0.x));
    It's very close the formula @bgolus posted, but they differ a bit close to 0 range.

    upload_2023-6-2_15-59-48.png
    This graph shows 3 plots. The blue and black plot show a roundtrip of `atten_to_range(range_to_atten(range))`, which should just simplify to `range`. The blue plot uses the formula posted above, the black plot is the one posted by bgolus. The red plot is the error value of bgolus' approximation. This probably doesn't matter much in practice since for light ranges this small, the difference is going to be quite negligible.

    A recent bug investigation had me looking into this area, so I figured I'd post this in case it is useful to anyone.
     
    z3y and bgolus like this.