鸟语天空
UGUI—画笔
post by:追风剑情 2020-10-14 14:10

示例:在Texture2D上的画笔功能

1、准备一张画笔图片

brush_point_8x8.png

2、合成图片shader (可选功能)

Shader "Custom/ComposeShader"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _SecondTex("Texture", 2D) = "white" {}
    }
        SubShader
    {
        Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
        LOD 100

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _SecondTex;
            float4 _SecondTex_ST;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 col2 = tex2D(_SecondTex, i.uv);
                //step(a, x) 如果x<a,返回0;否则,返回1
                //col2.a = step(0.95, col2.a);
                col.rgb = col.rgba * (1 - col2.a) + col2.rgba * col2.a;
                return col;
            }
            ENDCG
        }
    }
}

3、画笔脚本

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
/// <summary>
/// 画笔层
/// </summary>
public class UIBrushLayer : MonoBehaviour, IPointerClickHandler
{
    //画布的RectTransform
    [SerializeField]
    private RectTransform m_RectTransform;
    //显示画布
    [SerializeField]
    private RawImage m_RawImage;
    //处理图片合成
    [SerializeField]
    private Material m_ComposeMat;
    //画笔
    //作为画笔的Texture需要在Inspector面板进行下面两项设置
    //Texture Type: Default
    //Wrap Mode: Repeat
    //Read/Write Enabled: true
    [SerializeField]
    private Texture2D m_BrushPoint;
    //画布
    private Texture2D m_CanvasTexture;
    private Vector2 m_LastPosition = new Vector2(-1, -1);
    private bool m_Pressed = false;

    [Tooltip("画笔颜色")]
    public Color BrushColor = Color.red;
    //[Tooltip("抗锯齿级别")]
    //[Range(1, 5)]
    //public int AnisoLevel = 3;
    //是否存在绘制
    public bool IsBrushed { get; private set; }
    //是否启用
    [SerializeField]
    private bool m_EnabledBrush = true;
    public bool EnabledBrush
    {
        get { return m_EnabledBrush; }
        set
        {
            m_EnabledBrush = value;
            m_LastPosition.x = m_LastPosition.y = -1;
        }
    }
    //是否点击清除
    public bool ClickClear = false;
    //是否接收事件
    public bool RaycastTarget
    {
        set
        {
            m_RawImage.raycastTarget = value;
        }
    }

    //绘制点
    public Action<Point> OnTouchPointEvent;
    //取消绘制
    public Action OnTouchCanceledEvent;

    //重置
    private void Reset()
    {
        if (m_RectTransform == null)
            return;
        int width = (int)m_RectTransform.rect.width;
        int height = (int)m_RectTransform.rect.height;
        m_CanvasTexture = Texture2D.blackTexture;
        //Resize()方法会将所有像素值设置为(0.804, 0.804, 0.804, 0.804)
        //new Texture2D()也会将所有像素值设置为(0.804, 0.804, 0.804, 0.804)
        m_CanvasTexture.Resize(width, height);
        //将所有像素值设置为(0,0,0,0)
        Color[] colors = new Color[width * height];
        m_CanvasTexture.SetPixels(colors);
        m_CanvasTexture.Apply();
        m_RawImage.texture = m_CanvasTexture;
        m_RawImage.color = Color.white;
        IsBrushed = false;
        m_LastPosition.x = m_LastPosition.y = -1;
    }

    private void Start()
    {
        Reset();
        SetBrushColor(BrushColor);
    }

    private void Update()
    {
        if (!EnabledBrush)
            return;

        CheckPressed();

        if (!m_Pressed)
            return;

        if (m_LastPosition.x < 0 || m_LastPosition.y < 0)
            return;

        TouchBrush();

        CheckRelease();
    }

    private void OnDisable()
    {
        m_LastPosition.x = -1;
        m_LastPosition.y = -1;
    }

    //判断是否点击在自身的GameObject上
    private bool IsPointerOverGameObject()
    {
        PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
        eventDataCurrentPosition.position = TouchPosition;
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
        if (results.Count == 0)
            return false;
        RaycastResult result = results[0];
        return result.gameObject == this.gameObject;
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        if (ClickClear && IsBrushed)
            Clear();
    }

