Redditor u/pigrockets asked a few days ago on r/Unity3D “What’s do you call this kind of static pattern shader?”

I call it the “Screen space Texture Shader” and here is how to make one in Unity.

First of all: What do we need to make this?

We need some way to calculate the screen position of every point on your model so that you know what pixel of your screen space texture/static pattern goes where. In Unity you can get this position calculated for free!

And as you can see from the video you want the shader to have an outline to get that cartoony look! This we will accomplish with a vertex program, where we want to scale our model a little bigger depending on the width we want on our outline, and then we color this model without any lighting in a fragment program. This “pass” will render underneath the static pattern.

For this simple shader example we won’t make a custom lighting model, which we would do if we wanted a more cartoony look.

Create your custom shader

First you create your shader Create>Shader>Standard Surface Shader, and name it “ScreenSpaceTextureShader” or whatever you wan’t to call it! Open up the shader and you will see that you’ve got a lot of free code for your new shader:

Standard Shader Shader "Custom/ScreenSpaceTextureShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 Shader "Custom/ScreenSpaceTextureShader" { Properties { _Color ( "Color" , Color ) = ( 1 , 1 , 1 , 1 ) _MainTex ( "Albedo (RGB)" , 2D ) = "white" { } _Glossiness ( "Smoothness" , Range ( 0 , 1 ) ) = 0.5 _Metallic ( "Metallic" , Range ( 0 , 1 ) ) = 0.0 } SubShader { Tags { "RenderType" = "Opaque" } LOD 200 CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex ; struct Input { float2 uv_MainTex ; } ; half _Glossiness ; half _Metallic ; fixed4 _Color ; void surf ( Input IN , inout SurfaceOutputStandard o ) { // Albedo comes from a texture tinted by color fixed4 c = tex2D ( _MainTex , IN . uv_MainTex ) * _Color ; o . Albedo = c . rgb ; // Metallic and smoothness come from slider variables o . Metallic = _Metallic ; o . Smoothness = _Glossiness ; o . Alpha = c . a ; } ENDCG } FallBack "Diffuse" }

Okey! That’s a nice start! On the top we have

