Masking and clipping textures using shaders in NGUI

A question that showed up on Tasharen’s forum, where I happen to be a moderator, was this one:

(..) I have a panel that’s draggable and clipped, which displays a list of GameObjects, each one composed of usernames, avatars, and some other text.

The avatars are not square, they have an hexagonal mask. To achieve this, they are Texture widgets with a special shader I found here. (..) when they get clipped by the panel, they keep showing even outside the boundaries. Only when completely out of sight, they get hidden(..).

Source: http://www.tasharen.com/forum/index.php?topic=5621

First: the reason it gets disabled when its entirely outside the clip area is the way NGUI culls widgets by simply not drawing them. It’s not that the gameObject is disabled, but the UIPanel does not feed this widget into the UIDrawCall’s geometry which is the thing that gets drawn on screen.

The shader he used for the masking was obtained at http://www.zedia.net/2013/masking-textures-using-shaders-ngui/ who based the shader on the old non-clipping version of NGUIs builtin Transparent Alpha shader (it has since changed to not use the combine keyword).

This doesn’t work with the different clipping options that NGUI uses (Alpha Clipping, Soft Clipping) since the UIPanels change the shader used and also feed in the coordinates where it needs to be clipped. It’s very worth noting that the way the UIPanel chooses shaders is entirely based on the name of the shader. This means that when you make a new shader, you should make a similar named shader but with ” (AlphaClip)” added to it, so it’s used when alpha cliping is enabled.

// If clipping should be used, we need to find a replacement shader
if (useClipping && mClipping != Clipping.None)
{
Shader shader = null;
const string alpha = " (AlphaClip)";
const string soft = " (SoftClip)";

// Figure out the normal shader's name
string shaderName = mSharedMat.shader.name;
shaderName = shaderName.Replace(alpha, "");
shaderName = shaderName.Replace(soft, "");

// Try to find the new shader
if (mClipping == Clipping.HardClip ||
mClipping == Clipping.AlphaClip) shader = Shader.Find(shaderName + alpha);
else if (mClipping == Clipping.SoftClip) shader = Shader.Find(shaderName + soft);

Source: UIDrawCall.cs in NGUI.

Note also that the former HardClipping is entirely deprecated and just changes to be AlphaClip under the surface.

Here’s a shader that works with Alpha Clipping based on the Transparent Colored (AlphaClip) builtin shader in NGUI:

Shader "Custom/MaskClipShader (AlphaClip)" 
{
  Properties 
  {
    _MainTex ("Base (RGB), Alpha (A)", 2D) = "white" {}
    _AlphaTex ("MaskTexture", 2D) = "white" {}
  }

  SubShader
  {
    LOD 100

    Tags{
      "Queue" = "Transparent"
      "IgnoreProjector" = "True"
      "RenderType" = "Transparent"
    }
     Pass
		{
			Cull Off
			Lighting Off
			ZWrite Off
			Offset -1, -1
			Fog { Mode Off }
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			sampler2D _MainTex;
			float4 _MainTex_ST;
			sampler2D _AlphaTex;

			struct appdata_t
			{
				float4 vertex : POSITION;
				half4 color : COLOR;
				float2 texcoord : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex : POSITION;
				half4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				float2 worldPos : TEXCOORD1;
			};

			v2f vert (appdata_t v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.color = v.color;
				o.texcoord = v.texcoord;
				o.worldPos = TRANSFORM_TEX(v.vertex.xy, _MainTex);
				return o;
			}

			half4 frag (v2f IN) : COLOR
			{
				// Sample the texture
				half4 col = tex2D(_MainTex, IN.texcoord) * IN.color;
				half4 a2 = tex2D(_AlphaTex, IN.texcoord);

				float2 factor = abs(IN.worldPos);
				float val = 1.0 - max(factor.x, factor.y);

				// Option 1: 'if' statement
				if (val < 0.0) col.a = 0.0;
				if (a2.a < col.a) col.a = a2.a;

				// Option 2: no 'if' statement -- may be faster on some devices
				//col.a *= ceil(clamp(val, 0.0, 1.0));

				return col;
			}
			ENDCG
		}
  }
}

You will have to use the non clipping shader that was provided on the other blog or make one based on the new version of the Transparent Colored shader in NGUI. This shader will get all sorts of weird if you use it in a non-clipping panel.

Clipping and masking using a modified example 7 in NGUI
Clipping and masking using a modified example 7 in NGUI
Custom material with a alpha-8 mask texture

I use a custom Material which I put onto a UITexture. If you instantiate the material, you can feed it different mainTextures such as facebook images or something similar.

I hope this will be helpful to someone.

Nicki.

15 thoughts on “Masking and clipping textures using shaders in NGUI

  1. I am the one who did the original shader and I really thank you for this. I am new to shaders so when I did it I was surprised it even worked. This post is very informative and luckily for me in 2 days I will work on something which requires clipping and masking, so you just provided the way to do it. Thanks a lot!

  2. Hey again, I was reading the new shader I made for masking and I realized that all the variable names were one letter and I figured that that might not be helpful for someone willing to learn about shaders. I renamed most of the variables from what I could figure out, but I still have 2 that I don’t know what they mean. I was wondering if you could enlighten me. Those are appdata_t (what does the ‘t’ stands for?) and the v in the vert function (I assume it could be inputVertex ??).

    Also, I see that there is a float4 _MainTex_ST defined in the CGPROGRAM but it is never used. Do you have any idea why that is there?
    Thanks again

  3. So you’re getting into some of the deeper magics of shaderlab. I’m by no means an expert, but I do know how to look up stuff. 😉

    appdata_t is just a variable name, so you can change it to whatever. The name itself seems to be a leftover from somewhere – I honestly don’t know from where.

    The MainTex_ST is a convention that Unity uses to apply texture tiling and offsets. Source: http://forum.unity3d.com/threads/11346-Displacing-vertex?p=79913&viewfull=1#post79913

  4. Hi, is there a tutorial on how to use this shader?
    I really want to learn more about this but don’t know where to start.

    1. Hey Ray, I’m not entirely sure it will work anymore, since the alphaclip shader has been removed in favor of just using soft clipping with an edge of 0.

      What you want to look into is how you can set up your own materials – there you can select the shader you want, which exposes those settings you see in the bottom picture.

      In NGUI’s UITexture you can select your own material rather than texture, which makes it use whatever shader happens to be on your material.

      Does that help you?

  5. So I’m using NGUI and looking to create a custom shader that will display a texture, and apply a dynamically created alpha mask texture to it. Essentially, the end goal is to be able to draw on the texture in game with your finger and the spots where you touch will ‘erase’ the texture by modifying an alpha mask. I don’t really know where to start, and haven’t made any shaders myself in Unity yet. Any tips?

    1. You can generate a texture with http://docs.unity3d.com/ScriptReference/Texture2D.SetPixel.html you just have to match your are to the texture coordinates.

      In the shader you would simply have the visual texture in emission and your generated alpha mask connected to alpha. As you drag your finger on it, write the texture coordinates around your finger towards 0 so it gets see through.

      I would recommend you invest in ShaderForge as it makes everything so much easier.

  6. Hi, I used both of the shaders (masked and masked with soft clip). I have only one problem with clipped version. If i make transparent area of my atlas white then this shader doesnt work. Although the masked shader without clipping works. I am making transparent area white because when we compress a transparent texture in unity its more prone to artefacts where as if we fill transparent area with white its much better.

Leave a Reply

Your email address will not be published. Required fields are marked *