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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Toon Shading Question

Discussion in 'Shaders' started by Allan-MacDonald, Jun 27, 2016.

  1. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    82
    Hey there, Artist here with no programming experience. I really like the look of the basic Unity Toon Shader, but I've got shadows in the environment that it doesn't take into account. Is there any way to do this with this Shader in particular? I've experimented with other Toon Shaders that have custom ramps but they just don't look quite as good as the one that uses the Cubemap.

    Any help would be much appreciated!

    Thanks
     
  2. mholub

    mholub

    Joined:
    Oct 3, 2012
    Posts:
    123
    If you create an example scene with two shaders (good one which has problem with shadows) and bad one in one scene, I'll try to look into it and understand if it's possible to fix shadow issue
     
  3. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    82
    Hey! Thanks for getting back to me. I can set that up, but it's not so much an error in the Unity Basic Toon shader it's just that it doesn't receive shadows at all, I was hoping there was an easy way to turn that on. I'd like it to use the cubemap it has by default but also receive shadows.
     
  4. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,469
    It would be easier to hijack an existing shader and modifying it to have the correct behavior. Generally shader do their stuff and send the result to a final gather, with one line of code you can pick that final gather and transform it into ramped toon lighting.

    That's all I can say now, sorry. I hope someone else pick from there !
     
  5. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    82
    Haha, thanks for the post neoshaman. I hear what you're saying, but I'm I can't code. This is the code for Unity's Basic Toon Shader that I like. Any idea what I would add to this so it'll receive shadows? Is this even possible?


    Code (CSharp):
    1.  
    2.  
    3. Shader "Toon/Basic" {
    4.     Properties {
    5.         _Color ("Main Color", Color) = (.5,.5,.5,1)
    6.         _MainTex ("Base (RGB)", 2D) = "white" {}
    7.         _ToonShade ("ToonShader Cubemap(RGB)", CUBE) = "" { }
    8.     }
    9.  
    10.  
    11.     SubShader {
    12.         Tags { "RenderType"="Opaque" }
    13.         Pass {
    14.             Name "BASE"
    15.             Cull Off
    16.          
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #pragma multi_compile_fog
    21.  
    22.             #include "UnityCG.cginc"
    23.  
    24.             sampler2D _MainTex;
    25.             samplerCUBE _ToonShade;
    26.             float4 _MainTex_ST;
    27.             float4 _Color;
    28.  
    29.             struct appdata {
    30.                 float4 vertex : POSITION;
    31.                 float2 texcoord : TEXCOORD0;
    32.                 float3 normal : NORMAL;
    33.             };
    34.          
    35.             struct v2f {
    36.                 float4 pos : SV_POSITION;
    37.                 float2 texcoord : TEXCOORD0;
    38.                 float3 cubenormal : TEXCOORD1;
    39.                 UNITY_FOG_COORDS(2)
    40.             };
    41.  
    42.             v2f vert (appdata v)
    43.             {
    44.                 v2f o;
    45.                 o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
    46.                 o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
    47.                 o.cubenormal = mul (UNITY_MATRIX_MV, float4(v.normal,0));
    48.                 UNITY_TRANSFER_FOG(o,o.pos);
    49.                 return o;
    50.             }
    51.  
    52.             fixed4 frag (v2f i) : SV_Target
    53.             {
    54.                 fixed4 col = _Color * tex2D(_MainTex, i.texcoord);
    55.                 fixed4 cube = texCUBE(_ToonShade, i.cubenormal);
    56.                 fixed4 c = fixed4(2.0f * cube.rgb * col.rgb, col.a);
    57.                 UNITY_APPLY_FOG(i.fogCoord, c);
    58.                 return c;
    59.             }
    60.             ENDCG        
    61.         }
    62.     }
    63.  
    64.     Fallback "VertexLit"
    65. }
     
    Last edited: Jun 28, 2016
  6. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,469
    I have never dabble with shadow directly that's the problem, I do have modified shader who receive shadow, so the idea was to start with a shader that receive shadow and modify it, now this one don't seem to have it so I a first glance I can't help.

    There is many varient of teh toon shader

    Use code tag to display code please :eek:

    What I can tell you is that the vert part is doing a bunch of conversion of reference I always forget which one but it's only a pick away from the doc :D The vert is basically doing some operation at EACH vertex.

    Then the GPU automatically interpolate those data across a triangle and for EACH pixel the frag part process its code.
    - The first line of the frag is multiplying the color field with the texture.
    - the second is using the normal at the pixel to sample the cubemap
    - the third multiply the sampled cubemap (from the second line) by the result of the first line and multiply that by 2 again, and put it in the final gather (c)
    - the fourth line apply fog to the final gather
    - the fifth simply return the result the final gather for displaying on the screen

    Observation.:
    - it has no concept of light, light direction or presence will do nothing, normally it shouldn't respond to ambient light either.
    - the light is entirely dependent on the cubemap and its orientation.
    - it doesnt react to normal map, there is no hook for it.
    - It's only toon shader if the cubemap is using a step drawing, it can be any image or lighting drawn on the cubemap.

    You should start with a shader that already have lighting and mix it with this one.
     
  7. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    82
    Apparently it's easier to work with a standard surface shader. So now I'm looking to add a cubemap input to this shader since it already has shadows.


    Code (CSharp):
    1. Shader "Sky/Toon/Shadows" {
    2.     Properties{
    3.         _Color("Main Color", Color) = (0.5,0.5,0.5,1)
    4.         _MainTex("Base (RGB)", 2D) = "white" {}
    5.     _Ramp("Toon Ramp (RGB)", 2D) = "grey" {}
    6.     }
    7.  
    8.         SubShader{
    9.         Tags{ "RenderType" = "Opaque" }
    10.         LOD 200
    11.         Lighting On
    12.         Cull off
    13.         CGPROGRAM
    14. #pragma surface surf ToonRamp
    15.  
    16.         sampler2D _Ramp;
    17.     // custom lighting function that uses a texture ramp based
    18.     // on angle between light direction and normal
    19. #pragma lighting ToonRamp exclude_path:prepass
    20.     inline half4 LightingToonRamp(SurfaceOutput s, half3 lightDir, half atten)
    21.     {
    22. #ifndef USING_DIRECTIONAL_LIGHT
    23.         lightDir = normalize(lightDir);
    24. #endif
    25.  
    26.         half d = (dot(s.Normal, lightDir)*0.5 + 0.5) * atten;
    27.         half3 ramp = tex2D(_Ramp, float2(d,d)).rgb;
    28.  
    29.         half4 c;
    30.         c.rgb = s.Albedo * _LightColor0.rgb * ramp * (atten * 2);
    31.         c.a = 0;
    32.         return c;
    33.     }
    34.  
    35.  
    36.     sampler2D _MainTex;
    37.     float4 _Color;
    38.  
    39.     struct Input {
    40.         float2 uv_MainTex : TEXCOORD0;
    41.         float3 cubenormal : TEXCOORD1;
    42.     };
    43.  
    44.     void surf(Input IN, inout SurfaceOutput o) {
    45.         half4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    46.         o.Albedo = c.rgb;
    47.         o.Alpha = c.a;
    48.     }
    49.     ENDCG
    50.  
    51.     }
    52.  
    53.         Fallback "Diffuse"
    54. }
     
  8. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    82
    Thanks Neoshaman, a friend mentioned that too. I'll look around!
     
  9. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,469
    Now Looking at that code you can see it has a custom lighting part, but there isn't a vert part. I don't remember how unity handle lack of vert part (I'm sure it's automated when not mentioned) So I need to check that, though just adding it shouldn't be a problem. It should be trivial to add normal map too.

    This shader work differently, it takes ONE light and use a texture to replace the light gradient by your own. So it beg the question what kind of rendering you really want! If I have full specification I can help you more.

    Now you must understand how light work, it takes the direction of the light relative to the point and then the normal of the surface, it does an operation (dot product) and return the value of light. Now this isn't a full shader, it's a surface shader, it mean unity already do a lot of stuff behind the curtain.

    Now I see the code I remember where the shadow are :D they are automatically put within the atten variable (which is the light attenuation). Attenuation store 2 things in a single value, it has the occlusion from light (shadow) and the distance attenuation (ie light is less bright the further away from the light point). Of course this shader only use directional light so it only has shadow. Given that it is a gradient, you might need to process this to have toon shadow on top.
     
  10. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    82
    I'm really quite out of my element with this stuff, I was hoping for an easy fix cause it's all over my head.

    Is it possible to make a toon shader with a cube map input and receive shadows?
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
  12. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,469
    Yes

    But there is many issue, it's never a quickfix lol

    Both shader use different coding paragdim so I can't easily port one to the other, without checking stuff in doc.

    The second one use surface shader (ie write to a surface structure) while the second is mostly a typical shader. The main problem is to document what's the equivalence to make modification. The main thing is to convert the first one to a surface shader, it will make everything simpler, however I don't know about the use of vert data necessary for environment within surface method :eek:

    I haven't that down by heart :oops:

    Okay I'll try something:
    1. I think I was wrong it's about the shader only receiving one light
    2. You can have a quick version by modifier the first shader
    a. add the to the pragma list

    Code (CSharp):
    1. #pragma lighting ToonRamp exclude_path:prepass

    then add

    Code (CSharp):
    1. inline half4 LightingToonRamp(SurfaceOutput s, half atten)
    2.     {
    3. #ifndef USING_DIRECTIONAL_LIGHT
    4.         lightDir = normalize(lightDir);
    5. #endif
    6.         half4 c;
    7.         c.rgb = s.Albedo * _LightColor0.rgb  * (atten * 2);
    8.         c.a = 0;
    9.         return c;
    10.     }
    11.  
     
  13. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,469
    Digging in the doc using @bgolus convenient link I found this
    http://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html
    which has an example of cubemap writing in surface shader, this should do to adapt the code :D

    Code (CSharp):
    1. Shader "Example/WorldRefl" {
    2.     Properties {
    3.       _MainTex ("Texture", 2D) = "white" {}
    4.       _Cube ("Cubemap", CUBE) = "" {}
    5.     }
    6.     SubShader {
    7.       Tags { "RenderType" = "Opaque" }
    8.       CGPROGRAM
    9.       #pragma surface surf Lambert
    10.       struct Input {
    11.           float2 uv_MainTex;
    12.           float3 worldRefl;
    13.       };
    14.       sampler2D _MainTex;
    15.       samplerCUBE _Cube;
    16.       void surf (Input IN, inout SurfaceOutput o) {
    17.           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * 0.5;
    18.           o.Emission = texCUBE (_Cube, IN.worldRefl).rgb;
    19.       }
    20.       ENDCG
    21.     }
    22.     Fallback "Diffuse"
    23.   }
    Which give us

    Code (CSharp):
    1. Shader "Example/Custom/Toon" {
    2.     Properties {
    3.       _MainTex ("Texture", 2D) = "white" {}
    4.       _Cube ("Cubemap", CUBE) = "" {}
    5.     }
    6.     SubShader {
    7.       Tags { "RenderType" = "Opaque" }
    8.       CGPROGRAM
    9.  
    10.       #pragma surface surf Lambert
    11.       #pragma lighting ToonRamp exclude_path:prepass
    12.       struct Input {
    13.           float2 uv_MainTex;
    14.           float3 worldRefl;
    15.       };
    16.       sampler2D _MainTex;
    17.       samplerCUBE _Cube;
    18.       void surf (Input IN, inout SurfaceOutput o) {
    19.           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * texCUBE (_Cube, IN.worldRefl).rgb * 2;
    20.       }
    21.  
    22. inline half4 LightingToonRamp(SurfaceOutput s, half atten)
    23.     {
    24. #ifndef USING_DIRECTIONAL_LIGHT
    25.         lightDir = normalize(lightDir);
    26. #endif
    27.         half4 c;
    28.         c.rgb = s.Albedo * _LightColor0.rgb  * (atten * 2);
    29.         c.a = 0;
    30.         return c;
    31.     }
    32.  
    33.  
    34.       ENDCG
    35.     }
    36.     Fallback "Diffuse"
    37.   }
    Try this and tell me any error
     
  14. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    82
    Hey, thanks for the help. I've tried it out, and it's saying "undeclared identifier 'lightDir'
     
  15. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    82
    Thanks for that, it's great. I've tried looking into some tutorials but they don't ease into it enough. I have plenty of 3D content creation experience but zero coding, so this is great. Thanks again.
     
  16. Allan-MacDonald

    Allan-MacDonald

    Joined:
    Sep 21, 2015
    Posts:
    82
  17. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,469
    Oh I made a mistake I haven't corrected

    I remove dir the first time but then kept it for having the light color You can change this part to

    Code (CSharp):
    1. inline half4 LightingToonRamp(SurfaceOutput s, half atten)
    2.     {
    3.         half4 c;
    4.         c.rgb = s.Albedo * _LightColor0.rgb  * (atten * 2);
    5.         c.a = 0;
    6.         return c;
    7.     }
    Tell me if there is any error left