示例:在Texture2D上的画笔功能
1、准备一张画笔图片
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、挂上画笔脚本
BrushImage.mat
运行效果