    //清空
    public void Clear()
    {
        Reset();
    }

    //当前触摸的位置(屏幕坐标)
    public Vector2 TouchPosition
    {
        get
        {
            Vector2 position = Vector2.zero;
            if (Input.touchSupported)
            {
                if (Input.touchCount > 0)
                    position = Input.touches[0].position;
            }
            else
            {
                position = Input.mousePosition;
            }
            return position;
        }
    }

    //检测鼠标或手指是否按下
    private void CheckPressed()
    {
        if (Input.touchSupported)
        {
            if (Input.touchCount > 0 && Input.touches[0].phase == TouchPhase.Began)
            {
                if (IsPointerOverGameObject())
                {
                    m_LastPosition = Input.touches[0].position;
                    m_Pressed = true;
                }
            }
        }
        else if (Input.GetMouseButtonDown(0))
        {
            if (IsPointerOverGameObject())
            {
                m_LastPosition = Input.mousePosition;
                m_Pressed = true;
            }
        }
    }

    //检测鼠标或手指是否释放
    private void CheckRelease()
    {
        if (Input.touchSupported)
        {
            if (Input.touchCount == 0 || Input.touches[0].phase == TouchPhase.Ended)
            {
                m_Pressed = false;
                if (OnTouchCanceledEvent != null)
                    OnTouchCanceledEvent();
            }
        }
        else if (Input.GetMouseButtonUp(0))
        {
            m_Pressed = false;
            if (OnTouchCanceledEvent != null)
                OnTouchCanceledEvent();
        }
    }

    //在指定点绘制
    public void TouchBrush(Vector2 localPosition)
    {
        if (localPosition.x < 0 || localPosition.y < 0)
        {
            m_LastPosition.x = m_LastPosition.y = -1;
            return;
        }

        if (gameObject.activeInHierarchy)
            StartCoroutine(DrawLocalPointCoroutine(localPosition));
    }

    //通过手指绘制曲线
    private void TouchBrush()
    {
        StartCoroutine(DrawPointCoroutine(TouchPosition));
    }

    //以协程方式绘制连续点
    private IEnumerator DrawLocalPointCoroutine(Vector2 localPosition)
    {
        //绘制第一个起始点
        if (m_LastPosition.x < 0 || m_LastPosition.y < 0)
        {
            DrawLocalPoint(localPosition);
            m_LastPosition = localPosition;
            m_CanvasTexture.Apply();
            yield break;
        }

        //以插值方式绘制连续的点,形成曲线
        float maxDistanceDelta = m_BrushPoint.width / 3;
        float distance = 0.0f;
        int loopCount = 0;
        do
        {
            distance = Vector2.Distance(localPosition, m_LastPosition);
            Vector2 lerpPosition = Vector2.MoveTowards(m_LastPosition, localPosition, maxDistanceDelta);
            DrawLocalPoint(lerpPosition);
            m_LastPosition = lerpPosition;
            //控制每帧最多绘制几个点,避免卡主线程
            loopCount++;
            if (loopCount > 10)
                yield return null;
            loopCount = 0;
        } while (distance > 1.0f);
        m_LastPosition = localPosition;
        m_CanvasTexture.Apply();
    }

    //以协程方式绘制连续点
    private IEnumerator DrawPointCoroutine(Vector2 screenPosition)
    {
        //判断坐标是否在可绘区
        if (!RectTransformUtility.RectangleContainsScreenPoint(m_RectTransform, screenPosition))
            yield break;

        if (OnTouchPointEvent != null)
        {
            Vector2 localPosition = UGUITool.ScreenPointToLocal(m_RectTransform, screenPosition);
            OnTouchPointEvent(new Point() { x=(int)localPosition.x, y=(int)localPosition.y});
        }

        yield return null;

        //以插值方式绘制连续的点,形成曲线
        float maxDistanceDelta = m_BrushPoint.width / 3;
        float distance = 0.0f;
        int loopCount = 0;
        do
        {
            distance = Vector2.Distance(screenPosition, m_LastPosition);
            Vector2 lerpPosition = Vector2.MoveTowards(m_LastPosition, screenPosition, maxDistanceDelta);
            DrawPoint(lerpPosition);
            m_LastPosition = lerpPosition;
            //控制每帧最多绘制几个点,避免卡主线程
            loopCount++;
            if (loopCount > 10)
                yield return null;
            loopCount = 0;
        } while (distance > 1.0f);
        m_LastPosition = screenPosition;
        m_CanvasTexture.Apply();
    }

