Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Lightmapped Spiderweb shader without Lambert?

Discussion in 'Shaders' started by IgorAherne, Jul 10, 2017.

  1. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    Hello guys,

    Trying to implement a shader for spiderweb, the idea is that

    1)it is affected by distance to lights, their intensity *and* receives shadows
    2) but is not affected by Lambertian shading (Has to be without dot-product of Normal and direction to light)
    3) Bonus if works with Non-directional Lightmapping

    Was writing a fragment + vertex shader, but realised might be a hustle if we decide to go to deferred rendering pipelines - getting directions to many lights etc.
    So thinking that surface shaders will save me I've re-written the code. I hoped Smoothness would remove the Lambert dot-product, but it's still being applied :(

    Code (CSharp):
    1. Shader "Transparent/Spiderweb" {
    2.     Properties {
    3.       _MainTex ("Texture", 2D) = "white" {}
    4.       _Color("Tinting color", Color) = (1,1,1,1)
    5.     }
    6.  
    7.     SubShader {
    8.         Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
    9.         Cull Off
    10.  
    11.         CGPROGRAM
    12.         #pragma surface surf Standard alpha
    13.  
    14.         struct Input {
    15.             float2 uv_MainTex;
    16.         };
    17.  
    18.  
    19.         sampler2D _MainTex;
    20.         fixed4 _Color;
    21.  
    22.  
    23.         void surf (Input IN, inout SurfaceOutputStandard o) {
    24.             fixed4 col = tex2D (_MainTex, IN.uv_MainTex).rgba * _Color;
    25.             o.Albedo = col.rgba;
    26.             o.Alpha = col.a*2;
    27.             o.Smoothness = 1;
    28.         }
    29.         ENDCG
    30.     } //end Subshader
    31.     Fallback "Diffuse"
    32. }

    Anyone has ideas on how to disable that Lambert shading Unity is doing?
    If not, maybe we can always point normal to each light, as they are being computed? That way the dot-product would be 1 regardless of where the light is at


    Edit

    Just realized that the web is transparent, so even if we decide to go deferred pipeline, unity will force all transparent objects into forward lightlighting

    In that case, will try make the surface shader (else hard to get shadows + ambient working) and will try to modify the normal to point to each light. In fact, I only want 2 closest lights

    Will post result if succeeded!
     
    Last edited: Jul 10, 2017
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    You're going to be running into these two issues:
    • Transparent objects cannot receive real time shadows in Unity.
    • Lightmapped objects are always assumed to be Lambertian.
    Those are hard limitations with Unity's built in systems.

    There's a giant thread on the first issue I list. The second one it's possible I'm wrong about, but you should post in the Global Illumination subforum to find out for sure.

    Deferred vs Forward rendering paths behave identically for light mapping, but you are correct anything transparent will be forward rendered anyway. This is actually good for you because the only way to get non-Lambertian-ish diffuse is to use real time forward rendering (that is without modifying the deferred lighting system).

    To do what you want, you could modify the normal direction to always face the light, but you could also just use a vertex fragment shader and use the UNITY_LIGHT_ATTENUATION and light color and ignore the surface normal. Ambient lighting for non-lambertian gets a little funny since light probes also assume Lambert shading, so you will want to sample in at least 2 directions (like towards and away from the camera, or + and - normal direction), or even better if you do 4 (like using the normalized direction from the points of a tetrahedron).
     
    IgorAherne likes this.
  3. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    by the way lightprobes are quite simple to setup - there was a post about ShadeSH9, just ctrl+f it.

    Spent whole day trying to code up the shader, but it seems will have to go for something simpler


    Let's say we go vert & frag shader
    The issue boils down to unity trying all it can to hide getting poisition of light. On top of that it's seems common practice to use if-statement for checking whether the light is directional or point, and dynamic branching is bad as I know.
    Also, unity keeps one more in it's hand, - attenuation and range of light is hard to get, resulting in severe posts

    Let's say we go with surf shader
    Unity will do all it can to not give you position of its light,
    - well, in my case I don't need it right? All I need is to align my normal to light, for example using WorldSpaceLightDir()
    ...well, that's only possible if we specify "LightMode"="ForwardAdd"
    -fair enough we do so, and then run into a lovely issue where the pass is never executed, - only ForwardBase works))
    Changing Graphics settings from Deferred to Forward doesn't work either.

    Well, maybe there is something else to find?
    Like this presentation but I failed to grasp it's gist. Once we extract all the needed code (structs, surf function and vert + frag functions) the code seems to hick up on the frag_surf LightingLambert (o, lightDir, LIGHT_ATTENUATION(IN));

    I get why unity is not giving light position, - it want's to save me from shooting myself in the leg, and inforce correct usage of pipeline. But I f*cking hate having to be dragged through these semantics, thanks @Unity!
    A simple non-lambert spiderweb attenuated shader...
     
    Last edited: Jul 11, 2017
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    For forward rendering, in both the base and add passes, the light position or direction is _WorldSpaceLightPos0. Base pass is always a directional light. Add pass can be a directional, point, or spot, with w == 0 for directional. You can also use #ifdef USING_DIRECTIONAL_LIGHT if you want to avoid the branch which modern shaders use.

    Code (CSharp):
    1. #ifndef USING_DIRECTIONAL_LIGHT
    2.     fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
    3. #else
    4.     fixed3 lightDir = _WorldSpaceLightPos0.xyz;
    5. #endif
    Eh, not really. There's a lot of stigma against if statements, but they're not as terrible as people think. Even on old hardware it isn't really a bad thing to use, as long as you understand what it is actually doing. Also in case you're unaware, #if and if are very different. Using #if is essentially free, they're preprocessor macros. Unity compiles multiple shader variants with different keywords set and the resulting shader uses only the code that passes those #if tests. Using if will be a branch, but not necessarily dynamic. In this case it's a static branch as the value being tested is set as a uniform, and this too is almost free.

    It's likely only possible if using the forward base or forward add passes the Surface shader generates, it won't work for other passes. The problem with surface shaders is the surf function is called by all passes, so if it's using code that's not supported in those passes it might fail. Unity has some useful defines for Surface shaders to help with that though.

    #if defined(UNITY_PASS_FORWARDBASE)
    float3 lightDir = _WorldSpaceLightPos0.xyz;
    #elif defined(UNITY_PASS_FORWARDADD)
    float3 lightDir = WorldSpaceLightDir(IN.worldPos); // or use the above code if you're going to have more than one directional light
    #else
    float3 lightDir = float3(0,1,0);
    #endif


    That document is writing out a vertex fragment shader using the code a Unity 3.0 Surface Shader would generate. Won't work for Unity 5.0, and is also rather extraneous as stuff like the frag_surf() function doesn't need to exist for a custom shader.

    Here's a minimal shader with support for real time lights with attenuation, but no shadows or Lambert shading.
    Code (CSharp):
    1. Shader "Custom/SpiderWeb"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.     }
    7.     SubShader
    8.     {
    9.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    10.         LOD 100
    11.         Cull Off
    12.  
    13.         Pass
    14.         {
    15.             Tags { "LightMode"="ForwardBase" }
    16.             ZWrite Off
    17.             Blend SrcAlpha OneMinusSrcAlpha
    18.  
    19.             CGPROGRAM
    20.             #pragma vertex vert
    21.             #pragma fragment frag
    22.  
    23.             #pragma multi_compile_fwdbase
    24.             #pragma multi_compile_fog
    25.      
    26.             #include "UnityCG.cginc"
    27.             #include "Lighting.cginc"
    28.             #include "AutoLight.cginc"
    29.  
    30.             struct appdata
    31.             {
    32.                 float4 vertex : POSITION;
    33.                 float2 uv : TEXCOORD0;
    34.             };
    35.  
    36.             struct v2f
    37.             {
    38.                 float4 pos : SV_POSITION;
    39.                 float2 uv : TEXCOORD0;
    40.                 float3 worldPos : TEXCOORD1;
    41.                 UNITY_FOG_COORDS(2)
    42.                 SHADOW_COORDS(3)
    43.             };
    44.  
    45.             sampler2D _MainTex;
    46.             float4 _MainTex_ST;
    47.      
    48.             v2f vert (appdata v)
    49.             {
    50.                 v2f o;
    51.                 o.pos = UnityObjectToClipPos(v.vertex);
    52.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    53.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    54.                 UNITY_TRANSFER_FOG(o,o.vertex);
    55.                 TRANSFER_SHADOW(o)
    56.                 return o;
    57.             }
    58.      
    59.             fixed4 frag (v2f i) : SV_Target
    60.             {
    61.                 fixed4 col = tex2D(_MainTex, i.uv);
    62.  
    63.                 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
    64.  
    65.                 half3 lighting = _LightColor0.rgb * atten;
    66.                 half3 ambient = ShadeSH9(half4(0,1,0,1));
    67.  
    68.                 col.rgb *= lighting + ambient;
    69.  
    70.                 UNITY_APPLY_FOG(i.fogCoord, col);
    71.                 return col;
    72.             }
    73.             ENDCG
    74.         }
    75.  
    76.         Pass
    77.         {
    78.             Tags { "LightMode"="ForwardAdd" }
    79.             ZWrite Off
    80.             Blend One One
    81.  
    82.             CGPROGRAM
    83.             #pragma vertex vert
    84.             #pragma fragment frag
    85.      
    86.             #pragma multi_compile_fwdadd
    87.             #pragma multi_compile_fog
    88.      
    89.             #include "UnityCG.cginc"
    90.             #include "Lighting.cginc"
    91.             #include "AutoLight.cginc"
    92.  
    93.             struct appdata
    94.             {
    95.                 float4 vertex : POSITION;
    96.                 float2 uv : TEXCOORD0;
    97.             };
    98.  
    99.             struct v2f
    100.             {
    101.                 float4 pos : SV_POSITION;
    102.                 float2 uv : TEXCOORD0;
    103.                 float3 worldPos : TEXCOORD1;
    104.                 UNITY_FOG_COORDS(2)
    105.                 SHADOW_COORDS(3)
    106.             };
    107.  
    108.             sampler2D _MainTex;
    109.             float4 _MainTex_ST;
    110.      
    111.             v2f vert (appdata v)
    112.             {
    113.                 v2f o;
    114.                 o.pos = UnityObjectToClipPos(v.vertex);
    115.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    116.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    117.                 UNITY_TRANSFER_FOG(o,o.vertex);
    118.                 TRANSFER_SHADOW(o)
    119.                 return o;
    120.             }
    121.      
    122.             fixed4 frag (v2f i) : SV_Target
    123.             {
    124.                 fixed4 col = tex2D(_MainTex, i.uv);
    125.  
    126.                 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
    127.  
    128.                 half3 lighting = _LightColor0.rgb * atten;
    129.  
    130.                 col.rgb *= lighting;
    131.  
    132.                 UNITY_APPLY_FOG_COLOR(i.fogCoord, col, fixed4(0,0,0,0));
    133.                 return col * col.a;;
    134.             }
    135.             ENDCG
    136.         }
    137.     }
    138. }
    Supporting light maps, especially with the multitude of mixed options, is a bit more work than that, but it would be lambert shaded too.

    edit: Note I'm also being lazy skipping a little bit of code for proper shadow support since it's a transparent shader and Unity doesn't support shadows. The UNITY_LIGHT_ATTENUATION macros would normally also handle shadows, but I'm not passing the SHADOW_COORDS(#) in the v2f and TRANSFER_SHADOW(o) in the vert shader, so copying this code into an opaque shader will error. I say I'm being lazy as the those macros are blank if no shadows are enabled. Un-lazified the shader so the editor will stop complaining about shader errors if you use that.
     
    Last edited: Jul 11, 2017
  5. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    Thank you, didn't know that!

    Shader code still doesn't render the point light, but renders if it's directional (so ForwardAdd is never executed)

    https://gyazo.com/b36750131c727c0dfb395c89db11109c

    Following your advice about sampling light-probes in a tetrahedron-shape, I computed coordinates to 4 vertices of tetrahedron. They are already normalised by the way, one points up, the other point its three legs towards bottom:

    Code (CSharp):
    1. half4 right = half4(0.8165, -0.3312, 0.4729,  0); //w is 0
    2. half4 left  = half4(-0.8165, 0.3312, 0.4729,   0);
    3. half4 back = half4(0, -0.3377,  -0.9413,   0);
    4. half4 up = half4(0, 0, 1, 0);
    So with these four we could change
    Code (CSharp):
    1. half3 ambient = ShadeSH9(half4(0,1,0,1));
    to
    Code (CSharp):
    1. half3 ambient = ShadeSH9(right) + ShadeSH9(left) + ShadeSH9(back) + ShadeSH9(up);
    2. ambient *= 0.25; //one multiplication at the end, for faster execution.
    3.  

    declaring it in uniform seems a better idea though, saves four half4's declared for each vertex.

    By the way in ShadeSH9 you've specified 1 for w. I think it should be 0, because we are dealing with direction, although it might not matter, - can't remember straight away
     
    Last edited: Jul 13, 2017
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    I tested this using point lights and even spot lights. Check to make sure your Quality Settings doesn't have Pixel Light Count set to 1?

    Meh? Technically true, but it's unlikely that's going to be a performance factor. If you want you could define a few Vector properties with the [HideInInspector] attribute like:

    [HideInInspector] _TetraRight ("Right", Vector) = (0.8165, -0.3312, 0.4729, 1)
    etc.

    That lets you define uniforms and be sure they'll stay set.

    Otherwise that looks right. You could play with using ambient *= 0.5 instead of ambient *= 0.25 or have that be adjustable ... makes no physical sense but may look better for something like a cobweb which tends to be "bright".

    The xyz are indeed a normalized direction, but that function isn't a matrix transform, it's a specially encoded spherical harmonic, so it should be 1. A copypasta comment from Unity's UnityCG.cginc:

    // normal should be normalized, w=1.0
    // output in active color space
    half3 ShadeSH9 (half4 normal)
     
    IgorAherne likes this.
  7. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    Indeed, PixelLight was set to 0, - after changing it in ProjectSettings->Quality to 1 it started working (that is, "ForwardAdd" started to work)

    Thanks!

    Once again, here is the combined code
    Code (CSharp):
    1. Shader "Custom/SpiderWeb"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _Brightness("Brightness", Float) = 2.5
    7.         [HideInInspector] _TetraRight("Right", Vector) = (0.8165, -0.3312, 0.4729, 1)
    8.         [HideInInspector] _TetraLeft("Right", Vector) = (-0.8165, 0.3312, 0.4729, 1)
    9.         [HideInInspector] _TetraBack("Right", Vector) = (0, -0.3377, -0.9413, 1)
    10.         [HideInInspector] _TetraUp("Right", Vector) = (0, 0, 1, 1)
    11.     }
    12.     SubShader
    13.     {
    14.         Tags { "Queue"="Transparent" "RenderType"="Transparent" }
    15.         LOD 100
    16.         Cull Off
    17.         Pass
    18.         {
    19.             Tags { "LightMode"="ForwardBase" }
    20.             ZWrite Off
    21.             Blend SrcAlpha OneMinusSrcAlpha
    22.             CGPROGRAM
    23.             #pragma vertex vert
    24.             #pragma fragment frag
    25.             #pragma multi_compile_fwdbase
    26.             #pragma multi_compile_fog
    27.    
    28.             #include "UnityCG.cginc"
    29.             #include "Lighting.cginc"
    30.             #include "AutoLight.cginc"
    31.             struct appdata
    32.             {
    33.                 float4 vertex : POSITION;
    34.                 float2 uv : TEXCOORD0;
    35.             };
    36.             struct v2f
    37.             {
    38.                 float4 pos : SV_POSITION;
    39.                 float2 uv : TEXCOORD0;
    40.                 float3 worldPos : TEXCOORD1;
    41.                 UNITY_FOG_COORDS(2)
    42.                 SHADOW_COORDS(3)
    43.             };
    44.             sampler2D _MainTex;
    45.             float4 _MainTex_ST;
    46.  
    47.             half4 _TetraRigh;
    48.             half4 _TetraLeft;
    49.             half4 _TetraBack;
    50.             half4 _TetraUp;
    51.             half _Brightness;
    52.    
    53.             v2f vert (appdata v)
    54.             {
    55.                 v2f o;
    56.                 o.pos = UnityObjectToClipPos(v.vertex);
    57.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    58.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    59.                 UNITY_TRANSFER_FOG(o,o.vertex);
    60.                 TRANSFER_SHADOW(o)
    61.                 return o;
    62.             }
    63.    
    64.             fixed4 frag (v2f i) : SV_Target
    65.             {
    66.                 fixed4 col = tex2D(_MainTex, i.uv);
    67.                 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
    68.                 half3 lighting = _LightColor0.rgb * atten;
    69.  
    70.                 half3 ambient = ShadeSH9(_TetraRigh)  +  ShadeSH9(_TetraLeft)  +  ShadeSH9(_TetraBack)  +  ShadeSH9(_TetraUp);
    71.                 ambient *= 0.25*_Brightness; //one multiplication at the end, for faster execution.
    72.                 col.rgb *= lighting + ambient;
    73.                 UNITY_APPLY_FOG(i.fogCoord, col);
    74.                 return col;
    75.             }
    76.             ENDCG
    77.         }
    78.         Pass
    79.         {
    80.             Tags { "LightMode"="ForwardAdd" }
    81.             ZWrite Off
    82.             Blend One One
    83.             CGPROGRAM
    84.             #pragma vertex vert
    85.             #pragma fragment frag
    86.    
    87.             #pragma multi_compile_fwdadd
    88.             #pragma multi_compile_fog
    89.    
    90.             #include "UnityCG.cginc"
    91.             #include "Lighting.cginc"
    92.             #include "AutoLight.cginc"
    93.             struct appdata
    94.             {
    95.                 float4 vertex : POSITION;
    96.                 float2 uv : TEXCOORD0;
    97.             };
    98.             struct v2f
    99.             {
    100.                 float4 pos : SV_POSITION;
    101.                 float2 uv : TEXCOORD0;
    102.                 float3 worldPos : TEXCOORD1;
    103.                 UNITY_FOG_COORDS(2)
    104.                 SHADOW_COORDS(3)
    105.             };
    106.             sampler2D _MainTex;
    107.             float4 _MainTex_ST;
    108.    
    109.             v2f vert (appdata v)
    110.             {
    111.                 v2f o;
    112.                 o.pos = UnityObjectToClipPos(v.vertex);
    113.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    114.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    115.                 UNITY_TRANSFER_FOG(o,o.vertex);
    116.                 TRANSFER_SHADOW(o)
    117.                 return o;
    118.             }
    119.    
    120.             fixed4 frag (v2f i) : SV_Target
    121.             {
    122.                 fixed4 col = tex2D(_MainTex, i.uv);
    123.                 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos)
    124.                 half3 lighting = _LightColor0.rgb * atten;
    125.                 col.rgb *= lighting;
    126.                 UNITY_APPLY_FOG_COLOR(i.fogCoord, col, fixed4(0,0,0,0));
    127.                 return col * col.a;;
    128.             }
    129.             ENDCG
    130.         }//end Pass
    131.     }
    132. }

    Thank you for your help!