Unity3D Dither jitter Shader implementation

overview

I wrote an article about the color removal shader before , which roughly uses a method similar to interval division to discretize the colors used in the original picture to achieve some special visual effects.

When using the previous shader and reducing the number of colors to two, the effect is as follows, and it is completely impossible to see what the original image is.

Please add a picture description
Today we use a special dither dither shader to achieve the effect of only using two colors but still ensuring certain image details.
Let’s take a look at the effect first. The picture below only uses pure black or pure white. Although it is still blurry, the effect is much better than the picture above.
Please add a picture description

Some people may not believe that this picture only uses two colors (I didn't believe it at first). Let's capture a part here and zoom in, and you can clearly see that this picture only has two colors: pure black or pure white. In addition, anyone who has studied computer graphics should see what kind of algorithm this is.
Please add a picture description

As a quick overview, in computer graphics there is a way to represent a wider variety of colors using only black and white. As shown in the figure below, use a 2x2 pixel square, and by changing the number of black dots filled in the four points inside, you can get five different gray levels, and use this 2x2 pixel square for each pixel If you replace the grid, you can get 5 color changes. Although there are only black and white colors in fact, as long as you don’t zoom in, you can get the illusion of 5 color changes.
Please add a picture description
5 variations can be made with a 2x2 square, so if you want more gray levels, you need to increase the size of the square, 4x4 squares can get 17 gray levels, and 8x8 squares can get an amazing 65 kinds grayscale.
But using a 2x2 square to represent the original pixel requires a larger resolution for the entire image. Assuming that the original image is a 1024x1024 image, there are 1,048,576 pixels in total. If a 2x2 grid is used to represent the original pixel, although 5 gray levels can be obtained, the resolution needs to be increased to 2048x2048. Using a larger grid is the same, although more color levels can be obtained, but a larger resolution is required to display the original image.

There is a way to use the dither algorithm on the original image without increasing the resolution, which is the method we are going to talk about today. Assuming the above-mentioned 8x8 block is used, this method does not need to enlarge each pixel by 64 times, but directly performs this division on the original image, divides the original image into several 8x8 blocks, and calculates each pixel of the original image , and compare it with the threshold in the corresponding block. If it exceeds, a white dot will be marked, otherwise a black dot will be marked.

First obtain the screen coordinates corresponding to the pixels

float2 screenPos = IN.screenPos.xy / IN.screenPos.w * _ScreenParams.xy;

Get the brightness corresponding to the pixel

int brightness = Luminance(c) * 256;

The 8x8 grid supports 65 kinds of brightness, so first reduce the 256 colors to 64 colors (that is, 0~64, a total of 65 colors)

brightness = brightness >> 2;

Take the remainder of the coordinates, and then pass the comparison function to get whether the point maps to a black point or a white point.

gray = Dither8x8Bayer(screenPos.x % 8, screenPos.y % 8, brightness);

The key comparison method is as follows. Pass in the screen coordinates and the corresponding pixel brightness. By comparing the threshold between 1 and 64, you can convert this pixel into a white point or a black point.

        float Dither8x8Bayer(int x, int y, float brightness)
        {
    
    
            const float dither[64] = {
    
    
                1, 49, 13, 61, 4, 52, 16, 64,
                33, 17, 45, 29, 36, 20, 48, 32,
                9, 57, 5, 53, 12, 60, 8, 56,
                41, 25, 37, 21, 44, 28, 40, 24,
                3, 51, 15, 63, 2, 50, 14, 62,
                35, 19, 47, 31, 34, 18, 46, 30,
                11, 59, 7, 55, 10, 58, 6, 54,
                43, 27, 39, 23, 42, 26, 38, 22
            };
            int r = y * 8 + x;
            return step(dither[r], brightness);
        }

For a single pixel, even if its brightness is high, it may be converted into a black point because the corresponding position has a higher threshold. If you think about it this way, you may feel that this approach is unreasonable.
But for the entire 8x8 block, if the average brightness of the pixels in the entire block is high, then there will be more white spots at the end of the entire block, so the effect is still the same in the end. For the entire block, the brighter the original The more white points after conversion, the darker the original, the more black points, but this method does not need to increase the resolution.

The methods discussed above are grayscale images, and the same is true for color images, as long as each component is considered separately.
The effect of the color map is as follows. Only two colors are used for each component, so at most 2^3=8 colors are used after the final mixing.
Please add a picture description

full code

The above only discusses the implementation under the 8x8 grid, but any NxN grid is possible. The following code uses macro definitions to realize the three grid sizes of 2x2, 4x4, and 8x8 respectively.

Shader "LX/Dither"
{
    
    
    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
        _GrayScale("GrayScale", Range(0,1)) = 1
    }
    SubShader
    {
    
    
        Tags
        {
    
    
            "RenderType"="Opaque"
        }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows vertex:vertexDataFunc
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
    
    
            float2 uv_MainTex;
            fixed4 screenPos;
        };

        float Dither2x2Bayer(int x, int y, float brightness)
        {
    
    
            const float dither[4] = {
    
    
                0, 2,
                3, 1
            };
            int r = y * 2 + x;
            return step(dither[r], brightness);
        }

        float Dither4x4Bayer(int x, int y, float brightness)
        {
    
    
            const float dither[16] = {
    
    
                0, 8, 2, 10,
                12, 4, 14, 6,
                3, 11, 1, 9,
                15, 7, 13, 5
            };
            int r = y * 4 + x;
            return step(dither[r], brightness);
        }


        float Dither8x8Bayer(int x, int y, float brightness)
        {
    
    
            const float dither[64] = {
    
    
                1, 49, 13, 61, 4, 52, 16, 64,
                33, 17, 45, 29, 36, 20, 48, 32,
                9, 57, 5, 53, 12, 60, 8, 56,
                41, 25, 37, 21, 44, 28, 40, 24,
                3, 51, 15, 63, 2, 50, 14, 62,
                35, 19, 47, 31, 34, 18, 46, 30,
                11, 59, 7, 55, 10, 58, 6, 54,
                43, 27, 39, 23, 42, 26, 38, 22
            };
            int r = y * 8 + x;
            return step(dither[r], brightness);
        }

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float _GrayScale;


        void vertexDataFunc(inout appdata_full v, out Input o)
        {
    
    
            UNITY_INITIALIZE_OUTPUT(Input, o);
            float4 screenPos = ComputeScreenPos(UnityObjectToClipPos(v.vertex));
            o.screenPos = screenPos;
        }

        #define _8x8

        void surf(Input IN, inout SurfaceOutputStandard o)
        {
    
    
            float2 screenPos = IN.screenPos.xy / IN.screenPos.w * _ScreenParams.xy;
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            int brightness = Luminance(c) * 256;
            int colorR = c.r * 256;
            int colorG = c.g * 256;
            int colorB = c.b * 256;

            fixed gray;
            fixed r;
            fixed g;
            fixed b;

            #ifdef _8x8
            brightness = brightness >> 2;
            colorR = colorR >> 2;
            colorG = colorG >> 2;
            colorB = colorB >> 2;
            gray = Dither8x8Bayer(screenPos.x % 8, screenPos.y % 8, brightness);
            r = Dither8x8Bayer(screenPos.x % 8, screenPos.y % 8, colorR);
            g = Dither8x8Bayer(screenPos.x % 8, screenPos.y % 8, colorG);
            b = Dither8x8Bayer(screenPos.x % 8, screenPos.y % 8, colorB);
            #elif defined (_4x4)
            brightness = brightness >> 4;
            colorR=colorR>>4;
            colorG=colorG>>4;
            colorB=colorB>>4;
            gray = Dither8x8Bayer(screenPos.x % 4, screenPos.y % 4, brightness);
            r = Dither8x8Bayer(screenPos.x % 4, screenPos.y % 4, colorR);
            g = Dither8x8Bayer(screenPos.x % 4, screenPos.y % 4, colorG);
            b = Dither8x8Bayer(screenPos.x % 4, screenPos.y % 4, colorB);
            #elif defined (_2x2)
            brightness = brightness >> 6;
            colorR=colorR>>6;
            colorG=colorG>>6;
            colorB=colorB>>6;
            gray = Dither8x8Bayer(screenPos.x % 2, screenPos.y % 2, brightness);
            r = Dither8x8Bayer(screenPos.x % 2, screenPos.y % 2, colorR);
            g = Dither8x8Bayer(screenPos.x % 2, screenPos.y % 2, colorG);
            b = Dither8x8Bayer(screenPos.x % 2, screenPos.y % 2, colorB);
            #endif

            fixed3 grayColor = fixed3(gray, gray, gray);
            fixed3 color = fixed3(r, g, b);
            o.Albedo = lerp(color, grayColor, _GrayScale);
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

In addition, the code has also been passed to the github warehouse, you can also pay attention to it~
my github

Guess you like

Origin blog.csdn.net/o83290102o5/article/details/120604171