Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Official Outline Effect

Discussion in 'Open Projects' started by MrFlyingChip, Sep 30, 2020.

  1. MrFlyingChip

    MrFlyingChip

    Joined:
    Dec 5, 2016
    Posts:
    7
    Hey there,
    I want to make the outline effect system which will allow getting the needed renderer to have the outline and enable it with needed settings. Also, I can add two types of outline: the first one will not be seen through the walls, and another one will. Here's the link to the card: https://open.codecks.io/unity-open-project-1/decks/16/card/12e-outline-effect
    (Tell me if there are some other requirements)
     
    Franc3spo and tantx like this.
  2. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    How would you implement it? I know a classic way to do it is to render the geometry with inverted normals. Some people call it the "inverted hull" technique.
    (this tutorial does it: https://ronja-tutorials.tumblr.com/post/176120178562/multipass-shaders-inverted-hull-outlines )

    However, that technique has limitations and it would require us to author all of our meshes without sharp corners, or the technique breaks.

    However it can also go really well, like in this case: https://twitter.com/ironicaccount/status/1244621413425659906 but again, the mesh needs to be made on purpose...
     
  3. MrFlyingChip

    MrFlyingChip

    Joined:
    Dec 5, 2016
    Posts:
    7
    I'm going to use something similar like the "inverted hull" method but I'll move the vertex position among the normal not in the objects space but in the screen space coords. Also, I'll not use culling at all. Instead, I'm going to use another pass for writing into the stencil buffer which will hide things that we don't need. Here's the example:
    upload_2020-9-30_21-1-39.png
     
    morepixels likes this.
  4. MrFlyingChip

    MrFlyingChip

    Joined:
    Dec 5, 2016
    Posts:
    7
    Also, forgot to mention that it works in the URP.
     
    cirocontinisio likes this.
  5. Tibor0991

    Tibor0991

    Joined:
    Dec 11, 2016
    Posts:
    27
    @cirocontinisio I can take on the card if you wish, I have tested a method based on inverted hulls that doesn't break on sharp edges, but takes up the vertex color channel of the mesh. It works on URP, uses stencils to avoid outline cluttering/ugly self-intersections and it can also be used for per-object selection outlines (that is, non-black outlines).

    EDIT: Here's a proof: the model is completely hard-edged, but the outline still holds with no breaks. outline example.png
     
    Last edited: Sep 30, 2020
  6. vx4

    vx4

    Joined:
    Dec 11, 2012
    Posts:
    181
    So you to going to draw mesh twice.


    First time draw mesh to set stencil buffet to one .
    Second draw mesh with shift vertex position along normal (pos+=normal *offest )and set stencil test to pass in case is equal 0 only.
     
    qball13z likes this.
  7. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Not sure what's the difference between the two shaders, but a couple of questions:
    A) We plan to use Shader Graph for the toon shader. Is this outline going to play nice with that?
    B) I don't see these outlines producing any inside lines, which is a feature I'd like to have. i.e. having an outline when depth or normals change, but the outline is "inside" the mesh itself.

    You can see this behaviour in the outline solution that's in the repo right now on the toon-shading branch:

    Outline.jpg

    Bonus points if the outline is done IN ShaderGraph :)
     
  8. Tibor0991

    Tibor0991

    Joined:
    Dec 11, 2016
    Posts:
    27
    My solution is done in ShaderGraph, and if I disable the stencil pass it should also do outlines inside; the only "problem" is baking the vertex colors on the mesh: I have used Blender for my first tests but I'm pretty sure that such operation can be automated within Unity with an editor script.
    On a final note, I might also try to improve the outline shader in order to give it the ability to render imperfect/uneven strokes.
     
    cirocontinisio likes this.
  9. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Why do you want to bake the colours on the mesh?
     
  10. Tibor0991

    Tibor0991

    Joined:
    Dec 11, 2016
    Posts:
    27
    Because if the mesh has any flat face or sharp edge the outline hull will break, in order to sidestep this issue I bake the smoothly interpolated vertex normals in the vertex color channel and then plug that value instead of the mesh vertex normals in the outline inflation step.
    I'll post more info about my workflow later, it's 3 AM here.
     
    Gosciu and FaffyWaffles like this.
  11. AndreFMMecheto

    AndreFMMecheto

    Joined:
    Mar 23, 2017
    Posts:
    12
    This is actually genius
     
    FaffyWaffles likes this.
  12. MrFlyingChip

    MrFlyingChip

    Joined:
    Dec 5, 2016
    Posts:
    7
    Sorry but I prefer using hlsl instead of the Shader Graph. Also, I need some matrix multiplications in the shader and I don't know if Shader Graph can do it. My method doesn't need baking the mesh colors and yeah, I'm going to draw the mesh twice.
     
  13. MrFlyingChip

    MrFlyingChip

    Joined:
    Dec 5, 2016
    Posts:
    7
    Also, I've looked at your implementation of the outline and it's a screen space effect. So, the vertex moving outline never will look like the SS outline because it doesn't know about other vertices.
     
  14. fabiencollet

    fabiencollet

    Joined:
    Feb 1, 2020
    Posts:
    2
    Hello all,
    I'm Fabien, 3D artist very interesting by npr rendering since few of years.

    Using a thickness map could be usefull multiply with the global outline width.

    Documentation on the subject :
    https://graphics.pixar.com/library/ToonRendering/paper.pdf

    I will try in my side with the shader graph to test something but i'm really not sure to suceed.

    I did a test few years ago (without outline in this case) but to get an acceptable result it will be important to break edges of all (lights, shadows, colors, outlines) with a distortion and a little noise to get a more drawing look.

    Test toon shader and post processing distortion:


    Hope i will could help on this topics or in another.
    Cheers
     
  15. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    I am making this thread the official one for the Outline Effect (card on the roadmap).

    Thanks for the ongoing discussion, this is great!
     
    FaffyWaffles and Proddan_FR like this.
  16. Proddan_FR

    Proddan_FR

    Joined:
    Jun 3, 2018
    Posts:
    2
    According to me, we should use post processing to provide an easy parametric Outline Effect, .


    Maybe, we should do a poll to choice define guideline between Hlsl or ShaderGraph
     
  17. MrFlyingChip

    MrFlyingChip

    Joined:
    Dec 5, 2016
    Posts:
    7
    Shader graph uses hlsl files under the hood I think
     
  18. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Yes, but more importantly you can embed HLSL code into Custom Shader Graph nodes, which is how we're doing the toon shading in the project.
     
    FaffyWaffles likes this.
  19. Tibor0991

    Tibor0991

    Joined:
    Dec 11, 2016
    Posts:
    27
    I have toyed today with my outline shader and found some issues: while it works great for outer outlines, it doesn't appear as good as I thought on inner lines.

    What about using CommandBuffers instead to issue specific variations on each renderer? If I understood correctly, the reason you don't want to go full screen with a global filter is because you want to be able to set a specific outline width for each outlined object, and only for those object that must be outlined.
    I wonder, would it be possible to use the Graphics.Blit function, together with the method explained in https://alexanderameye.github.io/outlineshader to draw the outlines on top of the object by doing the calculations in a smaller area? I mean, the trick here would be blitting smaller screenspace squares which hold a single object to outline rather than processing the whole screen.
     
    daneobyrd likes this.
  20. MrFlyingChip

    MrFlyingChip

    Joined:
    Dec 5, 2016
    Posts:
    7
    Possibly you can do a special pass for the outlined objects which will do several blits. Something like that:
    1. Run a pass with the object shader but using depth normal pass.
    2. Store them into the render texture.
    3. Run a full-screen outline effect for this particular object sending the render texture into the shader.
    4. Discard the pixel if the depth == 0. (it will save some performance).

    It can work but need to check the performance issues that can possibly be. (but it's not a mobile game so it might not be an issue)
     
    FaffyWaffles, daneobyrd and Tibor0991 like this.
  21. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,492
    Subscribing
     
  22. Tibor0991

    Tibor0991

    Joined:
    Dec 11, 2016
    Posts:
    27
    Small update: I am halfway through my "thick strokes" shader, which is supposed to handle both in- and outlines; I'm just having a few issue wrapping my head around the RenderFeature/RenderPass process, I hope to have something ready for the next week.
    BTW, it will work on a per-object basis even though it's rendered full-screen.
     
    valentingurkov likes this.
  23. KatawareDoki

    KatawareDoki

    Joined:
    May 23, 2018
    Posts:
    9
    In standart assets we have an outline effect material and shader, it can helps you to recreate one.
     
  24. KatawareDoki

    KatawareDoki

    Joined:
    May 23, 2018
    Posts:
    9
    As a reference i think u need to see an old game XIII
     
  25. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    FYI, I did a bit of work and created a new test scene for the Outline in a new branch (https://github.com/UnityTechnologies/open-project-1/tree/custom-lighting-shader), feel free to check it out to see what we want to achieve with this shader.

    And by the way, I'm pretty happy with the result now.

    This is an ok way to do it, but you can't get inside lines with this method.
     
    vx4 likes this.
  26. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Reviving this thread since I just posted the toon shader on the art-assets branch. It includes the outline effect as a pass based on depth and normals texture.
    https://github.com/UnityTechnologies/open-project-1/tree/art-assets

    You can see it in action here:
    View attachment 723505

    and in other images here: https://forum.unity.com/threads/regarding-art-assets-and-music.980892/#post-6455752

    As with all things here, it can evolve. Feel free to give suggestions on how to improve it!
    I feel like it nees a bit of work to make it look "The Best"®.
     
  27. vx4

    vx4

    Joined:
    Dec 11, 2012
    Posts:
    181
    I will try do implement outline effect as following(need further optimization):

    Render outline object in another RT(R16F|,init :0) where output determine outline thickness(outline thickness can be change per object or per pixel).
    calc edge based on depth and normal texture(use same OutlineObject_float).
    Blur both thickness and edge texture.
    to increases pixel coverage
    Downsample thickness texture(4x to 16x) .
    Upsampling thickness texture(4x to 16x).​
    Combine both edge and thickness texture (ex: edge*thickness).
    Blend Combine texture with render texture of Scene.​
    hopefully I will able to have result .
     
    Last edited: Dec 31, 2020
  28. valentingurkov

    valentingurkov

    Joined:
    Jun 17, 2019
    Posts:
    13
    I just want to ask, will the outline apply only together with the toon shader, or you are exploring the idea of having some outline effect when hovering over other NPCs / objects? As a not-so-experienced developer, I'm going through the project's repository and learning a lot of stuff. Thanks to everyone contributing.
     
    cirocontinisio likes this.
  29. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    What do you mean by hovering? The game doesn't use a mouse pointer.
    In any case, let's say when you're close to an interactable object? Not sure. I think we should try and see if we like it.

    The outline can be tweaked by a script, it's a property of the shader so it's easy to access the Material on an object and change the value via script, to create a "selection outline effect".

    Glad you're enjoying it!!

    I'd love to see an alternate implementation of it, snice the one we currently have has limitations (no anti-aliasing, etc.).
    However, please keep in mind the requirements: needs to work with URP, needs to be integrated (somehow) with Shader Graph, and added on top of individual objects.
     
    vx4 and valentingurkov like this.
  30. r3ndesigner

    r3ndesigner

    Joined:
    Mar 21, 2013
    Posts:
    143
    Found this
    maybe can help in some way ^^
     
    cirocontinisio likes this.
  31. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Thanks! That's a very popular trick and it would produce anti-aliased outline, but unfortunately that method doesn't allow the geometry to display lines "inside" or on top of it. It just creates an outline outside, almost as if it was a layer behind.

    That's why we went with the more complex route of using a Depth and Normals texture to detect where there are interesting changes in the geometry, which them makes the outline resolution-dependent.
    (for a longer explanation)
     
  32. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Hey @r3ndesigner, since the shader is not from Open Projects and also this discussion is related to the Outline effect specifically, can we bring it to somewhere more appropriate? Can you do me a favour and post in this subforum https://forum.unity.com/forums/shader-graph.346/ dedicated to Shader Graph? Feel free to tag me, and also post the source where you got the shader (I assume it's my Github, but just to confirm which shader we're talking about).
     
  33. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    Hello folks.

    I have tried to have a look at several outline implementations that I have found over internet and I tried to implement this one which seemed according to me promising (https://www.videopoetics.com/tutorials/pixel-perfect-outline-shaders-unity/).This solution could be considered as a biased inverted hull technique to improve the rendering of the outline thickness of this classic method.

    upload_2020-12-7_0-53-57.png
    Here is the result in motion:


    This technique requires a standard unlit shader with a second pass. To enable it on the object I am adding a second material to the mesh renderer:

    upload_2020-12-6_23-53-45.png

    What do you think about this approach? It seems to solve all the problems we have listed so far (screen dependent, distance to the object dependence, double thickness when overlapping, black surface on a flat surface when camera is gliding over it)

    Note: Another advantage of the hull technique, it does not eat the color of our character for small part like the hands and the arms that are turning black quickly when the camera is moving a bit far from him.
     
    Last edited: Dec 6, 2020
    MattDavis, yyylny, De-Panther and 2 others like this.
  34. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Eh, I know it solves some issues, but it removes the nice "inside lines".

    Also... it does add, surprisingly, some weird artifacts? I didn't expect that. Where are they coming from?

    upload_2020-12-7_11-55-39.png upload_2020-12-7_11-56-15.png

    Very strange.

    That's a good point. What if, actually, we also push out the vertices where we have the outline? That would require a small modification to the shader, but might cure the outline "eating out into the shape".

    Also, we could mitigate the outline to be distance-based. We could use the two existing per-Material tolerances (depth and normals) and multiply them to a factor based on distance. This would make the outline get thinner and fade away in the distance.
     
    daneobyrd likes this.
  35. canchen_unity

    canchen_unity

    Joined:
    Mar 13, 2018
    Posts:
    12
    upload_2020-12-9_14-18-20.png @cirocontinisio Can you be more specific about what you mean by the "inside line"? It seems to me treivize's solution is producing the inside lines effect you showed in your previous screenshot.
     

    Attached Files:

  36. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    @canchen_unity, indeed this technique is able to draw some inner lines, but as spotted by @cirocontinisio, some of them are not fully drawn because the lines are made visible due to the hull mesh facing their back face to the camera, in my implementation, the technique is enhanced by generating the hull with a scaling along the normal but only in the camera xy plan to control more precisely the thickness of the outline. it works very well on outer lines and some inner lines, but some are not due to the initial geometry topology and the geometry resolution.

    First the topology, concave area will not generate outline even with sharp angles (big normal vector changes), think about a slightly conic glass seen from the top, the outer rim will be displayed but the inner one not, because the hull of the inner part of the glass will always show the front faces which are invisible.

    Secondly regarding mesh resolution, the vertex are displaced with a biased method, so if the vertex are not always close to the where the outline should be displayed, the space between the root geometry and the hull can vary and make the outline thinner or even not visible...

    The strange behaviors spotted by @cirocontinisio are caused by a combination of these limitations.

    I am still looking at this problem and how to improve the outline effect either using another technique or improving the current one ;)
     
  37. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    Question: could we do a full screen outline effect that utilizes distance and the DepthNormals texture with a mask over areas that will not receive an outline? I recently saw Harry Heath's outline shader that uses an additional texture as an alpha mask to create a sizzle effect to animate the lines. It is easier to understand when looking at the visuals. It seems that this blits one texture to another.

    This allows for inside lines as well as control over what receives an outline. I don't know if it is possible but I was thinking that in place of using the camera's color texture, we could substitute in the DepthNormals texture. If there are certain objects we want to receive an outline that are futher away then we could somehow have them write to the screen-space mask texture as white?

    I think there is something here we can learn from - I'm just not sure how much. Instead of deciding which objects are outlined we decide which objects are not outlined.

    I know our requirements are:
    1. Control over which objects (what parts of the screen) receive an outline
    2. Control over depth sensitivity and normals sensitivity
    3. No outlines at the border between different colors on the same object
    Links:
    Some images from outline effect mini-thread (Final, RGB, R, G, B)
    Em4SQ_4VkAEoPT_.png Em4RRDkVgAEMelE.png Em4RRhBVQAUzQdE.png Em4RSJ2UYAAGVll.png Em4RSlvUwAINoSN.png
    • Pastebin
      Code (CSharp):
      1. using System.Collections;
      2. using System.Collections.Generic;
      3. using UnityEngine;
      4. using UnityEngine.Rendering;
      5. using UnityEngine.Rendering.Universal;
      6.  
      7. namespace HarryH.Outline
      8. {
      9.     [System.Serializable]
      10.     public class OutlineSettings
      11.     {
      12.         [SerializeField]
      13.         public BlitSettings blitSettings;
      14.  
      15.         [Tooltip("Object Threshold.")]
      16.         public float outerThreshold = 0.0f;
      17.         [Tooltip("Inner Threshold.")]
      18.         public float innerThreshold = 0.0f;
      19.  
      20.         [Tooltip("Outline size X.")]
      21.         public float outlineSizeX = 1.0f;
      22.         [Tooltip("Outline size Y.")]
      23.         public float outlineSizeY = 1.0f;
      24.  
      25.         [Tooltip("Rotations.")]
      26.         public int rotations = 8;
      27.         [Tooltip("Depth Push.")]
      28.         public float depthPush = 1e-6f;
      29.  
      30.         [Tooltip("Object LUT.")]
      31.         public Texture2D outerLUT;
      32.         [Tooltip("Inner LUT.")]
      33.         public Texture2D innerLUT;
      34.     }
      35.  
      36.     public class OutlineFeature : ScriptableRendererFeature
      37.     {
      38.  
      39.         ShaderPassToTextureRenderer outlinePassColorTexture;
      40.         ShaderPassToTextureRenderer outlinePassDepthTexture;
      41.         ShaderPassToTextureRenderer outlineTransperantTexture;
      42.  
      43.         ShaderPassToTextureRenderer outlineDirectTexture;
      44.  
      45.         FullscreenQuadRenderer outlinePass;
      46.         FullscreenQuadRenderer colorBlitPass;
      47.         FullscreenQuadRenderer depthBlitPass;
      48.  
      49.  
      50.         public OutlineSettings settings = new OutlineSettings();
      51.  
      52.         Material outlineEncoderMaterial;
      53.         private string outlineEncoderName = "Hidden/Harryh___h/Outline Post Process";
      54.  
      55.         public override void Create()
      56.         {
      57.  
      58.             outlinePassColorTexture = new ShaderPassToTextureRenderer("Outline Pass Color", "Outline", RenderQueueRange.opaque, "_HHO_OutlineTexture", RenderPassEvent.AfterRenderingOpaques, true, 24, RenderTextureFormat.ARGBFloat);
      59.             outlinePassDepthTexture = new ShaderPassToTextureRenderer("Outline Pass Depth", "OutlineDepth", RenderQueueRange.opaque, "_HHO_OutlineDepthTexture", RenderPassEvent.AfterRenderingOpaques, true, 24, RenderTextureFormat.Depth);
      60.             outlineTransperantTexture = new ShaderPassToTextureRenderer("Outline Pass Transperant", "Outline", RenderQueueRange.transparent, "_HHO_OutlineTexture", RenderPassEvent.AfterRenderingOpaques, false, 24, RenderTextureFormat.ARGBFloat);
      61.  
      62.             outlineDirectTexture = new ShaderPassToTextureRenderer("Outline Direct", "OutlineDirect", RenderQueueRange.opaque, "_HHO_Pass", RenderPassEvent.BeforeRenderingTransparents, false, 24, RenderTextureFormat.ARGBFloat);
      63.  
      64.             outlinePass = new FullscreenQuadRenderer("Outline Encoder");
      65.             colorBlitPass = new FullscreenQuadRenderer("Outline Color Blit");
      66.             depthBlitPass = new FullscreenQuadRenderer("Outline Depth Blit");
      67.  
      68.             GetMaterial();
      69.         }
      70.  
      71.         public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
      72.         {
      73.             if (!GetMaterial())
      74.             {
      75.                 Debug.LogErrorFormat(
      76.                     "{0}.AddRenderPasses(): Missing material. Make sure to add a blit material, or make sure {1} exists.",
      77.                     GetType().Name, outlineEncoderName);
      78.                 return;
      79.             }
      80.  
      81.             Shader.SetGlobalFloat("_HHO_OuterThreshold", settings.outerThreshold);
      82.             Shader.SetGlobalFloat("_HHO_InnerThreshold", settings.innerThreshold);
      83.             Shader.SetGlobalFloat("_HHO_OutlineSizeX", settings.outlineSizeX);
      84.             Shader.SetGlobalFloat("_HHO_OutlineSizeY", settings.outlineSizeY);
      85.             Shader.SetGlobalInt("_HHO_Rotations", settings.rotations);
      86.             Shader.SetGlobalFloat("_HHO_DepthPush", settings.depthPush);
      87.             Shader.SetGlobalTexture("_HHO_OuterLUT", settings.outerLUT);
      88.             Shader.SetGlobalTexture("_HHO_InnerLUT", settings.innerLUT);
      89.  
      90.             settings.blitSettings.Update();
      91.  
      92.             renderer.EnqueuePass(outlinePassColorTexture);
      93.             renderer.EnqueuePass(outlinePassDepthTexture);
      94.             renderer.EnqueuePass(outlineTransperantTexture);
      95.  
      96.             outlinePass.Init(outlineEncoderMaterial, "_HHO_Pass", true);
      97.             renderer.EnqueuePass(outlinePass);
      98.  
      99.             renderer.EnqueuePass(outlineDirectTexture);
      100.  
      101.             colorBlitPass.Init(settings.blitSettings.blitMaterial, renderer, false);
      102.             renderer.EnqueuePass(colorBlitPass);
      103.  
      104.             depthBlitPass.Init(settings.blitSettings.blitMaterial, "_CameraDepthTexture", false);
      105.             renderer.EnqueuePass(depthBlitPass);
      106.         }
      107.  
      108.         private bool GetMaterial()
      109.         {
      110.             if (outlineEncoderMaterial && settings.blitSettings && settings.blitSettings.blitMaterial)
      111.             {
      112.                 return true;
      113.             }
      114.  
      115.             Shader outlineEncoderShader = Shader.Find(outlineEncoderName);
      116.             if (outlineEncoderShader != null && settings.blitSettings && settings.blitSettings.blitMaterial)
      117.             {
      118.                 outlineEncoderMaterial = new Material(outlineEncoderShader);
      119.                 return true;
      120.             }
      121.             return false;
      122.         }
      123.  
      124.  
      125.     }
      126.  
      127. }
     
    Last edited: Dec 10, 2020
  38. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    Edit: I thought of this as a way to achieve variable outline thickness on one object. However, the more I think about it, it makes more sense for outline thickness on any given object to be calculated using depth data. This was an interesting idea, whether it ends up being of any use is another story.

    Original post:
    Additionally I had a thought that we could prevent outlines from "eating into" the mesh (thick outlines covering areas with an edge close to another edge, such as hands) by drawing a line, perpendicular to the outline, inward until it hits another section of the outline. Apply this to all areas to be outlined until there are no new directions for lines to be drawn. Consider the length (magnitude) of all lines that hit this point (or possibly collinear section) of the outline. When I say collinear section I mean how the outline on the ear can be separated into four straight lines.

    You then consider the length of the line perpendicular. We could test applying this to only normal based outlines or both.

    Note: For "another idea" I mean evaluate the scene depth to the next object behind pig chef at any point where the outline would be applied. In addition to evaluating the distance from the camera to Pig Chef, you evaluate the distance from Pig Chef to the next object along the camera direction vector. We then consider both of these values in controlling outline thickness.

    These are just ideas - I haven't tested them out.

    outline idea.png
     
    Last edited: Dec 12, 2020
  39. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    Hi @daneobyrd, thanks for having shared some new sources of inspiration!
    I am not sure to understand your last comment approach :S

    I have the feeling Harry technique uses a good amount of hand made texture tuning to produce a good looking from a specific camera point of view (the grey part to avoid drawing the inner lines for instance)

    Regarding the outline "doubling" for inner part or overlapping dynamic object, the problem comes from the fact that the outline is half of its thickness on the border. Half is projected on the model and the other half on what is next to it (in front or behind it). If this other element is an outlined model the missing half part appears on it, otherwise it is just discarded.

    Actually, I am thinking now about using Harry Heath technique where he draws each object under a specific float. We could maybe having a renderer feature where we have the thickness of the outline encoded in one channel. Then we could read it in the current outline HLSL code and determine first if all the sampling points used to compute the depth/normal differences ahave the same thickness, if all the points are the same then we divide the thickness by two and perform the sampling for normal/depth with half thickness.

    Maybe we could also use this info to improve the outline drawing: if one of the point of the sampling is a different thickness (dynamic object over/behind a decor), just draw an outline. Maybe we should balance out it based on the depth data to avoid drawing outline because a static object is in front of view...

    What do you think about it?
     
    Last edited: Dec 10, 2020
  40. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    That sounds really promising! Another idea is to set the inside thickness to a constant value (or maximum value), and base only the outside thickness on depth data? I think this is all worth testing
     
  41. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    I'm going to try to explain this idea better.

    This is just me trying to better communicate the initial ideas - not necessarily advocating for these methods over others we are discussing.

    On my reply with the diagram, I didn’t fully think through it but I had two goals.
    1. Prevent the outline eating into more intricate parts of the model. (I may have misunderstand what caused this when I originally wrote this).
    2. When objects next to the outline are further away, the outline will be thicker.
      Let’s say we have a side view of Pig chef standing on top of a rock (static object). The outline between sky and pig chef’s head would be thicker than the outline between the ground and pig chef’s feet.
    Overly detailed explanation on the first idea:
    Scale the thickness of the outline based on whether a sampled point was outlining a more intricate section of the character (where the outline is wrapped more closely [around smaller sections like fingers]). You could also describe this as outlines on the edge of the character’s silhouette would be thicker than others.

    Using my poorly drawn diagram:
    I was going to draw a perpendicular line from a sampled point (B) on the outline and extend it until it hit another point on the outline (C). I would consider the length of this line (BC) as a value representing whether point B was in a intricate section of the model, this would impact thickness.

    Then I thought that lines perpendicular to other sampled points could terminate at point B (see line AB). I only thought about this second line because I was worried that sampling point B’s distance (from other parts of the outline) in a single direction might not be enough information.
     
    Last edited: Dec 12, 2020
  42. dotsquid

    dotsquid

    Joined:
    Aug 11, 2016
    Posts:
    224
    Did anyone consider to use this amazing technique - Jump Flood Algorithm? (it's described in the second part of the article)
     
  43. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    I have been able to test my approach to get rid of the doubling of the outline based on a new render pass. I did not manage to render exactly what I wanted (the thickness of each object in a new render pass through the SRP) but I am just computing a mask where all the dynamic object (belonging to a list of defined layers like Characters) are rendered in Red channel with 1.0 value the rest is 0.0.
    upload_2020-12-10_22-6-36.png

    Next I am reading it like the depth/normal in HLSL to determine if all points of the samplings are part of dynamic objects or not.

    Before:
    upload_2020-12-10_22-12-41.png

    After:
    upload_2020-12-10_21-56-24.png
     

    Attached Files:

    daneobyrd likes this.
  44. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    Can you post how the normal based outlines look? Also make sure to take the screenshot in the scene view. The amount of aliasing seems a bit high so I’m assuming the screenshot was taken in the game view window scaled up.
     
  45. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    @treivize take a look at this turntable video in the twitter thread. The visual style works dynamically and is not bound to one camera view. It’s really impressive
     
  46. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    @daneobyrd, indeed, my bad it looks great on all angles! :eek:

    Here is a new image in Scene view:
    upload_2020-12-11_9-31-7.png

    Let's explain it a bit in details.

    But first to recap, my updates are done by improving the current algo. It is not a new algo.

    The current algo is based on this article: https://alexanderameye.github.io/outlineshader.html, but this article is based on the algo described here: https://roystan.net/articles/outline-shader.html. As mentioned in his source.

    Strangely, the initial article (Roystan) included an optimization that was not kept by Alexander: The biasing of the depth sensibility depending of the surface normal and the camera point of view (called Thresholding with view direction)

    Indeed, the algo looks for depth/normal jump over pixels and compares it to a sensibility to decide if an outline has to be drawn or not. But the depth difference can be high without meaning that there is an edge. A flat surface like the surface of a table seen from a grazing angle will have a strong depth gradient but we do not want to cover it with outline. It is what happens today with the current version of the algo. Actually having a unique depth sensibility global to the whole image whatever the camera point of view is not correct. So that is why Roystan is biasing this depth sensibility input. Luckily we do not have to compute our self the view direction, it can be taken from Camera node (Direction output). I let you read Roystan article to understand the code below.

    Code (CSharp):
    1.  
    2.         // Thresholding with view direction.
    3.         // Balance normal difference based on the camera direction for flat surface viewed from a grazing angle
    4.         float NdotV = 1 - dot(normalSamples[0], viewDir);
    5.         float normalThreshold01 = saturate((NdotV - DepthNormalSensitivity) / (1 - DepthNormalSensitivity));
    6.         float normalThreshold = normalThreshold01 * DepthNormalThresholdScale + 1;
    7.         float depthThreshold = (biasedThickness / DepthSensitivity) * depthSamples[0] * normalThreshold;
    8.         edgeDepth = edgeDepth > depthThreshold ? 1 : 0;
    9.  
    Next, regarding the outlined masking optimization, here is the code with some explanations:

    I have introduced a new private method to do the initial masking pre-process.
    This method is reading the outlined masking texture with the same sampling method and returns a float based on the value of each sample value in red channel (0.0 or 1.0)
    - If all the points are out of the mask (black pixels) then the function return 0 (force no outline)
    - If at least one pixel is black and another is red (only red channel is used) the function return 1 (force outline)
    - If all the pixels are white (inside an area covered by outlined objects). Return 0.5 half outline size.

    Code (CSharp):
    1.  
    2. TEXTURE2D(_CameraOutlineTicknessTexture);
    3. SAMPLER(sampler_CameraOutlineTicknessTexture);
    4.  
    5. float getTicknessBiasing(float2 UV, float OutlineThickness) {
    6.     float halfScaleFloor = floor(OutlineThickness * 0.5);
    7.     float halfScaleCeil = ceil(OutlineThickness * 0.5);
    8.     float2 uvSamples[4];
    9.  
    10.     float allSamplesIn = 1.0;
    11.     float atLeastOneSampleIn = 0.0;
    12.  
    13.     uvSamples[0] = UV - float2(_CameraDepthTexture_TexelSize.x, _CameraDepthTexture_TexelSize.y) * halfScaleFloor;
    14.     uvSamples[1] = UV + float2(_CameraDepthTexture_TexelSize.x, _CameraDepthTexture_TexelSize.y) * halfScaleCeil;
    15.     uvSamples[2] = UV + float2(_CameraDepthTexture_TexelSize.x * halfScaleCeil, -_CameraDepthTexture_TexelSize.y * halfScaleFloor);
    16.     uvSamples[3] = UV + float2(-_CameraDepthTexture_TexelSize.x * halfScaleFloor, _CameraDepthTexture_TexelSize.y * halfScaleCeil);
    17.  
    18.     float maskSample;
    19.  
    20.     for (int i = 0; i < 4; i++)
    21.     {
    22.         maskSample = SAMPLE_TEXTURE2D(_CameraOutlineTicknessTexture, sampler_CameraOutlineTicknessTexture, uvSamples[i]).r;
    23.         allSamplesIn *= maskSample;
    24.         atLeastOneSampleIn += maskSample;
    25.     }
    26.     return atLeastOneSampleIn == 0 ? 0.0 : (1.0 - 0.5 * allSamplesIn);
    27. }
    28.  
    This method is called at the beginning of the current OutlineObject method.
    Based on the result, we can do some shortcuts in the outline algo. Again
    - if all the pixels are not part of any outlined objects, no need to compute the full algo, just return 0 (no outline).
    - If at least one is black one is red, the pixel is on a border, return 1 (force outline)
    - else run the normal algo, just with a smaller thickness (half of the size) since the outline will be fully drawn (either inner outline or border outline of dynamic object overlapping another one)

    Code (CSharp):
    1.  
    2. void OutlineObject_float(float2 UV, float OutlineThickness, float DepthSensitivity, float NormalsSensitivity, float DepthNormalSensitivity, float DepthNormalThresholdScale, float3 viewDir, out float Out)
    3. {
    4.     float biasedThickness = getTicknessBiasing(UV, OutlineThickness);
    5.     if (biasedThickness == 0.0) //The considered pixel is far from an outlined object so no need to draw outline.
    6.     {
    7.         Out = 0.0;
    8.     }
    9.     else if (biasedThickness == 1.0) // The considered pixel is on the border between outlined and not outlined object. Always draw an outline.
    10.     {
    11.         Out = 1.0;
    12.     }
    13.     else // Inner outline and outline between overlapping outlined objects. Apply the depth/normal technique with half outline thickness to balance that all outline width will be drawn
    14.     {
    15.         ...
    16.     }
    17. }
    18.  
    Finally as explained earlier, this optimization relies on identifying the outlined objects based on layer mask, so it requires that all of dynamic objects are belonging to a layer.
    Currently we have only Characters that seems to match exclusively outlined objects.
    I think we should add new ones like: Props, Critters and assign the prefab to them, is it ok to do so?
     
    Last edited: Dec 11, 2020
    MateiGiurgiu, Neonage and daneobyrd like this.
  47. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
  48. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    I'm not sure how that could help in our case. Do you have something in mind?
    As far as I understood, Ben Golus' post is all about when outlines are so wide that they overlap each other, so you need to detect that, and in a performant way. In our case that won't happen.
     
  49. dotsquid

    dotsquid

    Joined:
    Aug 11, 2016
    Posts:
    224
    Ah, yes, you are right. The fact is that I was so amazed by this article at one time (especially since the most popular way to do outlines on 3d meshes is to use black inverted copy of that mesh) that I try to mention it at any opportunity. This time I indeed did not ponder over the real problem.
     
    cirocontinisio likes this.
  50. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    Great explanation of the process. I'm glad introducing Harry's layer mask method was helpful. I agree that adding more layers like Props and Critters makes sense for these outline improvements.

    One thing I'd love to see tested is if any interior (or normal?) outline had their color and/or alpha adjusted relative to the depth based outline. For example, I'm curious how it would look with black depth outlines and dark grey (or lower opacity) normal outlines.

    Also, here's a few small notes/questions:

    I'm not sure how aliased the outline truly is, when I do this in my local project aliasing seems to decrease.
    In game view, under the aspect ratio dropdown, maybe setting Low Resolution Aspect Ratios to off would help?

    Roystan sets Anti-Aliasing to Subpixel Morphological Anti-aliasing (SMAA) at High quality. Should we do the same?
     
    Last edited: Dec 12, 2020