I'm making a puzzle game in Unity with sprite-based graphics. When the game is paused, I want to hide most game elements, because I don't want the player to be able to pause and leisurely think about their next move.

I could just make the sprites invisible, but I decided to do something more stylish; render sprites with pseudo-glitches, simulated in this shader:

Shader "Sprites/Glitch Sprite"
        [NoScaleOffset] _MainTex ("Sprite Atlas", 2D) = "white" {}
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
        [PerRendererData] _TextureRect ("Base Texture Rect", Vector) = (0, 0, 0, 0)
        // The position and size of this sprite on the original texture atlas


        Cull Off
        Lighting Off
        ZWrite Off
        Blend One OneMinusSrcAlpha

            #pragma vertex vert
            #pragma fragment frag
            #pragma target 3.0
            #pragma multi_compile_instancing
            #pragma multi_compile _ PIXELSNAP_ON
            #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
            #pragma instancing_options assumeuniformscaling
            // Sprites will not scale in my game

            #pragma instancing_options nolightmap
            #pragma instancing_options nolightprobe
            // My game does not use lighting (it's all pixel art)

            #include "UnitySprites.cginc"

                UNITY_DEFINE_INSTANCED_PROP(float4, _TextureRect)
                // Sprites are GPU-instanced where supported

            // These four properties are set once, when the game is first loaded
            uniform float4 _MainTex_TexelSize;
            uniform float2 _InitialSeed;
            uniform float4 _SpriteRectChunks[128];
            // Coordinates of each 8x8 sprite chunk I want to use in the shader
            // x,y are the positions (in pixels) of the chunk on the original atlas, z,w are unused and set to 0
            uniform int _SpriteRectChunkCount = 0;
            // Not all of these vectors are used, so I also pass in how many actually are

            struct vert2frag_img
                float4 pos : SV_POSITION;
                half2 uv : TEXCOORD0;
                nointerpolation float4 chunk : POSITION1;
                // The chunk selection is done in the vertex shader


            // Taken from https://stackoverflow.com/a/3451607/1089957
            float2 remap(float2 value, float2 low1, float2 high1, float2 low2, float2 high2)
                return low2 + (value - low1) * (high2 - low2) / (high1 - low1);

            // Given a vec2 as a seed, return a number from [0, 1)
            // Taken from https://stackoverflow.com/a/10625698/1089957
            float random(float2 seed)
                const float2 r = float2(
                return frac(cos(fmod(123456789., 1e-7 + 256. * dot(seed, r))));  

            int random(float2 seed, int limit) {
                return (int)round(random(seed) * limit);

            vert2frag_img vert(appdata_img IN)
                vert2frag_img OUT;


                OUT.pos = UnityFlipSprite(IN.vertex, _Flip);
                OUT.pos = UnityObjectToClipPos(OUT.pos);
                OUT.uv = IN.texcoord;

                float2 position = mul(unity_ObjectToWorld, float4(0, 0, 0, 1)).xy;

                #ifdef UNITY_INSTANCING_ENABLED
                int index = random(round(position * _InitialSeed * 8 + UNITY_GET_INSTANCE_ID(IN)), _SpriteRectChunkCount);
                int index = random(round(position * _InitialSeed * 8), _SpriteRectChunkCount);
                OUT.chunk = _SpriteRectChunks[index];

                #ifdef PIXELSNAP_ON
                OUT.pos = UnityPixelSnap(OUT.pos);

                return OUT;

            fixed4 frag(vert2frag_img IN) : SV_Target

                float2 pixels = remap(IN.uv, 0, 1, 0, _MainTex_TexelSize.zw);
                // texture coords (range [0, 1]) -> texture pixels (range [0, TextureSize])

                pixels = pixels - UNITY_ACCESS_INSTANCED_PROP(Props, _TextureRect).xy;
                // Sprite pixel coordinate, relative to (0, 0)

                pixels = fmod(pixels, 8);
                // Display glitched sprites in 8x8 chunks

                pixels = pixels + IN.chunk.xy;
                // Back to the sprite

                pixels = remap(pixels, 0, _MainTex_TexelSize.zw, 0, 1);
                // Remap to normalized coordinates

                fixed4 c = tex2D(_MainTex, clamp(pixels, 0, 1));

                c.rgb *= c.a;
                return c;


There's a lot of window dressing, but the actual shader code in the CGPROGRAM block is standard HLSL. This is a screenshot of my game without this shader:

Screenshot, no shader

And this is the same scene, with the shader:

Screenshot, with shader

Here are some relevant facts:

  • All sprites are stored on a single texture atlas.
  • All sprites have a length and width that are a multiple of 8. But their position on the texture atlas might not be.
  • Some sprites in the second screenshot are invisible, even though they should be glitchy like the ones that are. While this is technically a bug, it's consistent with the style I'm going for, so I will probably leave it as is. If you happen to have a fix to suggest, I'll try it out and see how it looks, but that's not the basis of my question.
  • I intend to release this game on a variety of platforms, so make no assumptions about what graphics API (DirectX, OpenGL, etc.) is actually being used. However, this shader will always be written in HLSL (Unity compiles it to other languages as needed).

These are my questions:

  • Are there ways I can optimize this shader?

I'm looking for changes I can make in either the actual HLSL code or the surrounding ShaderLab declarations.



Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.