Making a local pixelation image effect shader

Good day everyone. For the last few days i’ve been trying to make a pixelation image effect for unity pro, with a pixel shader, but without success.

I want to make a defined rectangular area of the screen to go pixelated. The exact effect is like the one shows in the sims games, when the game needs to censor them. Like this.
1252076--54285--$MTS_grimreefer24601-1216792-NakedSleep.jpg

I’ve been trying a lot of things and searched over the web for hlsl tutorials, reference and whatever, but i cannot make it. The only i got to work is making the effect by converting RenderTexture to Texture2D and using SetPixels() in the selected area, but it is obviously very slow. I need a pixel shader for this.

Any help is very appreciated. A link to a tutorial, a piece of code, slight advice, whatever is useful.

Thanks in advance. :slight_smile:

did you look into the compute shader examples there is an averagePixel CS you just have to modify it to your needs so you can select the area that is effected

https://dl.dropboxusercontent.com/u/1119248/DX11Examples-4.0.0b7.zip

The easiest way to do this for all targets is to look at the SSAO C# source code, which shows you how to downsample the rendertexture into a temporary, and then set it as a texture variable for your image effect. (Remember to set the RenderTexture.filterMode to FilterMode.Point so you get a pixelation effect as opposed to a blurring effect)

In your Image Effect shader, simply use the vert_img vertex shader, which uses the v2f_img struct to send information to the fragment shader (The source of these is in UnityCG.cginc, which you should refer to for the syntax). In the fragment shader, Use the uv in the v2f_img as a position for simplicity,which you may have to invert the y value of if UNITY_UV_STARTS_AT_TOP is set(look at the vignette shader for how to do this), or the position value, depending on how you want to specify the pixelated area. Then simply set the colour from the main texture in the non-pixelated area, and the colour from the downsampled texture in the pixelated area.

That should work :slight_smile:

First, thanks a lot for fast answers :slight_smile:

I’ve been experimenting with the down-sampling of the texture (without going into shaders) and it’s going perfect. I’m now trying to make the shader, but I’m still very unexperienced, yet I think I have the bare-bones of the shader.

Shader "Hidden/Pixelation Effect"
{
  Properties
  {
    _MainTexture ("Main Texture", 2D) = "white" {}
    _DownsampledTexture ("Downsampled Texture", 2D) = "white" {}
  }
  
  SubShader
  {
    Pass
    {
      ZTest Always Cull Off ZWrite Off
      Fog { Mode off }
      
      CGPROGRAM
      #pragma vertex vert_img
      #pragma fragment frag
      #pragma fragmentoption ARB_precision_hint_fastest
      
      #include "UnityCG.cginc"
      
      uniform float4 _RectangleArea;
      
      float4 frag(v2f_img i) : COLOR
      {
        // Do pixelation effect
      }
      ENDCG
    }
  }
  
  Fallback off
}

My code is prepared to pass both textures to the material. I’d like to ask how do you combine the two textures by a defined area in the shader code, i don’t now how to at this time. And ask why Unity complains for a syntax error at the line “CGPROGRAM”.:face_with_spiral_eyes:

PS: The DX11 examples were very good, but i have to make this in DX10 and DX9. Sorry, my fault for not saying.
:smile:Thanks a lot:smile:

You virtually had it. This should work:

Shader "Hidden/Pixelation Effect"
{
  Properties
  {
    _MainTex ("Main Texture", 2D) = "white" {}
    _DownsampledTexture ("Downsampled Texture", 2D) = "white" {}
  }

      
Subshader {
      ZTest Always Cull Off ZWrite Off
      Fog { Mode off }

 Pass {

      CGPROGRAM
      #pragma vertex vert_img
      #pragma fragment frag
      #pragma fragmentoption ARB_precision_hint_fastest
      
      #include "UnityCG.cginc"
      
      uniform float4 _RectangleArea;
      sampler2D _MainTex;
      sampler2D _DownsampledTexture;
      float4 _MainTex_TexelSize;

      float4 frag(v2f_img i) : COLOR
      {
      	// make sure uv is the right way up
      	#if UNITY_UV_STARTS_AT_TOP
		if (_MainTex_TexelSize.y < 0)
			 i.uv.y = 1.0 - i.uv.y;
		#endif
		
		// start with original render texture
		float4 col = tex2D(_MainTex,i.uv);
		
		// if within rectangle, replace with downsampled version.
		if(i.uv.x > _RectangleArea.x  i.uv.x < _RectangleArea.z)
		{
			if(i.uv.y >_RectangleArea.y  i.uv.y < _RectangleArea.w)
			{
				col = tex2D(_DownsampledTexture,i.uv);
			}
		}

        return col;
      }
      
      ENDCG
      
   }
}
  Fallback off
}

My hands aren’t that great at the moment, so there’s a lot of cut paste there, so check it makes sense:) (it compiles, and looks right!)

I think the easiest and fastest approach here is to use a GrabPass and stepped sampling. It can all be done in a single shader this way, and gives you the twinkling aliased effect rather than the smooth one you’d get from averaging:

Shader "Pixelate" {
	Properties {
		_CellSize ("Cell Size", Vector) = (0.02, 0.02, 0, 0)
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		GrabPass { "_PixelationGrabTexture"}
		
		Pass {
			CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#include "UnityCG.cginc"
			
				struct v2f {
					float4 pos : SV_POSITION;
					float4 grabUV : TEXCOORD0;
				};
				
				float4 _CellSize;
				
				v2f vert(appdata_base v) {
					v2f o;
					o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
					o.grabUV = ComputeGrabScreenPos(o.pos);
					return o;
				}
			
				sampler2D _PixelationGrabTexture;
			
				float4 frag(v2f IN) : COLOR {
					float2 steppedUV = IN.grabUV.xy/IN.grabUV.w;
					steppedUV /= _CellSize.xy;
					steppedUV = round(steppedUV);
					steppedUV *= _CellSize.xy;
					return tex2D(_PixelationGrabTexture, steppedUV);
				}
			ENDCG
		}
	}
}

