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,性能也好。