Procedural Hatching

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!