Thanks a lot gaustwick!!!:grin: I got it working now the exact way i wanted, and i even got the grasp for making other area effects if i need to.
Thanks a lot Daniel too, your method works awesome too, but i do not know right now how to modify it so it only affects a rectangular area of the viewport.

Only a slight question to gaustwick…

In the vignetting shader, the “UNITY_UV_STARTS_AT_TOP” thingy check is in the vertex function, but in the shader code you showed to me, it’s in the fragment function. What’s the difference in putting the check in one function or the other? I ask only because i want to learn more.

It’s a shader you can put on any object. If you want the effect to move with the camera, put it on a mesh that is a child of your camera.

So, if i put it in the material of some mesh, it does render whatever is behind pixelated?:face_with_spiral_eyes:

Yes. Additionally, you could put it on a cube or whatever that fully surrounds your object, ensuring that it is pixelated appropriately from every angle.

I’m testing it right now, and it’s awesome, it lets me have even finer control of the effect. Thanks a lot!!! :smile:

I’m glad it works for you. Note that you can control the cell size in the material, but Z and W don’t do anything.

A bit late, as you’re using Daniel’s far more versatile method, but the only difference is speed, as it is being called every pixel as opposed to only 4 times if it was in the vertex shader (image effects make a single quad for the screen, iirc). I thought that for simplicity’s sake, it might as well go in the pixel shader, as on modern graphics cards it wouldn’t really be a performance problem and may even be simplified by the shader optimizer in the driver. If you did ever use that method and you found it to be a bottleneck, moving it to the vertex shader would be fine, as the algorithm would be the same.

Nonetheless, I would use Daniel’s version :smile:

A bit late too, but thanks for explaining so clear to me.

That’s awesome, I was trying to do something just like this but I was dividing by grabUV.w after I applied the stepping to the uv, so it looked weird when viewing from an angle. I don’t really understand why this happens though, like I know that ComputeGrabScreenPos puts hpos.w into grabUV.w which I think means it’s the distance from the camera to the vertex, but why am I dividing the uv by that? I also tried looking at ComputeGrabScreenPos, this is the gist of it:

float4 o = pos * 0.5f;
o.xy = float2(o.x, o.y) + o.w;
o.zw = pos.zw;

I don’t really understand how that works though, why is it adding o.w (pos.w/2)?

Sorry for the word-vomit, I’m not very good with shader-math :face_with_spiral_eyes:

Also, I adjusted my version of the shader to have a universal _PixelSize float rather than a vector, and you can make the pixels the same size regardless of screen resolution with something like this:

float4 frag( frag_in f ) : COLOR 
{
	float2 uv = f.grabUV.xy;

	uv /= _PixelSize / _ScreenParams.xy;
	uv = round( uv );
	uv *= _PixelSize / _ScreenParams.xy;

        return tex2D( _GrabTexture, uv );
}

Alternatively when you set _CellSize just incorporate Screen.width and Screen.height via a script.

Tried to adapt Daniel’s shader and it works beautifully on the PC, but the shader doesn’t seem to work on a Mac with integrated Intel graphics. I’ve very little knowledge of shaders so I’m not sure what’s causing the issue. Any ideas?

Is it possible to make a shader that does this that doesn’t use post processing?

I had a problem with this shader where when I re-opened Unity 5, the shader ceased to function. However, it would function upon creation. Turns out there was a problem with the Render Queue. Here’s my update to the shader which works beautifully.

Shader "Custom\Pixelate"
{
    Properties
    {
        _CellSize ("Cell Size", Vector) = (0.02, 0.02, 0, 0)
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue" = "Transparent" }
        LOD 200
     
        GrabPass { "_PixelationGrabTexture"}
     
        Pass {
            CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"
         
                struct v2f {
                    float4 pos : SV_POSITION;
                    float4 grabUV : TEXCOORD0;
                };
             
                float4 _CellSize;
             
                v2f vert(appdata_base v) {
                    v2f o;
                    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                    o.grabUV = ComputeGrabScreenPos(o.pos);
                    return o;
                }
         
                sampler2D _PixelationGrabTexture;
         
                float4 frag(v2f IN) : COLOR
                {
                    float2 steppedUV = IN.grabUV.xy/IN.grabUV.w;
                    steppedUV /= _CellSize.xy;
                    steppedUV = round(steppedUV);
                    steppedUV *= _CellSize.xy;
                    return tex2D(_PixelationGrabTexture, steppedUV);
                }
            ENDCG
        }
    }
}

I use this shader in Unity 4.5.5. But I ran into a little detail problem. I basically want to render all objects on all of my sortinglayers as pixelated except for one. The GUI sortingLayer. But it doesn’t seem to take it into account.
if the pixelateShader object with the pixelate_Material on it is physically between the camera and the other object that is on a different layer then default, the object (that is on a different layer) is not rendered at all.
I hope the sentence makes sense at least.
I tried setting the sortingLayer of the pixelateshader by code to the GUI layer, while setting the order to something ridiculously low. But that did not help me, except that it didn’t matter where in the scene the pixelateObject was placed for pixelating the default sortinglayer.
Do you have any tips/directions?

Edit:
Adding the line

ZWrite Off Lighting Off Cull Off Fog { Mode Off }

right under the setting of the renderqueue seemed to do the trick. Sorry I don’t know what it does exactly, but I imagine the Cull Off is the important part.
Now I set the sortinglayer and order in code and it seems to listen to that setting