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

Question URP Shader Graph: Cel-shading + received shadows... is it possible?

Discussion in 'Shader Graph' started by msyoung2012, Mar 30, 2021.

  1. msyoung2012

    msyoung2012

    Joined:
    Mar 20, 2014
    Posts:
    49
    I've got a great toon shader working that performs similar to the style seen in the Wind Waker (the gamecube version, on the left below). One thing that WW does really well in HD (on the right) is factoring in volumetric shadows/lights. A great example is seen in the vid here as Link moves from sun to the shaded areas. Screenshot below too.

    Screen Shot 2021-03-30 at 12.03.04 AM.png

    Notice that in the comparison, Link moves from being colored Sun/Shade to Shade/Shade as he enters the shadowed region. The cast shadow from the island seems to be shading him exactly the same color as the shadow color of his cel-shading in all the regions of his model that it touches.

    I tried to replicate this with a lit shader and a hard-shadow generating directional light, but the shadows cast by the directional light in my world were much too bright (even at max shadow intensity). They basically look like a transparent light gray vs. the darker cel-shade color. I'm wondering if there's some way to factor in these received shadows in my shader graph and shade my models to get the same effect as shown on the right in the video/pic. Or, anyone know a better strategy to achieve this? :)

    I've tried to use the shadow attenuation variable to get the shadows cast on the object and then multiplying it with my computed "shade" value in the shader graph, but I don't think that's how that value works - in any case, it didn't seem like any of the received shadows were factoring in. I'm using 2021.1.0f1.
     
  2. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,295
    I am not sure if I understood what you want, but it sounds like normal toon shader. There is texture for character in light and you lerp this color to darker one according to the shadow attenuation. The importnant part is to use some texture or gradient as ramp for attenuation - many basic things are described here https://blogs.unity3d.com/2019/07/31/custom-lighting-in-shader-graph-expanding-your-graphs-in-2019/.

    If you want both main light and shadows to work you must multiply them together, this will enforce darker color when character is in shadow of other object. Let me know if I understood your question wrong.
     
  3. msyoung2012

    msyoung2012

    Joined:
    Mar 20, 2014
    Posts:
    49

    Thanks for the reply. I have a working toon shader already - basically, my game performs just like the image on the left. I want to factor in the shadows cast on objects now.

    Are you saying there is a way to access the received shadows (cast by other game objects) in the shader graph? Sorry, I am still new to the shader graph and I'm not sure how to access those values.
     
  4. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,295
    If you have got ready to use asset (toon shader), then I am unable to say how or is it possible to do that.
    In case you developed your own shader, you can just follow what is said in article in the previous post.

    Very simplified: To determine brightness you need to calculate dot product between surface normal and light direction, result is number between -1 and 1 and most of the time you will need to clamp it to 0-1.
    This value does not take into account other objects that might cast shadows. To do that you need to sample shadowmap and multiply both together. This way it does not matter if object faces the sun, if its in shadow it will be dark anyways.

    Assuming you are on URP, If this is what you are missing you should just read the article.
    My personal toon shader uses custom node with very similar content to the one in the post:
    Code (CSharp):
    1. void MainLight_half(float3 WorldPos, out half3 Direction, out half3 Color, out half DistanceAtten, out half ShadowAtten)
    2. {
    3.     #ifdef SHADERGRAPH_PREVIEW
    4.         Direction = half3(0.5, 0.5, 0);
    5.         Color = 1;
    6.         DistanceAtten = 1;
    7.         ShadowAtten = 1;
    8.     #else
    9.         #if SHADOWS_SCREEN
    10.             half4 clipPos = TransformWorldToHClip(WorldPos);
    11.             half4 shadowCoord = ComputeScreenPos(clipPos);
    12.         #else
    13.             half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
    14.         #endif
    15.             Light mainLight = GetMainLight(shadowCoord);
    16.             Direction = mainLight.direction;
    17.             Color = mainLight.color;
    18.             DistanceAtten = mainLight.distanceAttenuation;
    19.  
    20.         #if !defined(_MAIN_LIGHT_SHADOWS) || defined(_RECEIVE_SHADOWS_OFF)
    21.             ShadowAtten = 1.0h;
    22.         #endif
    23.  
    24.         #if SHADOWS_SCREEN
    25.             ShadowAtten = SampleScreenSpaceShadowmap(shadowCoord);
    26.         #else
    27.             ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
    28.             half shadowStrength = GetMainLightShadowStrength();
    29.             ShadowAtten = SampleShadowmap(shadowCoord, TEXTURE2D_ARGS(_MainLightShadowmapTexture,
    30.             sampler_MainLightShadowmapTexture),
    31.             shadowSamplingData, shadowStrength, false);
    32.         #endif
    33.     #endif
    34. }
    You can see there is output param ShadowAtten - this is shadow casted by other objects.
    If you have unlit graph you might need some keywords, this might be usefull for you.
     
    ColtPtrHun and WalterPalladino like this.