Treating an animated sprite as a mask?

I would like to make an animated sprite which serves as a kind of portal, allowing you to view into another world. However, because it's going to be a character, it also needs to be able to interact with other objects.

Here's an example of what I mean.

Ignoring the awful MSPaint art, in this example, moving the character would result in the stripes shifting as he walks. http://i112.photobucket.com/albums/n162/alienjesus/DemoMasking.jpg

This would be pretty easy to achieve in something like flash, but from everything I've seen in Unity for masking, it seems it would be rather complex to do in Unity. Does anyone have any idea how I'd go about it? Is there away it could be achieved using transparency with a solid outline for the character?

I'm pretty sure there is a way, one I'd try would involve using an alpha'd PNG for the character and another texture for the background, and play with the UV's or tiling offsets, but since I have no solid answer, I'll leave that for someone else.

I THINK that "linespacing" is the difference between font.lineHeight, and font.ascent.

1 Answer

1

Use two cameras. The one with the higher depth value needs to use the Depth Only clear mode, which only overwrites color where meshes it sees have materials that render color data. Make sure that the lower-depth camera doesn't render the layer that your character is on.

With this shader, we'll render into the depth buffer only, which, because it renders first (Background queue), will show whatever the lower-depth camera renders, as long as no other meshes are closer to the camera. You will need to create an RGBA texture, where the alpha channel is white where your character's body is, and black everywhere else:

Shader "Alpha Test Depth Mask" {

Properties {
    _MainTex ("Texture", 2D) = ""
}

Category {
    Tags {Queue = Background}
    ColorMask 0

    SubShader {Pass {
        GLSLPROGRAM
        varying lowp vec2 uv;

        #ifdef VERTEX
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
            uv = gl_MultiTexCoord0.xy;
        }
        #endif

        #ifdef FRAGMENT
        uniform lowp sampler2D _MainTex;
        void main() {
            if (texture2D(_MainTex, uv).a < .5) discard;
        }
        #endif      
        ENDGLSL
    }}

    SubShader {Pass {
        AlphaTest Greater 0.5
        SetTexture[_MainTex]
    }}
}

}

So there's the first step, creating a "hole" into the other "world". Next, you want a black outline. Black is easy, because it means we don't need any contribution of the character's texture. This is handy, because we can store the blending data in RGB, instead of A; A was already used, so the only choice is to use at least one other channel for blending data (if we want to keep this whole thing nice and compact in a single texture). Using all three is fine, and should lead to optimal compression:

Long story short, paint a black outline on a white background, in RGB (like you showed above).

Shader "Black Sprite Outline" {

Properties {
    _MainTex ("Texture", 2D) = ""
}

Category {
    Tags {Queue = Transparent}
    ZWrite Off
    Blend Zero SrcColor

    SubShader {Pass {
        GLSLPROGRAM
        varying lowp vec2 uv;

        #ifdef VERTEX
        void main() {
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
            uv = gl_MultiTexCoord0.xy;
        }
        #endif

        #ifdef FRAGMENT
        uniform lowp sampler2D _MainTex;
        void main() {
            gl_FragColor = texture2D(_MainTex, uv);
        }
        #endif      
        ENDGLSL
    }}

    SubShader {Pass {
        SetTexture[_MainTex]
    }}
}

}

It will look best if the "body" in the Alpha channel is bigger than the inner white negative space of the RGB outline. The black outline should be soft, and cover up the jagged aliased edges from the alpha test in the other shader. Unfortunately, you can't use two different queues, in a shader, so you're going to have to use two materials on the same mesh. (If we didn't put the outline in a late-queue shader, like Transparent, things would get really weird.)

This is an amazing response, I'll get right on it, thanks! This is my first time experimenting with shaders and the likes, so it might take me a while, but I'm looking forward to the results!

BTW, just to double check, this will work fine on the free version of unity right? I've had a fair few things not work due to them requiring pro features lately, and it's making me paranoid.

If it doesn't work for you with a free license, then it's a bug. (It's working for me, but I have seen some oddities under DirectX with ColorMasking.)

How do I add a second material to a mesh? The method I'm trying keeps replacing the original one.

Change the size of its materials array to 2.