rbjacob
rbjacob

Reputation: 333

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

Upvotes: 2

Views: 2951

Answers (2)

rbjacob
rbjacob

Reputation: 333

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.

Upvotes: 1

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

That would be correct. Or mostly correct (particle systems combine all the particles into one runtime mesh and that's what your shader is applied to, not the underlying individual particle mesh, which isn't obvious). Try your outline shader on a non-convex mesh (that is also not a particle): You'll find that the concave part won't have the desired outline, confirming your suspicion.

I wrote this shader a couple of years back because the only free shaders I could find that generated outlines were either (a) not free or (b) of the "just scale it bigger" variety. It still has problems (such as getting jagged and weird at large thickness values), but I was never able to resolve them satisfactorily. It uses a geometry pass to turn the source mesh's edges into camera-facing quads, then stencil magic to render only the outline portion.

However I am unsure if that shader will function when applied to particles. I doubt it will without modification, but you're free to give it a shot.

Upvotes: 0

Related Questions