“Shader “Custom/ScreenSpaceTextureShader” {” This names our shader “ScreenSpaceTextureShader” and puts it in the Custom shader folder.

Then we have our properties to set up our materials from the inspector. You can actually skip the properties and set up your materials entirely from script, but that is another story.

Then there’s the Subshader. This is where the magic happens, the “Tags” defines certain parameters for the shader, mainly render order. You can set the LOD for your shader also.

In the CGPROGRAM you have your shader code.

“#pragma surface surf Standard fullforwardshadows” says that we will have a surface-function called “surf” using the Standard lighting model. And finally we want fullforwardshadows, which basically means we get shadows cast from all light sources in forward rendering.

“#pragma target 3.0” means we are targeting shader model 3.0, this gives a little nicer lighting i guess, but limits the compatibility of the shader, so you can remove this if you want it to work “everywhere”.

Next up is our definitions _MainTex is a sampler2D, _Glossiness is half, _Metallic is half and _Color is fixed4. float, half, and fixed is basically the same thing with different precisions, float being the most precise, then half which you should use alot for mobile optimization, and fixed which is the least precise.

Then we have our Input structure.

Add properties struct Input { float2 uv_MainTex; }; 1 2 3 struct Input { float2 uv_MainTex ; } ;

This sets up what we want to pass to our surface-function. Like our models UV info. Here we have uv_MainTex which is the first UV set of the rendered mesh.

Finally we have the surface-funtion:

Add properties void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } 1 2 3 4 5 6 7 8 9 void surf ( Input IN , inout SurfaceOutputStandard o ) { // Albedo comes from a texture tinted by color fixed4 c = tex2D ( _MainTex , IN . uv_MainTex ) * _Color ; o . Albedo = c . rgb ; // Metallic and smoothness come from slider variables o . Metallic = _Metallic ; o . Smoothness = _Glossiness ; o . Alpha = c . a ; }

“void” – we are not returning anything from this function. “surf” – like we said in our “#pragma surface surf” our function is called “surf”. “Input IN” – structure type Input, with variable name “IN” is passed to our surface-function. “inout SurfaceOutputStandard o” – what light model output structure we are using.

tex2D(_MainTex, IN.uv_MainTex) read the pixelcolor of our main texture in the xy position from the uv_map, this is multiplied with our _Color and we have our Albedo Color. Metallic and Smoothness is set directly in the inspector.

Now try making a material with this “Standard” Shader! Create a new material. I’ve called it “ScreenSpaceMaterial” and select our new custom shader like so:

The material should have an inspector view with these properties:

As you can see we have material Color, Albedo Texture, Smoothness and Metallic, but we need more properties for our Screen Space Texture Shader. We need a “screen space texture”, “outline color”, and a way to set our “outline width”.

So we add some new properties to our shader. _ScreenTex, _OutlineColor and _Outline.

Add properties Shader "Custom/ScreenSpaceTextureShader" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Main texture (RGB)", 2D) = "white" {} _ScreenTex("Screen space texture (RGB)", 2D) = "white" {} _OutlineColor("Outline Color", Color) = (0,0,0,1) _Outline("Outline width", Range(0.0, 0.03)) = .005 _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 } 1 2 3 4 5 6 7 8 9 10 Shader "Custom/ScreenSpaceTextureShader" { Properties { _Color ( "Color" , Color ) = ( 1 , 1 , 1 , 1 ) _MainTex ( "Main texture (RGB)" , 2D ) = "white" { } _ScreenTex ( "Screen space texture (RGB)" , 2D ) = "white" { } _OutlineColor ( "Outline Color" , Color ) = ( 0 , 0 , 0 , 1 ) _Outline ( "Outline width" , Range ( 0.0 , 0.03 ) ) = . 005 _Glossiness ( "Smoothness" , Range ( 0 , 1 ) ) = 0.5 _Metallic ( "Metallic" , Range ( 0 , 1 ) ) = 0.0 }

Our _ScreenTex defaults to all white, _Outline color defaults to black (0, 0, 0, 1), and our _Outline needs to be within the range 0.0 to 0.03 and defaults to 0.005. Save this and your material inspector will look like this.

The Screen space Texture implementation is quite simple compared to the outline so we’ll start there. We need to sample our _ScreenTex in the surface-function so add “sampler2D _ScreenTex;” like we have for our _MainTex. And we need to know what the screen position is in our surface-function so add “float4 screenPos” to the Input-structure. Like this:

define variables sampler2D _MainTex; sampler2D _ScreenTex; struct Input { float2 uv_MainTex; float4 screenPos; }; 1 2 3 4 5 6 7 sampler2D _MainTex ; sampler2D _ScreenTex ; struct Input { float2 uv_MainTex ; float4 screenPos ; } ;

When we add screenPos to our Input like this, with exactly that name Unity gives us this “for free”. We can also have worldPos, viewDir and others that is done by Unity. But we only need screenPos for this.

Now we want to get the screen position in normalized values and sample the texture. To get the normalized screenpos we do:

half2 screenUV = IN.screenPos.xy / IN.screenPos.w

then we sample the texture with this coordinate:

fixed4 sstc = tex2D(_ScreenTex, screenUV);

then we multiply this color with the color we already have and get this surface-function:

the finished surface function void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; half2 screenUV = IN.screenPos.xy / IN.screenPos.w; fixed4 sstc = tex2D(_ScreenTex, screenUV); o.Albedo = c.rgb * sstc.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } 1 2 3 4 5 6 7 8 9 10 11 void surf ( Input IN , inout SurfaceOutputStandard o ) { // Albedo comes from a texture tinted by color fixed4 c = tex2D ( _MainTex , IN . uv_MainTex ) * _Color ; half2 screenUV = IN . screenPos . xy / IN . screenPos . w ; fixed4 sstc = tex2D ( _ScreenTex , screenUV ) ; o . Albedo = c . rgb * sstc . rgb ; // Metallic and smoothness come from slider variables o . Metallic = _Metallic ; o . Smoothness = _Glossiness ; o . Alpha = c . a ; }

That is all you need for the screen space texture part. Save the shader and try it out! Add a screen space texture to your material and you should get a result like this:



In the next part we will wrap up the shader, by adding the outline! So long!