I’ve been using Shadershop as a tool to explore useful procedural functions. In this case I needed procedural hatching. A square wave was quick to define:
In this case, all that we need to do is call a couple of floor functions. Frequency, phase and amplitude are easy enough to adjust after the fact just as you would any other periodic function. (A simpler method to create a square wave is to use a rounding function, but I wanted to see where Shadershop’s approach took me.)
I then extrapolated this function into a vector2, skewed it with a 2×2 matrix, and voila. Shadershop makes figuring this kind of thing out much easier by using a visual workflow and graphing your function as you go. Very handy!

You can do something similar with a 1d random function fract(sin(x)*999) to achieve a periodic 2d noise pattern.

A simple crosshatch shader in unity can be made by adding two of the above square wave functions. In this case I used a min and max function to change the “pulse length” of the square wave. You can get a similar look by using the more common approach of establishing a sine wave which you then clip/threshold. I’m not sure which method would be faster… Here’s the basic shader:
Shader "Unlit/Test"
{
Properties
{
_Density ("Density", Range(1,400)) = 150
_Rotation ("Rotation", Range(0,360)) = 45.0
_Width ("Width", Range(0,1)) = 0.4
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
float _Density;
float _Rotation;
float _Width;
v2f vert (float4 pos : POSITION, float2 uv : TEXCOORD0)
{
v2f o;
o.vertex = UnityObjectToClipPos(pos);
o.uv = uv * _Density;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float sn, cs;
sincos(radians(_Rotation), sn, cs);
float2x2 mt = float2x2(cs, -sn, sn, cs);
float2 c = i.uv;
c.xy = mul ( c.xy, mt );
float hatch = max(abs(floor((c.x/2 - 0.5)/-1)
+ floor(c.x/2)+1),
abs(floor(((c.x-_Width)/2 - 0.5)/-1)
+ floor((c.x-_Width)/2)+1))
- min(abs(floor((c.y/2 - 0.5)/-1)
+ floor(c.y/2)+1),
abs(floor(((c.y-_Width)/2 - 0.5)/-1)
+ floor((c.y-_Width)/2)+1));
return hatch;
}
ENDCG
}
}
}
The problem with this kind of approach is that it’s overly complex. All of that min/max/floor stuff can be replaced with a much shorter step function. Same look, much more direct.
{
float sn, cs;
sincos(radians(_Rotation), sn, cs);
float2x2 mt = float2x2(cs, -sn, sn, cs);
float2 c = i.uv;
c.xy = mul ( c.xy, mt );
float hatch = abs(step(1-_Width,sin(c.x)))
+ abs(step(1-_Width,sin(c.y)));
return hatch;
}
I added some lighting to the shader and here’s the result. Procedural and written entirely in notepad!
