Search Unity

toon outline with DOF?

Discussion in 'Shaders' started by chkkll, Mar 13, 2011.

  1. chkkll

    chkkll

    Joined:
    Jul 20, 2010
    Posts:
    40
    Hi everyone,

    i have a little issue with toon outline shader and depth of field. when dof is on, my toon shaded objects outline gets blurred even though it's on the foreground and in focus.. is there a way around this? i tried using double cameras and having character on one and the environment on another, but its not really my problems solution..
    anyone can offer help?

    thanks a lot...
     
  2. chkkll

    chkkll

    Joined:
    Jul 20, 2010
    Posts:
    40
  3. ole

    ole

    Unity programmer

    Joined:
    Sep 30, 2010
    Posts:
    56
    yep, the problem is that outlines don't have a depth value, so the DOF will just defocus based on the original depth value. quite some legword should be needed here. you could e.g. modify the DOF and EdgeDetect shaders to accomplish a better look:

    1) store an outline mask (result of edge detection filter) along with the *minimum* depth value from the depth comparison check in a texture
    2) after calculating the circle of confusion (the first step in the DOF effect), update the COC based on the depth vales from the texture in 1) for all outlines
    3) continue DOF as usual

    this might totally not work at all, but is all i can spontaneously think of.
     
  4. dogzerx2

    dogzerx2

    Joined:
    Dec 27, 2009
    Posts:
    3,971
    what about making an outline by duplicating the mesh and inverting the normals? that'd have DOF, right?
     
  5. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,364
    It should but it doesn't, anyone has any idea?
    The toon outline shader has ZWrite on so it should write to depth.

    Code (CSharp):
    1.     SubShader {      
    2.         LOD 900      
    3.         UsePass "Toon/Basic/BASE"
    4.        
    5.         Pass {  
    6.             Tags { "Queue"="Geometry-1" "RenderType"="Opaque" }
    7.             Name "OUTLINE"
    8.             Tags { "LightMode" = "ForwardBase" }
    9.             Cull Front
    10.             ZWrite On
    11.  
    12.             CGPROGRAM
    13.             #pragma vertex vert
    14.             #pragma fragment frag
    15.             half4 frag(v2f i) :COLOR { return _OutlineColor; }
    16.             ENDCG
    17.         }
    18.     }
    19.  
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The depth used for image effects with the forward rendering path comes from a depth prepass which uses the shadow caster pass(es) in your shader. Whether or not your shader's forward pass does zwrite or not is actually irrelevant. Furthermore all passes zwrite by default so adding ZWrite On to a shader that otherwise does not define that behavior will have no effect.
     
  7. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,364
    ok - solution?
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You effectively need two shader caster passes, one for the base mesh and a second custom one for the outline that disables itself when casting shadows.

    If you add a shadowcaster pass to the shader that uses the same surface offsetting code that the outline shader uses you'll get it to show up in the depth, but disabling it from rendering in the shadows is a bit of a pain.

    Unfortunately there's no 100% way to test in the shadowcaster shader if it's currently being rendered for the camera depth texture or a shadow map. Unity uses unity_LightShadowBias.z > 0.0 in their own code, but this humorously only works on directional lights as it's zero for camera depth, spot lights, and point lights. Using unity_LightShadowBias.x > 0.0 is a little more reliable as long as you have at least a bias set on all of your lights, which you should. The unity_LightShadowBias.x value is the light's bias, and unity_LightShadowBias.z is supposed to be the normal bias though as mentioned it appears to be disabled at least in 5.4.

    Now if you add a custom shadowcaster pass to your shader the main model will no longer cast shadows or show up in the depth! The reason for this is the shadow caster pass for the main body was coming from the Fallback, but it sees a shadowcaster pass already exists in the shader and only the first one gets used. So now you can only choose to get the depth from the outline or depth and shadows from the body but not both!


    So the answer is there is no solution, at least not with basic shaders alone. So now what?


    You can split up the outline and main body into two separate objects and apply separate shaders on each. Has the benefit of you can just disable shadow casting on the outline mesh object. This is kind of clunky though.

    You can use the deferred rendering path and write custom deferred toon shaders as in the deferred rendering path the depth really does come from if the shader has ZWrite on or off. But that's not good for mobile or low end machines and you're paying the heavy cost of the deferred pipeline.

    You can use geometry shaders to double the geometry instead of doing it in two passes. This isn't something I'm going to spend the time to implement.

    You can double the geometry yourself in the model rather than rely on the shader. This is easily the best option, though it'd take some additional shader work to get the outlines to work the same it would still be possible. A bonus is now your objects use half the draw calls!

    We can complain at Unity to let more than one shadow caster pass in a shader get used. Since this has been an issue for 5 years I doubt that'll get changed any time soon.