    //绘制单个点
    private void DrawLocalPoint(Vector2 localPosition)
    {
        //使绘制时m_BrushPoint的中心点与点击位置对齐
        localPosition.x -= m_BrushPoint.width / 2;
        localPosition.y -= m_BrushPoint.height / 2;
        //绘制画笔圆点
        int x = (int)(localPosition.x);
        int y = (int)localPosition.y;
        int w = m_BrushPoint.width;
        int h = m_BrushPoint.height;
        for (int i = 0; i < w; i++)
        {
            for (int j = 0; j < h; j++)
            {
                Color c = m_BrushPoint.GetPixel(i, j);
                if (c.a > 0.9f)
                    m_CanvasTexture.SetPixel(x + i, y + j, c);
            }
        }
        IsBrushed = true;
    }

    //绘制单个点
    private void DrawPoint(Vector2 screenPosition)
    {
        //画笔层本地坐标
        Vector2 localPosition = UGUITool.ScreenPointToLocal(m_RectTransform, screenPosition);
        DrawLocalPoint(localPosition);
    }

    //RenderTexture转Texture2D
    private Texture2D ConvertTexture2D(RenderTexture renderTexture)
    {
        int width = renderTexture.width;
        int height = renderTexture.height;
        Texture2D texture2D = new Texture2D(width, height, TextureFormat.ARGB32, false);
        RenderTexture.active = renderTexture;
        texture2D.ReadPixels(new Rect(0, 0, width, height), 0, 0);
        texture2D.Apply();
        return texture2D;
    }

    //将画布合成到给定Texture上
    public Texture2D ComposeTexture(Texture2D texture)
    {
        m_ComposeMat.SetTexture("_SecondTex", m_CanvasTexture);
        RenderTexture destRT = RenderTexture.GetTemporary(texture.width, texture.height, 0, RenderTextureFormat.ARGB32);
        Graphics.Blit(texture, destRT, m_ComposeMat);
        Texture2D composeTex = ConvertTexture2D(destRT);
        destRT.Release();
        return composeTex;
    }

    //设置画笔颜色
    public void SetBrushColor(Color color)
    {
        BrushColor = color;
        Texture2D newBrush = new Texture2D(m_BrushPoint.width, m_BrushPoint.height, TextureFormat.RGBA32, false);
        Color[] colors = new Color[m_BrushPoint.width * m_BrushPoint.height];
        newBrush.SetPixels(colors);
        newBrush.Apply();
        for (int i = 0; i < m_BrushPoint.width; i++)
        {
            for (int j = 0; j < m_BrushPoint.height; j++)
            {
                Color c = m_BrushPoint.GetPixel(i, j);
                if (c.a > 0.0f)
                {
                    c.r = color.r;
                    c.g = color.g;
                    c.b = color.b;
                    newBrush.SetPixel(i, j, c);
                }
            }
        }
        newBrush.Apply();
        m_BrushPoint = newBrush;
    }

    public BrushRect GetBrushRect()
    {
        BrushRect rect = new BrushRect();
        rect.width = (int)m_RectTransform.rect.width;
        rect.height = (int)m_RectTransform.rect.height;
        return rect;
    }

    private void OnDestroy()
    {
        OnTouchPointEvent = null;
        OnTouchCanceledEvent = null;
    }

    [Serializable]
    public class Point
    {
        public int x;
        public int y;

        public override string ToString()
        {
            return string.Format("({0},{1})", x, y);
        }
    }

    [Serializable]
    public class BrushPointList
    {
        public UIBrushLayer.Point[] points;
    }

    [Serializable]
    public class BrushRect
    {
        public int width;
        public int height;

        public override string ToString()
        {
            return string.Format("width={0},height{1}", width, height);
        }
    }
}

4、挂上画笔脚本

11111.png

BrushImage.mat

2222.png

运行效果

3335.gif

评论:
发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容