Erase function in Unity

original text see.

The pixel recognition / statistical operation of shader is realized through a small Trick

1. Introduction

Divide a large image into several small blocks, process and merge them step by step, and retain the down sampling of key pixels:

But I was thinking about a simpler method, so I thought of a form of judgment and detection in the vertex shader and obtaining the results in the pixel shader:

Use a group of vertices to read a single pixel, submit the failed vertex coordinates to the outside of the screen, and put the successful vertex coordinates on the screen.

Finally, obtain the result of whether there are vertices in the screen in the CPU for simple recognition operation.

Transparency can be turned on after more complex results are obtained.

2. Practice

First of all, the practice result is not as good as expected, because it would be too inefficient to judge the vertex part purely by using the triangular surface.

So I changed the way to judge the incoming vertices and generate faces, and reduced the pixel size of the incoming image.

Graphics.DrawProcedural(MeshTopology.Points, blueTex.width * blueTex.height, 1);

After all, more applications are used for scraping card or erasing recognition. Only mask images need to be detected.

Upper Code:

Shader "Hidden/FooShader"
{
    Properties
    {
    }
    SubShader
    {
        Blend One One

        tags
        {
            "Queue" = "Transparent"
            "RenderType" = "Transparent"
        }

        Pass
        {
            CGPROGRAM
            #pragma target 4.0
            #pragma vertex vert
            #pragma geometry geom
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct v2f
            {
                float4 color : COLOR;
                float4 vertex : SV_POSITION;
            };

            sampler2D _Image;
            float4 _ImageSize;

            v2f vert(uint vid : SV_VertexID)
            {
                v2f o = (v2f)0;

                half y = floor(vid / _ImageSize.x);
                half x = (vid - y * _ImageSize.x) / _ImageSize.x;
                y = y / _ImageSize.y;

                o.vertex = 0;

                float4 image_col = tex2Dlod(_Image, half4(x,y,0,0));

                if (all(image_col.rgb == half3(0, 0, 1)))
                //if (all(image_col.rgb == half3(0, 1, 1)))    /*error*/
                {
                    o.color = 1;
                }
                else
                {
                    o.color = 0;
                }

                return o;
            }

            [maxvertexcount(4)]
            void geom(point v2f vertElement[1], inout TriangleStream<v2f> triStream)
            {
                if (vertElement[0].color.r <= 0) return;

                float size = 10;

                float4 v1 = vertElement[0].vertex + float4(-size, -size, 0, 0);
                float4 v2 = vertElement[0].vertex + float4(-size, size, 0, 0);
                float4 v3 = vertElement[0].vertex + float4(size, -size, 0, 0);
                float4 v4 = vertElement[0].vertex + float4(size, size, 0, 0);

                v2f r = (v2f)0;

                r.vertex = mul(UNITY_MATRIX_VP, v1);
                r.color = vertElement[0].color;
                triStream.Append(r);

                r.vertex = mul(UNITY_MATRIX_VP, v2);
                r.color = vertElement[0].color;
                triStream.Append(r);

                r.vertex = mul(UNITY_MATRIX_VP, v3);
                r.color = vertElement[0].color;
                triStream.Append(r);

                r.vertex = mul(UNITY_MATRIX_VP, v4);
                r.color = vertElement[0].color;
                triStream.Append(r);
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return i.color;
            }
            ENDCG
        }
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

namespace Hont
{
    public class Foo : MonoBehaviour
    {
        void Start()
        {
            var blueTex = new Texture2D(64, 64);
            for (int x = 0; x < blueTex.width; x++)
                for (int y = 0; y < blueTex.height; y++)
                    blueTex.SetPixel(x, y, Color.blue);
            blueTex.Apply();

            var mat = new Material(Shader.Find("Hidden/FooShader"));
            mat.SetTexture("_Image", blueTex);
            mat.SetVector("_ImageSize", new Vector4(blueTex.width, blueTex.height));
            mat.SetPass(0);
            var tempRT = RenderTexture.GetTemporary(16, 16, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB, 1);
            tempRT.filterMode = FilterMode.Point;
            tempRT.autoGenerateMips = false;
            tempRT.anisoLevel = 0;
            tempRT.wrapMode = TextureWrapMode.Clamp;
            var cacheRT = RenderTexture.active;
            RenderTexture.active = tempRT;
            Graphics.DrawProcedural(MeshTopology.Points, blueTex.width * blueTex.height, 1);
            var tex2D = new Texture2D(16, 16, TextureFormat.ARGB32, false, false);
            tex2D.wrapMode = TextureWrapMode.Clamp;
            tex2D.anisoLevel = 0;
            tex2D.filterMode = FilterMode.Point;
            tex2D.ReadPixels(new Rect(0, 0, 16, 16), 0, 0);
            var firstPixel = tex2D.GetPixel(0, 0);
            Debug.Log("firstPixel: " + firstPixel);
            RenderTexture.active = cacheRT;
            RenderTexture.ReleaseTemporary(tempRT);
        }
    }
}

After running the code, I found three problems, which are also unsolved. One is the error of the calculation result

o.color = float4(0.05, 0, 0, 0);

The output is 0.05, but there are some differences in the results.

Especially when the returned color is less than 0.1, I try to change the image format or RT and other parameters, but I still can't solve it

The second problem is that when transparency is turned on, there is an upper limit on the superposition of transparent pictures. After all, the depth is limited. After stacking more than 20 layers, the back layer will be lost.

The third problem is that the size of the incoming picture is too large, which directly leads to the bandwidth explosion, so that unity directly pretends to be dead. The 512x512 picture has more than 260000 pixels to process, that is, more than 260000 vertices.

The third problem is easy to solve. Control the image size + let a single vertex sample more pixels.

For the first problem, it doesn't need to be too precise at present, so it hasn't been solved, but it can also be used. The second problem can be alleviated in some ways

For example, increase the amount of computation in the vertex shader and distribute the return value to the four channels of rgba.

uint roll = (roll_width + roll_height) % 4;

if (roll == 0)
    result = float4(GAIN_VALUE, 0, 0, 0);

if (roll == 1)
    result = float4(0, GAIN_VALUE, 0, 0);

if (roll == 2)
    result = float4(0, 0, GAIN_VALUE, 0);

if (roll == 3)
    result = float4(0, 0, 0, GAIN_VALUE);

Put more pixels into the vertex traversal, so that the number of vertices in the processed image is the original size / n:

v2f vert(uint vid : SV_VertexID)
{
    v2f o = (v2f)0;

    o.vertex = 0;

    half2 image_size = half2(GRID_SIZE_X * LOOP_IMAGE_SIZE_X, GRID_SIZE_Y * LOOP_IMAGE_SIZE_Y);

    half y = floor(vid / LOOP_IMAGE_SIZE_X);
    half x = (vid - y * LOOP_IMAGE_SIZE_X) / LOOP_IMAGE_SIZE_X;
    y = y / LOOP_IMAGE_SIZE_Y;
    //Convert vid to x,y coordinates

    for (half rx = 0; rx < GRID_SIZE_X; rx++)
    {
        for (half ry = 0; ry < GRID_SIZE_Y; ry++)
        {
            half xx = x + rx;
            half yy = y + ry;

            float4 r = Statistics_sample(_Image, _Rec_Color, half4(xx, yy, 0, 0), image_size);

            o.color += r;
        }
    }
    //One vertex handles multiple pixels

    return o;
}

3. Test results

Finally, I achieved a good result. I encapsulated the related functions into a class.

I wrote a smear effect demo to test it. It judges whether it is completely painted by identifying the number of white pixels:

I lost the project file on GitHub: https://github.com/hont127/Image-Rec-Base-unity-shader-

Erasure is realized by changing pixels in GUGI

This method needs to change the settings of the wizard, as follows:

Don't say much, just go to the code:

using System.Collections.Generic;
using System.Reflection.Emit;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Profiling;
using UnityEngine.UI;


public class ChangeTexturePixel : MonoBehaviour, IDragHandler
{
    ///< summary > number of pixels erased < / summary >
    private int m_PixelAcount = 0;
    
    ///< summary > is the erasure successful? < / summary >
    private bool m_IsDrag = false;
    
    ///< summary > erase range size < / summary >
    [SerializeField][Range(10,100)]
    private int Radius = 10;
    
    ///< summary > erase completion (no more than 1) < / summary >
    [SerializeField][Range(0,1)] 
    private float m_Complete; 
    
    private RawImage m_UITex;
    
    private Texture2D m_MyTex;

    [SerializeField]
    private Color m_Col = Color.clear;
    
    private int[][] m_PixelArray;
  
    private Dictionary<int, TexturePixel> m_TexPixelDic = new Dictionary<int, TexturePixel>();

    void Start()
    {
        m_IsDrag = false;
        m_UITex = GetComponent<RawImage>();
        var tex = m_UITex.texture as Texture2D;

        m_MyTex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32,
            false); 

        m_MyTex.SetPixels(tex.GetPixels());
        m_MyTex.Apply();
        m_UITex.texture = m_MyTex;
        
        int value = 0;
        m_PixelArray = new int[m_MyTex.width][];
        for (int i = 0; i < m_PixelArray.Length; i++)
        {
            m_PixelArray[i] = new int[m_MyTex.height];
            for (int j = 0; j < m_MyTex.height; j++)
            {
                m_PixelArray[i][j] = value;

                m_TexPixelDic.Add(value, new TexturePixel(m_MyTex, i, j));
                value++;
            }
        }
        
    }

    /// <summary>
    ///Change Texture2D pixel color
    /// </summary>
    ///< param name = "X" > texture2d pixel X-axis position < / param >
    ///< param name = "Y" > texture2d pixel Y-axis position < / param >
    ///< param name = "radius" > change the range of pixels < / param >
    ///< param name = "col" > changed color < / param >
    void ChangePixelColorByCircle(int x, int y, int radius, Color col)
    {
        for (int i = -Radius; i < Radius; i++)
        {
            var py = y + i;
            if (py < 0 || py >= m_MyTex.height)
            {
                continue;
            }

            for (int j = -Radius; j < Radius; j++)
            {
                var px = x + j;
                if (px < 0 || px >= m_MyTex.width)
                {
                    continue;
                }

                if (new Vector2(px - x, py - y).magnitude > Radius)
                {
                    continue;
                }

                Profiler.BeginSample("text1");
                TexturePixel tp; //= texPixelDic[pixelArray[MyTex.width - 1][py]];

                if (px == 0)
                {
                    tp = m_TexPixelDic[m_PixelArray[m_MyTex.width - 1][py]];
                    tp.Scratch(m_Col);
                  
                }
                
                tp = m_TexPixelDic[m_PixelArray[px][py]];
                if (!tp.GetPixel())
                {
                    m_PixelAcount++;
                }
                tp.Scratch(m_Col);
                
                Profiler.EndSample();
            }
        }

        Profiler.BeginSample("text2");
        m_MyTex.Apply();
        Profiler.EndSample();
        Profiler.BeginSample("text3");
        Profiler.EndSample();
    }

     /// <summary>
     ///Erase point
     /// </summary>
     ///< param name = "mousepos" > mouse position < / param >
     ///< returns > erase point < / returns >
    Vector2 ScreenPoint2Pixel(Vector2 mousePos)
    {
        float imageWidth = m_UITex.rectTransform.sizeDelta.x;
        float imageHeight = m_UITex.rectTransform.sizeDelta.y;
        Vector3 imagePos = m_UITex.rectTransform.anchoredPosition3D;
        //Find the position of the mouse on the image
        float HorizontalPercent =
            (mousePos.x - (Screen.width / 2 + imagePos.x - imageWidth / 2)) / imageWidth; //Mouse position on Image level%
        float verticalPercent =
            (mousePos.y - (Screen.height / 2 + imagePos.y - imageHeight / 2)) / imageHeight; //Mouse position on Image vertical%
        float x = HorizontalPercent * m_MyTex.width;
        float y = verticalPercent * m_MyTex.height;
        return new Vector2(x, y);
    }

    
    /// <summary>
    ///Dragging...
    /// </summary>
    ///< param name = "eventdata" > drag data < / param >
    public void OnDrag(PointerEventData eventData)
    {
        if (!m_IsDrag)
        {
            var posA = ScreenPoint2Pixel(eventData.position);
            ChangePixelColorByCircle((int) posA.x, (int) posA.y, Radius, m_Col);
            SetAllPixelFadeAlpha();

        }
    }

    /// <summary>
    ///Called when erase is complete
    /// </summary>
    public void SetAllPixelFadeAlpha()
    {
        if (++m_PixelAcount >= m_MyTex.height*m_MyTex.width*m_Complete)
        {  
            m_UITex.color = Color.clear;
            m_IsDrag = true;
            Debug.Log("Erase complete");
        }
    }
}
sing System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TexturePixel
{
    public Texture2D myTex;
    //float alpha = 1;        // Current transparency
   // int scratchedTime = 0;// Number of scratches
    private int x;      //Pixel coordinate X
    private int y;      //Pixel coordinate Y
    //private bool scratcedPrevious = false;
    //private bool scratcedCurrent = false;
    public TexturePixel(Texture2D tex,int x,int y)
    {
        myTex = tex;
        this.x = x;
        this.y = y;
    }

    public void Scratch( Color targetCol)
    {
        myTex.SetPixel(x,y,targetCol);
      //  scratcedCurrent = true;
        //Debug.Log("x:"+x+"  y:"+y+"  a "+ targetCol.a);
    }

    public bool GetPixel()
    {
      Color color =  myTex.GetPixel(x, y);
      
      return color.a <= 0;
    }

The above method is to erase by changing the color of Texture2D pixels. The main contents are as follows:

 // Set pixel
  myTex.SetPixel(x,y,targetCol);
 // Get pixels
  myTex.GetPixel(x,y,targetCol);

I put the project file in gitee: https://gitee.com/ondaly/eraser_-master.git
Project case see.

Keywords: Unity AR

Added by damiantaylor on Tue, 22 Feb 2022 15:37:24 +0200