Constant screen-space-width rim shading As of lately I've been studying a bit of shaders (in Unity), and am trying to recreate the "light-based inline" that can be observed in the latest Legend of Zelda. But maybe the task is a bit above my knowledge, for now.



As from what I can analyze, it seems to behave (when moving) similar to what a "rim shading" does, except it has a sharp cutoff (simple to do), and — the part I'm struggling with — it seems to have a constant width independent from the local curvature of the mesh and distance from camera, essentially having a constant "width" in relation to the screen. (well it actually varies a bit with camera distance, but mostly it doesn't so let's assume it doesn't)

It's a bit hard to show in a still, but you can find plenty videos on the game, and the effect is visible everywhere. 

I've managed to do the simpler part, the cut-off rim shading. But this gives the awkward effect that depending on camera orientation and the geometry curvature, the "inline" occupies more or less screen area. Below is an image of the current results I get on various camera angles, with green arrows showing good/acceptable areas and red arrows demonstrating deviation from the intended effect.



This is my current implementation for reference, as a Unity Surface Light Model:

half4 LightingToonPoster(SurfaceOutputStandard surf, float3 viewDir, UnityGI gi) {
    // Material properties
    half3 specular;
    half oneMinusReflectivity;
    DiffuseAndSpecularFromMetallic(surf.Albedo, surf.Metallic, /*out*/ specular, /*out*/ oneMinusReflectivity);
    float3 reflected = normalize(-reflect(gi.light.dir, surf.Normal));
    float specularity = dot(viewDir, reflected) * _Specularity;

    // Light setup
    float ndotl = dot(surf.Normal, gi.light.dir) * 0.5 + 0.5;
    float lightIncidence = gi.light.color * ndotl;
    float3 ramp = tex2D(_Ramp, lightIncidence.xx).rgb;
    float3 specularRamp = tex2D(_Ramp, specularity.xx).rgb;
    gi.light.color = ramp * _LightColor0.rgb;

    // Rim and camera-light convergence
    float rim = 1 - saturate(dot(surf.Normal, viewDir));
    float facing = -dot(viewDir, gi.light.dir) * 0.5 + 0.25;

    // sigmoid of rim for smooth falloff centered at _EdgeBias
    float outline = 1 / (1 + exp(-100 * (rim - _EdgeBias)));
    float highlight = _HighlightStrength * outline * facing * outline;

    float4 std = LightingStandard(surf, viewDir, gi);

    float3 toonDiffuse = surf.Albedo * (gi.light.color / 2 + ShadeSH9(float4(viewDir, 1)));
    float3 toonSpecular = specular * gi.indirect.specular * specularRamp;
    float4 toon = half4(toonDiffuse + toonSpecular, surf.Alpha);
    return lerp(std, toon, _UnshadeFactor) * half4(1 + highlight.xxx, 1);
}


Or check this Gist for the full shader file

The question now being, what can I use instead of rim-shading to obtain a similar effect, where the curvature is not taken into account and I get a constant "inset"?
Nice question! I hadn't seen the game myself but this question got me interested so I watched some footage on Youtube.

This one for example gives a good view of it: https://youtu.be/Ktjdg3zgRzk?t=15m46s


A few things I notice:


Hair and right leg: note how only the diffuse lit part has rim lighting.
Left wrist: note how the width becomes thinner at the angle with the hand.
On some other part you can also see the rim lighting is affected by cast shadows too. 


From these observations, I think there are two effects used:


A diffuse shading with a sharp transition between lit a unlit, that happens somewhere below 0 so the light leaks behind a little.
The rim lighting that, as you mention, has an almost constant screenspace width.

From the look of it and some of the artifacts, my best guess is that it's actually some edge detection filter based on depth, which is then used in combination with the diffuse lighting so the rim lighting doesn't affect the unlit parts.


It's a pretty cool effect, thank you for point it out!