Search code examples
unity-game-engineshadercgshaderlab

Unity particle outline shader


I wish to create an unlit shader for a particle system that emits cube meshes, such that each emitted mesh has a hard black outline around it.

Here is the pass for the outline (in Cg):

struct appdata {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
};

struct v2f {
    float4 pos : POSITION;
    float4 color : COLOR;
};

uniform float _Outline;
uniform float4 _OutlineColor;

v2f vert(appdata v) {
    v2f o;

    v.vertex *= ( 1 + _Outline);

    o.pos = UnityObjectToClipPos(v.vertex);

    o.color = _OutlineColor;
    return o;
}

half4 frag(v2f i) :COLOR { return i.color; }

(And after this is a simple pass to render the unlit geometry of the mesh itself...)

As you can see, we are simply stretching the vertices outward... but from what?

For a single cube mesh, the shader works perfectly:

Shader applied to a single cube mesh

However, when applied to a particle system emitting cube meshes, the shader breaks down:

Shader applied to particle system emitting cubes

My suspicion is that the line v.vertex *= ( 1 + _Outline); stretches the vertices outward from the object center, not the mesh center.

Does anyone have a replacement shader or insight on how to fix this problem?

Thanks, rbjacob


Solution

  • It turns out that I misconstrued the problem. When accessing the POSITION semantic of the vertices, you are getting the vertices of the emitted particles in world space; therefore, stretching the vertices by multiplying is actually just scaling them away from the world center.

    To access the vertices relative to each particle, we must be able to access each particle's mesh center from within the shader. To do this, we enable "Custom Vertex Streams" inside the Renderer module of the particle system and press the + button to add the Center stream.

    Custom vertex streams in Renderer module

    Now we can access TEXCOORD0 (or whatever is specified to the right of the Center stream in the particle renderer GUI) from the shader to get the mesh center in world space. Then we subtract the mesh center from each vertices position, scale outward, and add the mesh center back. And voila, each particle has an outline.

    Here are the final vert and frag snippets for the outline pass:

            struct appdata {
                float3 vertex : POSITION;
                float4 color : COLOR;
                float3 center : TEXCOORD0;
            };
    
            struct v2f {
                float4 pos : POSITION;
                float4 color : COLOR;
                float3 center : TEXCOORD0;
    
            };
    
            uniform float _Outline;
            uniform float4 _OutlineColor;
    
            v2f vert(appdata v) {
                v2f o;
                o.center = v.center;
                float3 vert = v.vertex - v.center;
                vert *= ( 1 + _Outline);
                v.vertex = vert + v.center;
                o.pos = UnityObjectToClipPos(v.vertex);
    
                o.color = _OutlineColor;
                return o;
            }
    
            half4 frag(v2f i) :COLOR { return i.color; }
    

    TLDR: Enable vertex streams, add a stream for the particle center, and access this value in the shader to scale individual vertices outward.