Unity UI擦除效果

文章描述了一个在Unity中的Script组件,用于实时响应鼠标操作,通过Shader实现蒙版贴图的动态擦除,以显示底层图像。提到两种方法,一种是基于鼠标位置的粗暴方法,另一种是使用CommandBuffer和GPUInstance进行更高效的贴图操作。
摘要由CSDN通过智能技术生成
public class ScratchImage : MonoBehaviour
    {
        /// <summary>
        /// 蒙版贴图
        /// </summary>
        public Image maskImage;
        public Material maskMaterial;
        private Camera uiCamera;
        private Vector2 _maskSize;
        private Texture2D _rt;
        private Color[] spritePixels;
        public int brushRadius = 50;
        private float percent = 0;
        public float finishPercent = 0.95f;
        private int clearNum = 0;
        private int[] dirtyPoints;
        private Vector2 SCALE_FACTOR;
        private Vector2Int RTSize;
        bool bClear = false;
        private void Reset()
        {
            maskImage = GetComponent<Image>();
            maskMaterial = maskImage.material;
        }
        public void SetParams(int brushRadius,float fpercent)
        {
            this.brushRadius = brushRadius;
            this.finishPercent = fpercent;
        }
        private void Init()
        {
            if (uiCamera == null)
            {
                uiCamera = UIHelper.Instance.UICamera;
            }
            SCALE_FACTOR = maskImage.sprite.textureRect.size / maskImage.rectTransform.rect.size;
            _maskSize = maskImage.rectTransform.rect.size;
            //Debug.LogFormat("mask image size:{0}*{1}", maskSize.x, maskSize.y);
            RTSize = new Vector2Int((int)maskImage.sprite.textureRect.size.x, (int)maskImage.sprite.textureRect.size.y);
            spritePixels = maskImage.sprite.texture.GetPixels(0, 0, RTSize.x, RTSize.y);
            _rt = new Texture2D(RTSize.x, RTSize.y);
            _rt.SetPixels(spritePixels);
            _rt.Apply();
            maskMaterial.SetTexture("_TempTex", _rt);
            dirtyPoints = new int[RTSize.x * RTSize.y];
            bClear = false;
            percent = 0;
            clearNum = 0;
        }
        public void Update()
        {
            onUpdate();
        }
        void Fill()
        {
            bClear = true;
            for (int i = 0; i < RTSize.x; i++)
            {
                for (int j = 0; j < RTSize.y; j++)
                {
                    if(dirtyPoints[i*RTSize.y+j]==0)
                    {
                        _rt.SetPixel(i, j, new Color(1, 1, 1, 0));
                    }
                }
            }
            _rt.Apply();
        }
        public void ResetMask()
        {
            _rt.SetPixels(spritePixels);
            _rt.Apply();
            for (int i = 0; i < dirtyPoints.Length; i++)
            {
                dirtyPoints[i] = 0;
            }
            clearNum = 0;
            bClear = false;
        }
        private void onUpdate()
        {
            if (bClear) return;
            if (uiCamera == null)
                return;
            if (!Input.GetMouseButton(0)) // 移动鼠标或者处于按下状态
            {
                return;
            }
            Vector2 localPt = Vector2.zero;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(transform as RectTransform, Input.mousePosition, uiCamera, out localPt);

            //Debug.Log($"pt:{localPt}, status:{mouseStatus}");
            if (localPt.x < 0 || localPt.y < 0 || localPt.y >= _maskSize.x || localPt.y >= _maskSize.y)
                return;

            localPt = localPt * SCALE_FACTOR;
            Vector2Int usePixel = new Vector2Int((int)(localPt.x), (int)localPt.y);
            int left = (int)(usePixel.x - brushRadius);
            if (left < 0) left = 0;
            int right = (int)(usePixel.x + brushRadius);
            if (right > RTSize.x) right = (int)RTSize.x;
            int down = (int)(usePixel.y - brushRadius);
            if (down < 0) down = 0;
            int top = (int)(usePixel.y + brushRadius);
            if (top > RTSize.y) top = (int)RTSize.y;
            bool dirty = false;
            for (int i = left; i < right; i++)
            {
                for (int j = down; j < top; j++)
                {
                    int index = (int)(i * RTSize.y + j);
                    if (dirtyPoints[index] == 1)
                    {

                    }
                    else
                    {
                        Vector2 p = new Vector2(i, j);
                        float dis = Vector2.Distance(usePixel, p);
                        if (dis <= brushRadius)
                        {
                            _rt.SetPixel(i, j, new Color(1, 1, 1, 0));
                            dirtyPoints[index] = 1;
                            ++clearNum;
                            dirty = true;
                        }
                    }
                }
            }
            if (dirty)
            {
                percent = (float)clearNum / dirtyPoints.Length;
                if (percent > finishPercent)
                {
                    Fill();
                    GameHelp.FireEvent((ushort)DGlobalEvent.EVENT_SMALL_GAME_RESULT_OK, 0, 0, null);
                }
                else
                {
                    _rt.Apply();
                }
            }
        }
    }

shader

Shader "Unlit/PaintMask"
{
    Properties
    {
        _MainTex ("MainTexture", 2D) = "white" {}
        _TempTex ("TempTexture",2D) = "white" {}
    }
    SubShader
    {
        Tags 
        { 
           "Queue"="Transparent"
           "RenderType"="Transparent"
        }
        LOD 100
        ZWrite Off
        ZTest Off
        Blend SrcAlpha OneMinusSrcAlpha 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            sampler2D _TempTex;
            float4 _TempTex_ST;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                half alpha = tex2D(_TempTex,i.uv).a;
                col.a = alpha;
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

最粗暴的方法,原理就是跟着鼠标的位置,以鼠标为圆心把圆内的像素点清除,遮罩变成透明了,里面的图片就显示出来了,如果觉得鼠标滑动快的时候采样点不够,考虑DDA直线插值

说一下以上方法的缺陷:
1.setpixels要开读写,站内存,也耗时
2.setpixel擦除的时候只能使用几何数学描绘形状,不能使用贴图自定义点一下的形状
https://github.com/Misaka-Mikoto-Tech/ScratchImage
我认为这个的做法才是最正确的,可以用贴图丰富表现,也不需要开图片的读写用了commandbuffer 和gpu instance来draw mesh,性能也好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JustEasyCode

谢谢您

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值