<p>Nice question! I hadn't seen the game myself but this question got me interested so I watched some footage on Youtube.</p>

<p>This one for example gives a good view of it: <a href="https://youtu.be/Ktjdg3zgRzk?t=15m46s" rel="nofollow noreferrer">https://youtu.be/Ktjdg3zgRzk?t=15m46s</a>
<a href="https://i.sstatic.net/hEUiR.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/hEUiR.png" alt="enter image description here"></a></p>

<p>A few things I notice:</p>

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

<p>From these observations, I think there are two effects used:</p>

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

<p>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.</p></li>
</ol>

<p>It's a pretty cool effect, thank you for point it out!</p>

CC BY-SA 3.0
2017-07-13T12:31:43.727Z
I thought of edge detection, but when observing the same effect ingame in the terrain (which has rather large triangles), it is possible to see the same transition between a "wide" rim and the constant-width as the camera turns to a narrower angle. Also, what kind of edge detection could be used here? I actually tried using Sobel on depth, but then I have no idea how to adjust the "width" without blurring internal lines. (Though I imagine this should be a separate question)
Kroltan
CC BY-SA 3.0
2017-07-13T12:58:54.767Z
They probably distinguish characters and terrain (I thought the effect was absent on terrain, but maybe I just missed it). For Sobel, just scale the kernel to the thickness you want: instead of comparing the.adjacent pixels, compare the ones a bit further.
Julien Guertault
CC BY-SA 3.0
2017-07-13T23:07:59.447Z
Hmm I'll take another dab at Sobel, and report back
Kroltan
CC BY-SA 3.0
2017-07-13T08:11:16.030Z
2017-07-13T08:11:16.030Z
Julien Guertault
<p>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.</p>

<p><a href="https://i.sstatic.net/8z0Ex.png" rel="noreferrer"><img src="https://i.sstatic.net/8z0Ex.png" alt="Annotated reference image"></a></p>

<p>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)</p>

<p>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. </p>

<p>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.</p>

<p><a href="https://i.sstatic.net/dnPyb.png" rel="noreferrer"><img src="https://i.sstatic.net/dnPyb.png" alt="Current output with annotated good/bads"></a></p>

<p>This is my current implementation for reference, as a Unity Surface Light Model:</p>

<pre><code>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);
}
</code></pre>

<p><a href="https://gist.github.com/kroltan/8eb8ba213dfeea14fde694c967630ae2" rel="noreferrer">Or check this Gist for the full shader file</a></p>

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

CC BY-SA 3.0
2017-07-14T01:16:29.610Z
Frankly, I like the look you've achieved better!
user1118321
CC BY-SA 3.0
2017-07-14T01:21:51.130Z
@user1118321 I would be satisfied too, if not for the fact that it silhouettes perfectly every minor problem with the models it is applied to.
Kroltan
<shader><lighting>
Constant screen-space-width rim shading
Kroltan
# Constant screen-space-width rim shading

(Asked by: username_0 on 2017-07-13)

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.

[![Annotated reference image](https://i.sstatic.net/8z0Ex.png)](https://i.sstatic.net/8z0Ex.png)

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.

[![Current output with annotated good/bads](https://i.sstatic.net/dnPyb.png)](https://i.sstatic.net/dnPyb.png)

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](https://gist.github.com/username_0/8eb8ba213dfeea14fde694c967630ae2)

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

**Tags:** shader, lighting

> **Comments:**
> **username_1**: Frankly, I like the look you've achieved better!  
> **username_0**: @username_1 I would be satisfied too, if not for the fact that it silhouettes perfectly every minor problem with the models it is applied to.  

---

## 1 answers

### Answer by username_2 on 2017-07-13. Score: 3

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>
[![enter image description here](https://i.sstatic.net/hEUiR.png)](https://i.sstatic.net/hEUiR.png)

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:

1. A diffuse shading with a sharp transition between lit a unlit, that happens somewhere below 0 so the light leaks behind a little.
2. 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!

> **Comments:**
> **username_0**: I thought of edge detection, but when observing the same effect ingame in the terrain (which has rather large triangles), it is possible to see the same transition between a "wide" rim and the constant-width as the camera turns to a narrower angle. Also, what kind of edge detection could be used here? I actually tried using Sobel on depth, but then I have no idea how to adjust the "width" without blurring internal lines. (Though I imagine this should be a separate question)  
> **username_2**: They probably distinguish characters and terrain (I thought the effect was absent on terrain, but maybe I just missed it). For Sobel, just scale the kernel to the thickness you want: instead of comparing the.adjacent pixels, compare the ones a bit further.  
> **username_0**: Hmm I'll take another dab at Sobel, and report back  

---

