Search Unity

Official Outline Effect

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

  1. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    This is not a choice for some though. You can do this if you're on a retina or 4K screen. We should always make sure that the outline works also (and mainly, actually) on 1080p screens.

    In fact, the whole point with the task (at least the wish I have) is that the outline looks the same width at both 1080p and 4k resolutions. Not the same effect, of course the 4k is always going to be more crisp, but the same relative width (as if its size was measured in world space units).
     
  2. Neonage

    Neonage

    Joined:
    May 22, 2020
    Posts:
    287
    Is it possible to support MSAA with this post-process outline approach?
    Or maybe apply some local AA based off normals?
     
  3. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    @cirocontinisio
    Ah okay, I mentioned it because anecdotally I've had situations in other projects where the game view (in editor) is more pixelated than how the build looks on a 1080p screen (I likely just didn't understand some settings back then).

    I just set my game view to 1920x1080 and it looks good!
     
    Last edited: Dec 12, 2020
    cirocontinisio likes this.
  4. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    @treivize
    I pulled this into my local project and saw that the outline will remain visible as long as Outline Thickness is non-zero, regardless of whatever value any of the other properties have.
     
  5. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    @daneobyrd, actually only the border outline is always drawn after my changes, because they are now always correct thanks to the outlined object masking. My understanding of these sensitivity parameters are only here to find a good compromise to make the outlines looking good enough for each object not to be able to remove it completely. So now they control only the inner outlines.

    For the reduction of the outline thickness based on the distance from the camera, I think we can inject the distance in the algo at different places to force the outline to become thinner and thinner.
     
    daneobyrd likes this.
  6. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    I've done some tests on a 1080p monitor.
    I really like the ideas in this rework, but I think that in situations of lower resolution it ends up creating more aliasing - actually I should say dither.

    This is the current solution:

    OldOutline_Full.png

    OldOutline_Zoomed.png

    In the second screenshot I zoomed in 5x to make the issues very evident. You can see how things are aliased and all the problems we talked about, but it doesn't look that bad.

    This is the PR with the new proposed solution:

    FullScreen_Low.png

    ZoomedIn_Low.png

    You can see how the algorighm to detect the outlines produces this x-shaped dither, which, when going against very thin lines (see headband) create a lot of noise.
    This is zoomed 4x, but even when zoomed out I'd argue is quite visible.

    So personally I think the old one still looks better?
    The only big disadvantage is that is resolution-dependent, and that I'd really like to fix.
     
    daneobyrd likes this.
  7. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    Ah okay, I thought that the post-thickness reduction outline would then be affected by these parameters.
     
  8. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    Let me have a look at this problem, because in this new version of the current algo, the inner outline detection is almost the same as the existing one (except the part related to the thresholding based on the view direction), maybe there is an error somewhere.
     
  9. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    Here's a couple samples of what I see at 1920x1080 at 1x scale:

    Amount of aliasing at closest possible camera position during gameplay:
    upload_2020-12-12_15-3-1.png

    Close-up (I paused and manually moved the camera)
    upload_2020-12-12_15-2-19.png

    and at 4x scale:
    Amount of aliasing when using the game view scaling slider:
    upload_2020-12-12_15-6-55.png

    I think the new approach can be tweaked to avoid this dithering. It looks like this is happening because of the Robert’s Cross sampling being used. I think the new approach is better but the sampling method needs iteration.

    TL/DR:
    1. Combine the Depth and Normals textures into a DepthNormals texture
    2. In place of a robert's cross, use the sobel sampling method, using two convolution matrices, one for x and one for y.
    3. Using the sampled data and Harry H's method, determine which pixels can receive an outline, and set the initial thickness for inter-object outlines and inside lines.
    4. Modify these values using our current material parameters
    Thoughts:

    Here are a few references on thick and thin lines in an edge detection shader.

    Harry Alisavakis uses the algorithm listed below from William Chyr's blog (Manifold Garden), initially based on an edge detection method Lucas Pope wrote about in a DevLog for Return of the Obra Dinn:
    1. Harry Alisaviks – My take on shaders: Edge Detection
    2. William Chyr – Edge Detection Shader Deep Dive! Part 1 – Even or Thinner Edges?
      – Older DevLog William links to on edge detection
    3. Lucas Pope Edge Detection DevLog
    The new approach in @treivize's PR is close to the second algorithm in William's blog:

    – sample surrounding pixels
    – combine depth and normal values to form a color
    – compare the values of the new combined color in surrounding pixels (if values are close, it’s not an edge, else it is)

    I suggest we use this algorithm to set a variable outline (as the current PR does). However, in place of a Robert's Cross or the single sampling method Harry Alisavakis uses, we use two sobel convolution matrices as NedMakesGames does (and was previously mentioned in the main ToonShader thread).

    Using two matrices, one for the x component and one for the y component, should eliminate this dithering. We use the sampled information to decide which pixels receive an outline and set the initial outline thickness for each pixel.

    This utilizes Harry H's method I mentioned earlier in this thread:
    • Use R channel to store a float for each object, determine inter-object lines (outlines).
    • Use G channel for inside lines.
    • Use B to control transparency/harshness
      • He input noise into this channel to create some animation/flipbook effect
    Comparing each channel with the DepthNormals texture will result in the best edge detection. This initial thickness will then be affected by the parameters we have in the inspector/shader graph blackboard.
     
    Last edited: Dec 12, 2020
  10. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    I was thinking the same, and on the road to move for Sobel in my code :) Let's see if it brings nicer outlining
     
    daneobyrd likes this.
  11. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    Sorry that I keep editing the post to add more info! Haha
     
  12. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    In reference to anti-aliasing, I did see a unique approach to this when helping someone in the Unity Discord on their topographic-map contour line shader. I rebuilt and modified this shader forge shader. It was fairly easy to implement here since the only input was World Position Y. I'm not sure if this could be used for the outline shader but I thought I would share it just in case.

    Use a fraction node for both sides of the outline. Smoothstep with DDXY and the line's position. Multiply both results together.

    Anti-aliasing method:
    upload_2020-12-12_16-14-52.png
     
    Last edited: Dec 12, 2020
  13. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    @cirocontinisio and @daneobyrd, I have just pushed the algo bases on Sobel edge detection instead of Roberts cross.
    If you can have a look again and let me know if it looks better now than before.

    For antialiasing like you are proposing Dane, I have to digest it, I am not understanding 100% of it yet and it is too late for me now :) let's see that tomorrow!
     
    cirocontinisio likes this.
  14. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    Haha no worries! I thought I would share the method to get the conversation started
     
  15. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    I've done some testing and overall I would say this is a step forward. Using the sobel algorithm has decreased dithering and also has resulted in a nicer looking outline in my opinion.

    The sharp edges where on the border outline ends also needs to be addressed. I’m actually confused as to why these sharp border outlines are not subject to the properties in the inspector. This is more prominent as you increase the outline thickness (see video thumbnail).

    I think the next step is to tweak and adjust how we outline different objects as well as treating inter-object lines (outlines) differently than inside lines.

    Currently the Lantern and the headband both interrupt the outline drawing.

    I'm pretty sure the first issue is related to what layer the lantern and it's child objects are set to. The second issue is related to how characters have been modeled. The headband on pig chef and the townfolk are currently part of the character models. The same is true for Townsfolk_M's necklace. They are separate meshes but have been exported as a single model.

    To me it makes sense to update the characters by removing the headband and other accessories from the main model, and place them in an accessory slot? Maybe I haven't grasped the project's layer setup yet.

    @cirocontinisio What do you think?

    I increased outline thickness right before I took this video. At around 0:20 I set the thickness closer to it's original value.

     
    Last edited: Dec 14, 2020
  16. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    An update on this topic to explain what has been added to the PR.

    First I addressed also the other problems raised about outine:
    1. Resolution dependency: A simple proposition is to bias the outline thickness based on _ScreenParams.y as folloing in HSLS code:
    Code (CSharp):
    1.   float screenBiasedTickness = OutlineThickness * _ScreenParams.y / 1080;
    So the thickness is defined for FullHD resolution and it will scale depending on the actual screen resolution.
    2. Distance outline fading: again the Thickness is biased based on the distance between the object and the camera.

    The idea is that the thickness width is reduced by one every 10 distance unit from the camera.

    Finally regarding the outline algo itself, an update from the previous one, depth data has been added in the outline mask render pass, here is the outline mask shader.
    upload_2020-12-16_15-5-52.png
    The idea is to draw only border outline in the object which is the closest to the camera in the algo instead of drawing it on both object with half of the thickness, first the algo is quicker (based on the first cross sampling pass) and it removes the small outline displacement when object overlaps.

    Everything is in the PR. Just only Pig Chef material has been tuned properly, so other outlined object materials have to be readjusted to display nice outline based on their own constrains.

    Another thing that seems to improve the outline rendering is render scale, instead of changing MSAA, it seems that increasing scale from 1 to 1.25 is improving significantly the quality of the outline for an equivalent framerate cost
     
    Last edited: Dec 16, 2020
  17. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    For anti-aliasing, why don't we try out the technique Freya Holmér uses for Shapes, her Unity Vector Graphics Library?

    upload_2020-12-18_15-53-39.png

     
    Last edited: Dec 20, 2020
  18. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Yeah... but that has an explosive effect on the cost of rendering, 1.25 doesn't just mean
    Width x Height x 1.25
    , but
    Width x 1.25 x Height x 1.25
    the pixels! :confused:
    So I'd like to avoid that.
     
  19. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    @cirocontinisio, definitely it is quite costly. I tried to find a way to apply a render scale only for render pass to improve the quality of the depth normal texture before applying the outline algo, but I did not find how to do that in ScriptableRenderFeature / CommandBuffer

    @daneobyrd, again an interesting technique, I am not sure in which part we should use it, I do not know how to use it to improve globally the outline when become aliased /dither because the algo is not really drawing any line but black dot one by one and magically draw lines. Next it could be used to draw outline during the fading effect when the object start to be far, to be tested, I have the feeling that the thinner line effect based on light grey works in contrast of dark line, but if all the lines are light grey I am not sure it will work well. Again maybe to be tested.
     
    daneobyrd likes this.
  20. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    I'm still not convinced about the improvements...

    I did some quick test with the latest of @treivize 's commits (the merge, 15a9d55) and I can still see some decent "wandering ants" effect like Freya calls it in the gif above.

    Although I don't think it's worse, it's also not improved much...
    Here's a comparison of the before solution, and the after (the PR):



    Also, the outline keeps being doubled when the geometry overlaps with itself. You can see it here in the headband, hand and overall sleeve to the right:

    upload_2021-1-5_14-10-20.png
     
  21. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    I've been busy and not able to contribute lately, so to get back into things I thought I'd try and explain my understanding of the current outline improvements. @treivize please correct any misunderstandings I have below:

    Edit: I thought this was the case after looking at the outline's behavior with the model's hands. However I noticed this outline doubling on the ear and the jaw, with both depth and normal outlines.

    As long as Outline Thickness is non-zero, the new DepthNormal outline remains visible. Even if the new Depth Normal properties, Depth Sensitivity, and Normal Sensitivity are all set to zero.​

    @cirocontinisio ̶t̶h̶e̶ ̶c̶u̶r̶r̶e̶n̶t̶ ̶o̶u̶t̶l̶i̶n̶e̶ ̶d̶o̶u̶b̶l̶i̶n̶g̶ ̶y̶o̶u̶ ̶s̶e̶e̶ ̶n̶o̶w̶ ̶i̶s̶ ̶a̶ ̶r̶e̶s̶u̶l̶t̶ ̶o̶f̶ ̶t̶w̶o̶ ̶p̶a̶s̶s̶e̶s̶,̶ ̶e̶a̶c̶h̶ ̶d̶r̶a̶w̶i̶n̶g̶ ̶a̶n̶ ̶o̶u̶t̶l̶i̶n̶e̶ ̶o̶n̶c̶e̶ ̶r̶a̶t̶h̶e̶r̶ ̶t̶h̶a̶n̶ ̶a̶ ̶s̶i̶n̶g̶l̶e̶ ̶p̶a̶s̶s̶ ̶d̶r̶a̶w̶i̶n̶g̶ ̶t̶h̶e̶ ̶o̶u̶t̶l̶i̶n̶e̶ ̶t̶w̶i̶c̶e̶.̶

    As far as I know, the depth-based outline has been updated to use the sobel algorithm as well (replacing the robert's cross sampling algorithm).

    The original issue we aimed to solve was that the depth-based outline was drawn twice when the geometry overlapped itself as the algorithm did not determine whether the background geometry was a part of the same object. N̶o̶w̶ ̶b̶y̶ ̶s̶t̶o̶r̶i̶n̶g̶ ̶a̶ ̶f̶l̶o̶a̶t̶ ̶f̶o̶r̶ ̶e̶a̶c̶h̶ ̶o̶b̶j̶e̶c̶t̶,̶ ̶t̶h̶e̶ ̶d̶e̶p̶t̶h̶-̶b̶a̶s̶e̶d̶ ̶o̶u̶t̶l̶i̶n̶e̶ ̶i̶s̶ ̶o̶n̶l̶y̶ ̶d̶r̶a̶w̶n̶ ̶o̶n̶c̶e̶.̶​

    N̶o̶w̶ ̶w̶h̶e̶n̶ ̶t̶h̶e̶ ̶g̶e̶o̶m̶e̶t̶r̶y̶ ̶o̶v̶e̶r̶l̶a̶p̶s̶ ̶w̶i̶t̶h̶ ̶i̶t̶s̶e̶l̶f̶,̶ ̶t̶h̶e̶ ̶d̶o̶u̶b̶l̶i̶n̶g̶ ̶w̶e̶ ̶s̶e̶e̶ ̶i̶s̶ ̶t̶h̶e̶ ̶o̶v̶e̶r̶l̶a̶p̶ ̶o̶f̶ ̶t̶h̶e̶ ̶s̶o̶b̶e̶l̶ ̶o̶u̶t̶l̶i̶n̶e̶ ̶a̶n̶d̶ ̶o̶u̶r̶ ̶o̶r̶i̶g̶i̶n̶a̶l̶ ̶a̶p̶p̶r̶o̶a̶c̶h̶.̶
    Edit: This is the cause of some outline doubling at the edges of the geometry but I'm not sure if this is happening when geometry overlap. I haven't yet looked at turning off the DepthNormal outline to test this.

    Lastly, the new outline used at the geometry's edge terminates sharply. This results in some abrupt changes in outline thickness.
    upload_2021-1-11_16-16-21.png

    I had a few ideas on possible next steps:
    A. Consolidate the new DepthNormal outline with the original depth-based outline to avoid doubling and simplify the shader
    B. Make the DepthNormal outline continuous
    C. Remove doubling from the normal-based outline
    D. Add a color or alpha transition as lines get smaller, to eliminate or mitigate any "wandering ants."
     
    Last edited: Jan 25, 2021
  22. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    I have been silent for a while, because I was trying to figure out how to improve the outline because I am also not satisfied by the result of my last proposal...

    And I am reaching a point where I would challenge the usage of the Depth/Normal approach, because it seems really a nightmare to make it works at different scale of details in a complex model, at different distance from the camera, at different screen resolution, etc...

    I had a look at this paper from Pixar (https://graphics.pixar.com/library/ToonRendering/paper.pdf) which was mentioned in Roystan's article conclusion. The approach seems to provide more control over the outline process in exchange of manual work for each model we want to outline (outline thickness texture and vertex color assignment to define regions)

    How I see the implementation:
    - Keep the computation of the contour based on a render pass encoding the object distance from the camera in the Red channel then apply a Roberts' cross/Sobel edge detection to draw the contour
    - Encode in Vertex Color blue channel the regions of the model that should generate outline between each other (ie. head, ears, torso, arms, legs, bandana, inner arm shirt).
    - Draw a greyscale texture that will control the outline thickness (ie fading outline between ears and head where surface is smooth)
    - Read the vertex blue channel value in outline process, apply again a Roberts' cross/Sobel algo to determine frontiers and modulate the thickness based on the value of the thickness texture at this pixel.

    I will work on a showcase of this approach (if I manage to set the vertex color of the pig without breaking the animations in the FBX files...)

    What is your feeling about this approach?

    NOTE: I give a try (breaking animation so with Pig in "Rest Pose") and the result looks like that:
    outline_v3.gif
     
    Last edited: Jan 13, 2021
    Smurjo and daneobyrd like this.
  23. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    I agree. As I experimented with your last proposal I found it difficult to get consistent results for detailed areas of the geometry like the chef's hands. I definitely prefer more control over the outline process in exchange for more manual work. Having greater control over line thickness will bring us closer to the line-work shown in the concept art.

    I had come across that paper mentioned in Roy's article before but I hadn't looked into it much. The approach reminds me of some of what Harry Heath's approach that I shared in previous forum post.

    This new approach will help with outlining props and accessories as well. Previously there has been odd outline behavior between the chef and props like the lantern on his back.
     
  24. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    It looks ok, but the lines now are not super dynamic. For instance, along the line where the apron connects to the shirt, there's always a black line regardless of how he turns. I think this way it loses some nuance. I like that some black lines form when surfaces fold and bend (because the normal difference becomes more accentuated).

    I tried your latest PR, and I thought the AA looks good, except for the broken lines and other issues noted by @daneobyrd.

    I was thinking: personally, I don't care too much about the double lines. What if we just port the AA to the original solution, and keep the outline like that?
     
  25. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    Yeah, I was also thinking the same actually :) I would keep AA, screen size adjustment, and distance outline fading the part in shader graph actually. I can push that into a new PR, you are fine with the listed features included.
     
    cirocontinisio likes this.
  26. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    Here is the PR without touching at the inner outline algo: https://github.com/UnityTechnologies/open-project-1/pull/316

    A pure shader graph update:


    Distance fading:
    Outline Thickness is reduced based on the distance of an outlined object from the camera. Reduced by 1 every 10 world dimension unit

    Screen Resolution adjustment:

    Outline thickness is scaled up or down based on the screen height size. The material Outline Thickness property will be the one for a HD resolution (1080px height).

    Antialiasing algorithm:

    Based on Harry solution shared in Discord
    The main idea is to run the outline function twice with different thickness value and to lerp both outputs.

    NOTE: The materials of outlined object might have to be adjusted following this implementation if they were not defined with the targeted HD resolution.
     
    daneobyrd likes this.
  27. treivize

    treivize

    Joined:
    Jan 27, 2016
    Posts:
    136
    cirocontinisio likes this.
  28. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    No, you're right, we can probably mark it as done, finally! :D
     
    vx4 and treivize like this.
  29. Peter-Bailey

    Peter-Bailey

    Joined:
    Oct 12, 2012
    Posts:
    36
    How well does Harry's solution to anti-aliasing work?

    What about some kind of fast median filter or Scale2x algorithm? Game emulators use various pixel art upscaling algorithms that might be useful in this case? https://en.m.wikipedia.org/wiki/Pixel-art_scaling_algorithms

    I've seen Scale2x for Unity on the forums somewhere.
     
  30. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    @TimeStrafer
    I have been working on custom color buffer outlines for a little bit (Harry's method) and there are many many ways to implement anti-aliasing. Once I have something up on github I will post here about it.
     
    Peter-Bailey likes this.
  31. dexhort

    dexhort

    Joined:
    Dec 18, 2021
    Posts:
    10
    Hi, sorry to pop on this thread out of no where, I have read all previous message and I'm looking for answers to my problems.

    I have, like you, made an outline render that works well (I don't search for a realy precise work, it's just for a selection outline) based on articles you previously posted here :
    https://alexanderameye.github.io/notes/edge-detection-outlines/
    https://roystan.net/articles/outline-shader.html

    But I have a problem, as you can see here, my outline is applied on every single mesh on my scene, but I tried to make it applied only to object in a outlineLayer.

    Someone have a solution ? I have add a layermask parameter in the DepthNormalsRenderPass, cause the only option I have seen for adding it was into the DrawRenderers.

    Also it seems like my Blit() is applied to all my screen and not only a part of it.




    here is the code for the Depth Normals :
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Rendering.Universal;
    3. using UnityEngine.Rendering;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6.  
    7. public class DepthNormalsFeature : ScriptableRendererFeature
    8. {
    9.     class DepthNormalsPass : ScriptableRenderPass
    10.     {
    11.         int kDepthBufferBits = 32;
    12.         private RenderTargetHandle depthAttachmentHandle {get;set;}
    13.         internal RenderTextureDescriptor descriptor {get; private set;} //https://docs.unity3d.com/ScriptReference/RenderTextureDescriptor.html
    14.  
    15.         private Material depthNormalsMaterial = null;
    16.         private FilteringSettings filteringSettings; //Structure décrivant comment filtrer les objet lors d'un DrawRenderers. https://docs.unity3d.com/ScriptReference/Rendering.FilteringSettings.html
    17.         string profilerTag = "DepthNormals Prepass";
    18.         private List<ShaderTagId> shaderTagId = new List<ShaderTagId> {
    19.             new ShaderTagId("UniversalForward"),
    20.             new ShaderTagId("UniversalForwardOnly"),
    21.             new ShaderTagId("LightweightForward"),
    22.             new ShaderTagId("SRPDefaultUnlit"),
    23.             new ShaderTagId("DepthOnly")
    24.         };
    25.      
    26.  
    27.         //Constructeur, initialise le filteringSettings et le matériaux pour override texture
    28.         public DepthNormalsPass(RenderQueueRange renderQueueRange, LayerMask layerMask,Material material)
    29.         {
    30.             int mask = 1 << layerMask.value;
    31.             filteringSettings = new FilteringSettings(renderQueueRange, mask);
    32.             depthNormalsMaterial = material;
    33.         }
    34.  
    35.         public void Setup(RenderTextureDescriptor baseDescriptor, RenderTargetHandle depthAttachmentHandle)
    36.         {
    37.             this.depthAttachmentHandle = depthAttachmentHandle;
    38.             baseDescriptor.colorFormat = RenderTextureFormat.ARGB32;
    39.             baseDescriptor.depthBufferBits = kDepthBufferBits;
    40.             descriptor = baseDescriptor;
    41.         }
    42.  
    43.  
    44.         //Appeler avant l'exectution de la renderpass
    45.         //Jamais utiliser CommandBuffer.SetRenderTarget, plutôt utiliser ConfigureTarget et ConfigureClear
    46.         //permet d'assurer un fonctionnement opti performance
    47.         public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    48.         {
    49.             cmd.GetTemporaryRT(depthAttachmentHandle.id,descriptor,FilterMode.Point);
    50.             ConfigureTarget(depthAttachmentHandle.Identifier());
    51.             ConfigureClear(ClearFlag.All, Color.black); //Configure comment le pipeline clear les partit de l'image non retenu par le filtre
    52.         }
    53.  
    54.         //Ici s'inscript la logique de rendu
    55.         //ScriptableRenderContext permet de réaliser le rendu ou executer les commandes du buffer
    56.         //https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.html
    57.         //pas besoin de faire ScriptableRenderContext.submit(), il se fait automatiquement durant le pipeline
    58.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    59.         {
    60.             CommandBuffer cmd = CommandBufferPool.Get(profilerTag);
    61.  
    62.             using (new ProfilingScope(cmd, new ProfilingSampler(profilerTag))) // https://docs.unity.cn/Packages/com.unity.render-pipelines.core@14.0/api/UnityEngine.Rendering.ProfilingScope.html
    63.             {
    64.                 context.ExecuteCommandBuffer(cmd);
    65.                 cmd.Clear();
    66.              
    67.                 var sortFlags = renderingData.cameraData.defaultOpaqueSortFlags;
    68.                 var drawSettings = CreateDrawingSettings(shaderTagId, ref renderingData, sortFlags); // https://docs.unity3d.com/ScriptReference/Rendering.DrawingSettings.html
    69.                 drawSettings.perObjectData = PerObjectData.None;
    70.  
    71.                 ref CameraData cameraData = ref renderingData.cameraData;
    72.                 Camera camera = cameraData.camera;
    73.                 if(XRGraphics.enabled)  //CameraData.isStereoEnabled est obsolete
    74.                     context.StartMultiEye(camera);
    75.  
    76.                 drawSettings.overrideMaterial = depthNormalsMaterial;
    77.  
    78.                 context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filteringSettings);
    79.  
    80.                 cmd.SetGlobalTexture("_CameraDepthNormalsTexture", depthAttachmentHandle.id);
    81.             }
    82.  
    83.             context.ExecuteCommandBuffer(cmd);
    84.             CommandBufferPool.Release(cmd);
    85.         }
    86.  
    87.         // Permet de netoyer les ressources allouées pendant la création de la renderpass
    88.         public override void FrameCleanup(CommandBuffer cmd)
    89.         {
    90.             if(depthAttachmentHandle != RenderTargetHandle.CameraTarget)
    91.             {
    92.                 cmd.ReleaseTemporaryRT(depthAttachmentHandle.id);
    93.                 depthAttachmentHandle = RenderTargetHandle.CameraTarget;
    94.             }
    95.         }
    96.     }
    97.  
    98.     DepthNormalsPass depthNormalsPass;
    99.     RenderTargetHandle depthNormalsTexture;
    100.     Material depthNormalsMaterial;
    101.     [SerializeField] private LayerMask layerMask;
    102.  
    103.     public override void Create()
    104.     {
    105.         depthNormalsMaterial = CoreUtils.CreateEngineMaterial("Hidden/Internal-DepthNormalsTexture");
    106.         depthNormalsPass = new DepthNormalsPass(RenderQueueRange.opaque, layerMask, depthNormalsMaterial);
    107.         depthNormalsPass.renderPassEvent = RenderPassEvent.AfterRenderingPrePasses;
    108.         depthNormalsTexture.Init("_CameraDepthNormalsTexture");
    109.     }
    110.  
    111.     //Permet d'injecter des renderpass dans le pipeling
    112.     //Cette méthode est appeler une fois par camera au moment du setup du renderer
    113.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    114.     {
    115.         depthNormalsPass.Setup(renderingData.cameraData.cameraTargetDescriptor, depthNormalsTexture);
    116.         renderer.EnqueuePass(depthNormalsPass);
    117.     }
    118. }
    and here for the Outline Features :
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Rendering;
    3. using UnityEngine.Rendering.Universal;
    4.  
    5. public class OutlineFeatures : ScriptableRendererFeature
    6. {
    7.     class OutlinePass : ScriptableRenderPass
    8.     {
    9.         private RenderTargetIdentifier source {get; set;}
    10.         private RenderTargetHandle destination {get; set;}
    11.         public Material outlineMaterial = null;
    12.         RenderTargetHandle temporaryColorTexture;
    13.  
    14.         public void Setup(RenderTargetIdentifier source, RenderTargetHandle destination)
    15.         {
    16.             this.source = source;
    17.             this.destination = destination;
    18.         }
    19.  
    20.         public OutlinePass(Material outlineMaterial)
    21.         {
    22.             this.outlineMaterial = outlineMaterial;
    23.         }
    24.  
    25.         public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    26.         {
    27.             cmd.GetTemporaryRT(destination.id,cameraTextureDescriptor,FilterMode.Point);
    28.             ConfigureTarget(destination.Identifier());
    29.             ConfigureClear(ClearFlag.All, Color.black);
    30.         }
    31.  
    32.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    33.         {
    34.             CommandBuffer cmd = CommandBufferPool.Get("_OutlinePass");
    35.  
    36.             RenderTextureDescriptor opaqueDescriptor = renderingData.cameraData.cameraTargetDescriptor;
    37.             opaqueDescriptor.depthBufferBits = 0;
    38.  
    39.             using (new ProfilingScope(cmd, new ProfilingSampler("_OutlinePass")))
    40.             {
    41.                 if (destination == RenderTargetHandle.CameraTarget)
    42.                 {
    43.                     cmd.GetTemporaryRT(temporaryColorTexture.id, opaqueDescriptor, FilterMode.Point);
    44.                     Blit(cmd,source,temporaryColorTexture.Identifier(),outlineMaterial,0);
    45.                     Blit(cmd,temporaryColorTexture.Identifier(),source);
    46.                 }
    47.                 else {
    48.                     Blit(cmd,source,destination.Identifier(),outlineMaterial,0);
    49.                 }
    50.             }
    51.  
    52.             context.ExecuteCommandBuffer(cmd);
    53.             CommandBufferPool.Release(cmd);
    54.         }
    55.  
    56.         public override void FrameCleanup(CommandBuffer cmd)
    57.         {
    58.             if(destination == RenderTargetHandle.CameraTarget)
    59.                 cmd.ReleaseTemporaryRT(temporaryColorTexture.id);
    60.         }  
    61.     }
    62.  
    63.     [System.Serializable]
    64.     public class OutlineSettings
    65.     {
    66.         public Material outlineMaterial = null;
    67.     }
    68.     public OutlineSettings settings = new OutlineSettings();
    69.     OutlinePass outlinePass;
    70.     RenderTargetHandle outlineTexture;
    71.  
    72.     public override void Create()
    73.     {
    74.         outlinePass = new OutlinePass(settings.outlineMaterial);
    75.         outlinePass.renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
    76.         outlineTexture.Init("_OutlineTexture");
    77.     }
    78.  
    79.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    80.     {
    81.         if(settings.outlineMaterial == null)
    82.         {
    83.             Debug.LogWarningFormat("Missing Outline Material");
    84.             return;
    85.         }
    86.         outlinePass.Setup(renderer.cameraColorTarget, RenderTargetHandle.CameraTarget);
    87.         renderer.EnqueuePass(outlinePass);
    88.     }
    89. }
    90.  
    my Outline.hlsl is a pure copy of the alexander project.

    Thank you if you have some kind of ideas (I'm new in this type of things), I have tried some things but it didn't work either.
     
  32. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    I assume the reason your layerMask isn’t working as expected is because you are using a bit shift when you can simply cast LayerMask to int. Therefore, the int value of mask used by your filteringSettings is incorrect.

    For even greater control over the filtering you can enable light layers and use them for FilteringSettings.renderingLayerMask. You can use LightLayerEnum in your settings class just like LayerMask, and don’t forget to cast it to int.

    Additional resource, you should read Alexander’s subsequent article 5 ways to draw an outline.

    For an example of the custom discontinuity source Alexander mentions in the section on edge detection, see these pastebin links of Harry Heath’s render passes and renderer feature.

    I would go into more detail but I am currently away from my computer so I will stop here.
     
    Last edited: Jun 23, 2022
  33. dexhort

    dexhort

    Joined:
    Dec 18, 2021
    Posts:
    10
    Thank you ! I go read this right now.
     
    daneobyrd likes this.
  34. daneobyrd

    daneobyrd

    Joined:
    Mar 29, 2018
    Posts:
    101
    I said this in October and since then I’ve gotten way more ambitious with this outline repo.

    I will likely expand it to provide examples of all the methods listed in Alexander Ameye’s “5 ways to create an outline” as well as include a compute shader and fragment shader version.

    Sorry for the silence on this!
     
  35. dexhort

    dexhort

    Joined:
    Dec 18, 2021
    Posts:
    10
    Hello, I don't know if it's the right place to ask but :

    In this article linked in "5 ways to create an outline" for the JFA method, I understand that HDRP can ask a custom Renderpass from a monobehavior.

    Is it possible to do the same in URP or it's only possible through a scriptableRendererFeature ?

    I have tried to transform the code from HDRP to URP but I have only negativ results (no image or completely white meshes).

    I have tried to replace the rendererDraw phase by a DrawRenderers with a layermask that will restrict the gameobjects drawn.

    But when I try to aply the JFA on this black and white renderpass, I have negativ results.

    Sorry if it's hard to read, I haven't put comment yet and don't have time right now.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using System;
    4. using UnityEngine;
    5. using UnityEngine.Rendering;
    6. using UnityEngine.Rendering.Universal;
    7. using UnityEngine.Experimental.Rendering;
    8.  
    9. public class JumpFloodRenderFeatures : ScriptableRendererFeature
    10. {
    11.    
    12.     [Serializable]class RenderSettings
    13.     {
    14.         const int SHADER_PASS_INTERIOR_STENCIL = 0;
    15.         const int SHADER_PASS_SILHOUETTE_BUFFER_FILL = 1;
    16.         const string shaderName = "Hidden/URPJumpFloodShader";
    17.         readonly int _meshOcculsionID = Shader.PropertyToID("_MeshOcculsion");
    18.         const bool useSeparableAxisMethod = true;
    19.        
    20.         [ColorUsageAttribute(true,true)] public Color outlineColor = Color.white; // Outline Color, setup in the rendererFeature
    21.         [Range(0.0f, 1000.0f)] public float outlinePixelWidth = 4f; // Outline Width, setup in the rendererFeature
    22.         [HideInInspector, SerializeField] public Shader outlineShader; // Outline Shader, setup in the renderFeature
    23.         [SerializeField] public LayerMask layerMask;
    24.  
    25.         public Material outlineMaterial;
    26.         public bool doRender;
    27.  
    28.         public RenderSettings()
    29.         {
    30.             if(outlineColor.a <= (1f/255f) || outlinePixelWidth <= 0f)
    31.             {
    32.                 doRender = false;
    33.             }
    34.             else
    35.                 doRender = true;
    36.  
    37.         }
    38.  
    39.         public bool UseSeparableAxis()
    40.         {
    41.             return useSeparableAxisMethod;
    42.         }
    43.  
    44.         public string GetShaderName()
    45.         {
    46.             return shaderName;
    47.         }
    48.     }
    49.  
    50.  
    51.     class AlphaPass : ScriptableRenderPass
    52.     {
    53.         const int SHADER_PASS_INTERIOR_STENCIL = 0;
    54.         const int SHADER_PASS_SILHOUETTE_BUFFER_FILL = 1;
    55.  
    56.         readonly int _meshOcculsionID = Shader.PropertyToID("_MeshOcculsion");
    57.        
    58.  
    59.         private RenderTextureDescriptor silhouetteDescriptor;
    60.  
    61.         private FilteringSettings filteringSettings;
    62.         private RenderSettings settings;
    63.         private List<ShaderTagId> shaderTagIds = new List<ShaderTagId>{
    64.             new ShaderTagId("UniversalForward"),
    65.             new ShaderTagId("UniversalForwardOnly"),
    66.             new ShaderTagId("LightweightForward"),
    67.             new ShaderTagId("SRPDefaultUnlit")
    68.         };
    69.  
    70.         public AlphaPass(RenderQueueRange renderQueueRange, RenderSettings settings)
    71.         {
    72.             this.settings = settings;
    73.             filteringSettings = new FilteringSettings(renderQueueRange, (int) settings.layerMask);
    74.         }
    75.  
    76.         public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    77.         {
    78.             int msaa = Mathf.Max(1,QualitySettings.antiAliasing);
    79.            
    80.             RenderTextureDescriptor silhouetteDescriptor = new RenderTextureDescriptor(){
    81.                 dimension = TextureDimension.Tex2D,
    82.                 graphicsFormat = GraphicsFormat.R8_UNorm,
    83.  
    84.                 width = cameraTextureDescriptor.width,
    85.                 height = cameraTextureDescriptor.height,
    86.  
    87.                 msaaSamples = msaa,
    88.                 depthBufferBits = 0,
    89.  
    90.                 sRGB = false,
    91.                 useMipMap = false,
    92.                 autoGenerateMips = false
    93.             };
    94.             cmd.GetTemporaryRT(_meshOcculsionID,silhouetteDescriptor,FilterMode.Point);
    95.             ConfigureTarget(_meshOcculsionID);
    96.             ConfigureClear(ClearFlag.All, Color.black);
    97.         }
    98.  
    99.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    100.         {
    101.             CommandBuffer cmd = CommandBufferPool.Get("SilhouetteBufferPass");
    102.             using (new ProfilingScope(cmd, new ProfilingSampler("SilhouetteBufferPass")))
    103.             {
    104.                 context.ExecuteCommandBuffer(cmd);
    105.                 cmd.Clear();
    106.                 DrawingSettings drawSetting =  CreateDrawingSettings(shaderTagIds,ref renderingData, renderingData.cameraData.defaultOpaqueSortFlags);
    107.                 drawSetting.overrideMaterial = settings.outlineMaterial;
    108.                 context.DrawRenderers(renderingData.cullResults, ref drawSetting, ref filteringSettings);              
    109.                 cmd.SetGlobalTexture(_meshOcculsionID, _meshOcculsionID);
    110.             }
    111.             context.ExecuteCommandBuffer(cmd);
    112.             CommandBufferPool.Release(cmd);
    113.         }
    114.  
    115.         public override void FrameCleanup(CommandBuffer cmd)
    116.         {
    117.             cmd.ReleaseTemporaryRT(_meshOcculsionID);
    118.         }
    119.  
    120.  
    121.     }
    122.  
    123.  
    124.     class OutlinePass : ScriptableRenderPass
    125.     {
    126.         const int SHADER_PASS_INTERIOR_STENCIL = 0;
    127.         const int SHADER_PASS_SILHOUETTE_BUFFER_FILL = 1;
    128.         const int SHADER_PASS_JFA_INIT = 2;
    129.         const int SHADER_PASS_JFA_FLOOD = 3;
    130.         const int SHADER_PASS_JFA_FLOOD_SINGLE_AXIS = 4;
    131.         const int SHADER_PASS_JFA_OUTLINE = 5;
    132.        
    133.         private RenderTargetIdentifier _target;
    134.  
    135.         private RenderSettings settings;
    136.  
    137.         readonly int _meshOcculsionID = Shader.PropertyToID("_MeshOcculsion");
    138.  
    139.         readonly int _silhouetteBufferID = Shader.PropertyToID("_SilhouetteBuffer");
    140.         readonly int _nearestPointID = Shader.PropertyToID("_NearestPoint");
    141.         readonly int _nearestPointPingPongID = Shader.PropertyToID("_NearestPointPingPong");
    142.  
    143.         readonly int _outlineColorID = Shader.PropertyToID("_OutlineColor");
    144.         readonly int _mousePositionID = Shader.PropertyToID("_MousePosition");
    145.         readonly int _outlineWidthID = Shader.PropertyToID("_OutlineWidth");
    146.         readonly int _stepWidthID = Shader.PropertyToID("_StepWidth");
    147.         readonly int _axisWidthID = Shader.PropertyToID("_AxisWidth");
    148.  
    149.         public OutlinePass(RenderSettings settings)
    150.         {
    151.             this.settings = settings;
    152.         }
    153.  
    154.         public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
    155.         {
    156.             base.OnCameraSetup(cmd, ref renderingData);
    157.             _target = renderingData.cameraData.targetTexture;
    158.         }
    159.  
    160.         public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    161.         {
    162.             base.Configure(cmd, cameraTextureDescriptor);
    163.  
    164.             if(settings.outlineMaterial == null)
    165.             {
    166.                 settings.outlineMaterial = new Material(settings.outlineShader != null ? settings.outlineShader : Shader.Find(settings.GetShaderName()));
    167.             }
    168.             int msaa = Mathf.Max(1,QualitySettings.antiAliasing);
    169.            
    170.             RenderTextureDescriptor silhouetteDescriptor = new RenderTextureDescriptor(){
    171.                 dimension = TextureDimension.Tex2D,
    172.                 graphicsFormat = GraphicsFormat.R8_UNorm,
    173.  
    174.                 width = cameraTextureDescriptor.width,
    175.                 height = cameraTextureDescriptor.height,
    176.  
    177.                 msaaSamples = msaa,
    178.                 depthBufferBits = 0,
    179.  
    180.                 sRGB = false,
    181.                 useMipMap = false,
    182.                 autoGenerateMips = false
    183.             };
    184.             cmd.GetTemporaryRT(_silhouetteBufferID, silhouetteDescriptor, FilterMode.Point);
    185.  
    186.             RenderTextureDescriptor jfaDescriptor = silhouetteDescriptor;
    187.             jfaDescriptor.msaaSamples = 1;
    188.             jfaDescriptor.graphicsFormat = GraphicsFormat.R16G16_SNorm;
    189.  
    190.             cmd.GetTemporaryRT(_nearestPointID, jfaDescriptor, FilterMode.Point);
    191.             cmd.GetTemporaryRT(_nearestPointPingPongID, jfaDescriptor, FilterMode.Point);
    192.  
    193.             ConfigureTarget(_silhouetteBufferID);
    194.             ConfigureClear(ClearFlag.All, Color.clear);
    195.         }
    196.  
    197.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    198.         {
    199.             var mouseViewPortPos = renderingData.cameraData.camera.ScreenToViewportPoint(Input.mousePosition);
    200.  
    201.             CommandBuffer cmd = CommandBufferPool.Get("JumpFloodOutlinePass");
    202.             using(new ProfilingScope(cmd, new ProfilingSampler("JumpFloodOutlinePass")))
    203.             {
    204.                 context.ExecuteCommandBuffer(cmd);
    205.                 cmd.Clear();
    206.  
    207.                 cmd.Blit(_meshOcculsionID, _silhouetteBufferID, settings.outlineMaterial, SHADER_PASS_SILHOUETTE_BUFFER_FILL);
    208.  
    209.                 // Humus3D wire trick, keep line 1 pixel wide and fade alpha instead of making line smaller
    210.                 // slightly nicer looking and no more expensive
    211.                 Color adjustedOutlineColor = settings.outlineColor;
    212.                 adjustedOutlineColor.a *= Mathf.Clamp01(settings.outlinePixelWidth);
    213.                 cmd.SetGlobalColor(_outlineColorID, adjustedOutlineColor.linear);
    214.                 cmd.SetGlobalFloat(_outlineWidthID, Mathf.Max(1f, settings.outlinePixelWidth));
    215.                 cmd.SetGlobalVector(_mousePositionID, mouseViewPortPos);
    216.  
    217.                 int numMips = Mathf.CeilToInt(Mathf.Log(settings.outlinePixelWidth + 1.0f, 2f));
    218.                 int jfaIter = numMips - 1;
    219.  
    220.                 if(settings.UseSeparableAxis())
    221.                 {
    222.                     //jfa Init
    223.                     cmd.Blit(_silhouetteBufferID, _nearestPointID, settings.outlineMaterial, SHADER_PASS_JFA_INIT);
    224.  
    225.                     // jfa flood passes
    226.                     for (int i = jfaIter; i >= 0; i--)
    227.                     {
    228.                         // calculate jump width
    229.                         float stepWidth = Mathf.Pow(2,i) + 0.5f;
    230.  
    231.                         cmd.SetGlobalVector(_axisWidthID, new Vector2(stepWidth, 0f));
    232.                         cmd.Blit(_nearestPointID,_nearestPointPingPongID, settings.outlineMaterial, SHADER_PASS_JFA_FLOOD_SINGLE_AXIS);
    233.                         cmd.SetGlobalVector(_axisWidthID, new Vector2(0f,stepWidth));
    234.                         cmd.Blit(_nearestPointPingPongID, _nearestPointID, settings.outlineMaterial, SHADER_PASS_JFA_FLOOD_SINGLE_AXIS);
    235.  
    236.                     }
    237.                 }
    238.                 else
    239.                 {
    240.                     int startBufferID = ( jfaIter % 2 == 0) ? _nearestPointPingPongID : _nearestPointID;
    241.  
    242.                     cmd.Blit(_silhouetteBufferID, startBufferID, settings.outlineMaterial, SHADER_PASS_JFA_INIT);
    243.  
    244.                     for(int i = jfaIter; i >= 0; i--)
    245.                     {
    246.                         cmd.SetGlobalFloat(_stepWidthID, Mathf.Pow(2,i) + 0.5f);
    247.  
    248.                         if(i % 2 == 1)
    249.                             cmd.Blit(_nearestPointID,_nearestPointPingPongID, settings.outlineMaterial, SHADER_PASS_JFA_FLOOD);
    250.                         else
    251.                             cmd.Blit(_nearestPointPingPongID, _nearestPointID, settings.outlineMaterial, SHADER_PASS_JFA_FLOOD);
    252.                     }
    253.                 }
    254.                 cmd.Blit(_nearestPointID, _target, settings.outlineMaterial, SHADER_PASS_JFA_OUTLINE);
    255.             }
    256.             context.ExecuteCommandBuffer(cmd);
    257.             CommandBufferPool.Release(cmd);
    258.         }
    259.  
    260.         public override void FrameCleanup(CommandBuffer cmd)
    261.         {
    262.             cmd.ReleaseTemporaryRT(_silhouetteBufferID);
    263.             cmd.ReleaseTemporaryRT(_nearestPointID);
    264.             cmd.ReleaseTemporaryRT(_nearestPointPingPongID);
    265.  
    266.             base.FrameCleanup(cmd);
    267.         }
    268.  
    269.     }
    270.  
    271.  
    272.    
    273.     AlphaPass alphaPass; // InitPass
    274.  
    275.     OutlinePass outlinePass; // InitPass
    276.     private RenderTargetHandle alphaTexture;
    277.     [SerializeField] RenderSettings settings = new RenderSettings();
    278.  
    279.     public override void Create()
    280.     {
    281.         alphaPass = new AlphaPass(RenderQueueRange.opaque, settings);
    282.  
    283.         outlinePass = new OutlinePass(settings);
    284.     }
    285.  
    286.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    287.     {
    288.         renderer.EnqueuePass(alphaPass);
    289.         renderer.EnqueuePass(outlinePass);
    290.     }
    291. }
    292.  
    Thanks if you found a solution or if you can explain me if I have something I have misunderstand.
     
    daneobyrd likes this.
  36. Divesh_dogra

    Divesh_dogra

    Joined:
    Mar 30, 2020
    Posts:
    6
    Hello, I am having the same issue in unity 2020.3.
    I made a shader from shader graph for creating an outline from normals
    Here is a link which I followed to make this shader -


    This works fine in unity but after I make a build for android it is simply not visible, I checked my URP settings I have both opaque and depth texture enabled also anti-aliasing is enabled in URP settings.
    Any help would be appreciated.

    Thank you