Search Unity

outline by pushing vertex away by a normal.

Discussion in 'Shaders' started by CauseMoss, Dec 4, 2019.

  1. CauseMoss

    CauseMoss

    Joined:
    Sep 16, 2017
    Posts:
    17

    I want to achieve a simple outline. you know: scale original object a bit and render it before the actual object. Standard stuff. But I want to do it by adding a normal (multiplied by a user defined factor ) to the mesh vertex position (pushing the vertex out in the normal direction), which should guarantee that the outline width will not depend on object size. unlike when you just scale vertex by a factor like 1.2 to make it a 20% bigger so the bigger objects have bigger outlines and in that case you have assume that the mesh origin is in the actual center. so my approach seems to be better, but it gives me the same frailty. the thick objects have thicker outline which i completely don't understand. where's the fault in my thinking?

    Code (CSharp):
    1.                 v2f o;
    2.                 float3 ver = v.vertex.xyz + v.normal.xyz * _OutlineWidth;
    3.                 o.vertex = UnityObjectToClipPos(ver);
     
  2. mouurusai

    mouurusai

    Joined:
    Dec 2, 2011
    Posts:
    311
    Try to normalize push direction
    float3 ver = v.vertex.xyz + normalize(v.normal.xyz) * _OutlineWidth;
     
  3. CauseMoss

    CauseMoss

    Joined:
    Sep 16, 2017
    Posts:
    17
    it's normalized by default, but i tried it just to be sure.
     
  4. mouurusai

    mouurusai

    Joined:
    Dec 2, 2011
    Posts:
    311
    I suppose you use scaled objects? Try push in world space then. Instead of use UnityObjectToClipPos macros multiply vertex position with object to world matrix(you get positions in world space), transform normals to word space, push positions, then multiply by vp matrix.
     
    Last edited: Dec 4, 2019
    CauseMoss likes this.
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,694
    The vertex position you pass into the
    UnityObjectToClipPos()
    function is assumed to be in object space (hence the name of the function). Since it's in object space, it'll be affected by the object's scale. So if you move the object space vertex by 1 unit along the object space normal, if the object is scaled 200% it's now going to be 2 units when rendered.

    What you need to do instead is make sure you're moving the vertex position in world space. The
    UnityObjectToClipPos()
    function looks like this:
    Code (csharp):
    1. inline float4 UnityObjectToClipPos(in float3 pos)
    2. {
    3.     return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
    4. }
    There's actually a little more in that function if you look at the source files, but that's for special VR rendering that 99% of people will never use, so I removed it. The main two things is the two
    mul()
    calls and the matrices they're using. One is transforming the object space position into world space, and the second is transforming the world space into clip space. If you want to know what clip space is, search online for homogeneous clip space and enjoy that rabbit hole, or search for some of my posts on this forum on the topic. TLDR, you can think of it as screen space for simplicity (but it's totally not).

    So for a world space outline, we can skip using the built in function and instead call each
    mul()
    separately and modify the world space before transforming to clip space.
    Code (csharp):
    1. float4 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0));
    2. float3 worldNormal = UnityObjectToWorldNormal(v.normal);
    3. worldPos.xyz += worldNormal * _OutlineWidth;
    4. o.vertex = mul(UNITY_MATRIX_VP, worldPos);
    Basically, what @mouurusai said.
     
    CauseMoss likes this.
  6. CauseMoss

    CauseMoss

    Joined:
    Sep 16, 2017
    Posts:
    17
    Yeah! the scale of the objects was the issue!
    Thank you both!