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"?