Simple Toon Shading Problem

Hey! I’m trying to apply simple shaders in Unity but I have a little problem with my shader code.

Shader "Hidden/Toon Effect" {
	Properties {
		_MainTex ("Base (RGB)", RECT) = "white" {}
		_LightDir ("Light Direction", Vector) = (5.2,10,1,1)
	}

	SubShader {
		Pass {
			ZTest Always Cull Off ZWrite Off
			Fog { Mode off }
					
			CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#pragma fragmentoption ARB_precision_hint_fastest 
				#include "UnityCG.cginc"

				uniform samplerRECT _MainTex;
				uniform float4 _LightDir;
				
				struct v2f {
					float4 vertex : POSITION;
					float3 normal;
					float2 texcoord;
				};
				
				v2f vert (appdata_base v) {
                    v2f o;
                    o.vertex = mul(glstate.matrix.mvp, v.vertex);
                    o.normal = mul((float3x3)glstate.matrix.invtrans.modelview[0], v.normal );   
                    o.texcoord = float2(v.texcoord);
                    return o;
                }
				
				float4 frag (v2f data) : COLOR  {	
					float intensity = 0.0;
					float cos = dot(data.normal,float3(_LightDir));
					float4 pixelColor = texRECT(_MainTex,data.texcoord);
					
					if (cos > 0.95)  {
						intensity = 1;	
					} else if (cos > 0.65) {
						intensity = 0.7;
					} else if (cos > 0.25)  {
						intensity = 0.55;
					} else {
						intensity = 0.3;
					}
					
					pixelColor.rgb *= intensity;
					
					return pixelColor;
				}

			ENDCG

		}
	}

	Fallback off

}

My code does not detect different levels, it only applies the effects to the last condition, any ideas on what’s wrong?

Quick guess: You need to dot against the negative lightdir.

Edit: Oh - and you may want to normalize the LightDir.

Shader "Hidden/Toon Effect" {
	Properties {
		_MainTex ("Base (RGB)", RECT) = "white" {}
		_LightDir ("Light Direction", Vector) = (5.2,10,1,1)
	}

	SubShader {
		Pass {
			ZTest Always Cull Off ZWrite Off
			Tags { "LightMode" = "Always" }
					
			CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#pragma fragmentoption ARB_precision_hint_fastest 
				#include "UnityCG.cginc"

				uniform samplerRECT _MainTex;
				uniform float4 _LightDir;
				
				struct v2f {
					float4 vertex : POSITION;
					float3 normal;
					float2 texcoord;
				};
				
				v2f vert (appdata_base v) {
                    v2f o;
                    o.vertex = mul(glstate.matrix.mvp, v.vertex);
                    o.normal = mul((float3x3)glstate.matrix.invtrans.modelview[0], v.normal );   
                    o.texcoord = float2(v.texcoord);
                    return o;
                }
				
				float4 frag (v2f data) : COLOR  {	
					float intensity = 0.0;
					float3 norm = normalize(float3(_LightDir));
					float cos = dot(data.normal,-norm);
					
					float4 pixelColor = texRECT(_MainTex,data.texcoord);
					
					if (cos > 0.95)  {
						intensity = 1;	
					} else if (cos > 0.65) {
						intensity = 0.7;
					} else if (cos > 0.25)  {
						intensity = 0.55;
					} else {
						intensity = 0.3;
					}
					
					pixelColor.rgb *= intensity;
					
					return pixelColor;
				}

			ENDCG

		}
	}

	Fallback off

}

I changed the code but nothing different happened. Any ideias?

I tried your shader, and it seems to work somewhat (at least better) if you replace texRECT with tex2D. Not sure why, though.

Edited the script a bit (not saying that this is the best solution):

Shader "Hidden/Toon Effect" {
   Properties {
      _MainTex ("Base (RGB)", 2D) = "white" {}
      _LightDir ("Light Direction", Vector) = (5.2,10,1,1)
   }

   SubShader {
      Pass {
         //ZTest Always 
         //Cull Off 
         //ZWrite On
         Tags { "LightMode" = "Always" }
               
         CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest
            #include "UnityCG.cginc"

            uniform sampler2D _MainTex;
            uniform float4 _LightDir;
            
            struct v2f {
               float4 vertex : POSITION;
               float3 normal;
               float2 texcoord;
               float3 objSpaceLightDir;
            };
            
            v2f vert (appdata_base v) {
                    v2f o;
                    o.vertex = mul(glstate.matrix.mvp, v.vertex);
                    o.normal = v.normal;   
                    o.texcoord = float2(v.texcoord);
                    
                    float4 lightDir = _LightDir;
                    lightDir.w = 0.0;
                    
                    o.objSpaceLightDir = mul(_World2Object, normalize(lightDir)).xyz;
                    
                    return o;
                }
            
            float4 frag (v2f data) : COLOR  {   
               float intensity = 0.0;
               
               float cos = dot(data.normal,-data.objSpaceLightDir);
               
               float4 pixelColor = tex2D(_MainTex,data.texcoord);
               
               //An alternative here is a ramp-type texture. Not sure what is recommended.
               if (cos > 0.95)  {
                  intensity = 1;   
               } else if (cos > 0.65) {
                  intensity = 0.7;
               } else if (cos > 0.25)  {
                  intensity = 0.55;
               } else {
                  intensity = 0.3;
               }
               
               pixelColor.rgb *= intensity;
               
               return pixelColor;
            }

         ENDCG

      }
   }

   Fallback off

}

Every time that I tried to use 2D instead of RECT, this error appears: Error assigning 2D rectangle texture to 2D texture property ‘_MainTex’: Dimensions must match

And it happened again with this shader that you posted =/
Do you know why that happens?
Did I forget to import something?

Thx for the help

Well…
If this could help, I’ll post the C# code

void OnRenderImage (RenderTexture source, RenderTexture destination) {
		ImageEffects.BlitWithMaterial (material, source, destination);
	}
public static void BlitWithMaterial (Material material, RenderTexture source, RenderTexture destination)
	{
		RenderTexture.active = destination;		
		material.SetTexture("_MainTex", source);
		
		/*material.hideFlags = HideFlags.HideAndDontSave;
		material.shader.hideFlags = HideFlags.HideAndDontSave;*/
		
		GL.PushMatrix ();
		GL.LoadOrtho ();
		
		for (int i = 0; i < material.passCount; i++) {
			material.SetPass (i);
			ImageEffects.DrawQuad();
		}
		GL.PopMatrix ();
	}

Sorry!
I forgot to post the DrawQuad function

public static void DrawQuad()
	{
		GL.Begin (GL.QUADS);		
		GL.TexCoord2( 0.0f, 0.0f ); GL.Vertex3( 0.0f, 0.0f, 0.1f );
		GL.TexCoord2( 1.0f, 0.0f ); GL.Vertex3( 1.0f, 0.0f, 0.1f );
		GL.TexCoord2( 1.0f, 1.0f ); GL.Vertex3( 1.0f, 1.0f, 0.1f );
		GL.TexCoord2( 0.0f, 1.0f ); GL.Vertex3( 0.0f, 1.0f, 0.1f );
		GL.End();
	}

Aha, I understand. Yeah, that texture is probably a RECT texture. Anyway - are you sure you want to apply that shader as a post-effect? What results do you expect? I assumed you were using it on a per-object basis.

Sorry for the mistake. I really want to apply the shader on a per-object basis, but I don’t know how to do this. I used this code because that was the shader code in Island Demo and ALL shaders in that project are post-effect (I see this clear now)
How do I apply this shader in per-object basis?

I am newbie in ShaderLab and CGProgram. I coded shaders in HLSL a long time ago.

Another question. What the basic differece between 2D and RECT? I didn’t see something like this in HLSL. It was just Texture2D

Right-click on your shader file and choose “Create Material”. Drag’n’drop this material onto your object. That should work as far as I know.

The difference between 2D and RECT is that 2D textures are looked up in the uv-range 0-1, while RECT uses 0-size (I’m not completely sure of this, as it seems like it is using 0-1 on windows - anyone, feel free to fill in/correct me on this). Also, RECT does not have to be power of two sized, which is currently the case for 2D textures in Unity.

Yeah! It works!!
Thanks ToreTank!!