unity擦除原理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了unity擦除原理相关的知识,希望对你有一定的参考价值。
unity擦除原理介绍如下:1.开始的时候,取得遮罩图的像素数据,在鼠标滑动的时候,将屏幕坐标转换到遮罩在父节点下的坐标,然后以该点作为中心,生成需要擦除的区域大小,然后通过与刚开始记录的遮罩数据进行比较,替换需要擦除的区域的像素值,同时计算每一点像素的alpha值,在shader中做反转,达到透明的效果。
2.绘制函数放在了鼠标拖动时进行,没有放在update中,现在唯一的不足就是拖动太快时没有做平滑处理,导致出现断块。但是没啥时间,先记录一下,没有像其他说的生成大量预设,透明区域的形状可以自己找图设定。
3.在UI上生成一张Image作为遮罩,然后将shade对应的材质球交给Image,运行即可,在拖动鼠标看到效果。
4.测试工程在其他地方上传。C#脚本改了一下,使用线性插值的方式做平滑处理! 参考技术A 一个相机单独渲染笔刷轨迹到RenderTexture上,在通过RenderTexture中的笔刷路径修改原图中对uv的像素点的alpha值实现透明或者半透明1. Camera渲染到RenderTexture上:
a. 在场景中新建Camera并将ClearFlag设置为Don't Clear,目的是将渲染的物体连成轨迹。
b. 设置渲染层,只渲染笔刷(笔刷是一个球),笔刷根据鼠标位置移动即可。
c. 调整相机位置,使得要擦除的区域在整个视锥体内,也可以设置成正交投影。
d. 新建RenderTexture并挂载到相机上,相机设置为非激活状态(通过代码代码Camera.Render())进行渲染控制。因为只记录路径,所以只创建一个R8的RenderTexture就可以
ps:需要关闭相机的垂直同步(MSAA),否则会将RenderTexture翻转渲染。
设置效果如下图:
在这里插入图片描述
2. 通过RenderTexture修改原图片透明度(shader实现)
a. 通过相机的矩阵将RenderTexture变换到像素坐标系
b. 修改对应uv的原像素点的alpha值
shader代码:
Shader "Learning/guacaipiao"
Properties
_MainTex ("Texture", 2D) = "white"
// 相机渲染的RenderTexture
_BlitTex ("BlitTexture", 2D) = "white"
SubShader
Tags"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"
Cull Off
Pass
Tags"LightMode" = "ForwardBase"
// 开启alpah混合
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BlitTex;
struct a2v
float4 vertex : POSITION;
float4 texcoord : TEXCOORD0;
;
struct v2f
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
float4 paintPos : TEXCOORD3;
;
// 相机的投影矩阵
// C#中通过SetMatrix传入
// material.SetMatrix("paintCameraVP", camera.nonJitteredProjectionMatrix * camera.worldToCameraMatrix);
float4x4 paintCameraVP;
v2f vert(a2v v)
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
// 下面三行是通过投影矩阵将顶点变换到像素坐标系中([0, 1])
float4 paintPos = mul(paintCameraVP, mul(unity_ObjectToWorld, v.vertex));
paintPos /= paintPos.w; // 除以w分量,如果是相机正交投影可以省略
o.paintPos.xy = paintPos.xy * 0.5 + 0.5; // 将[-1, 1] 变换到 [0, 1]
return o;
fixed4 frag(v2f i) : SV_TARGET0
fixed4 texcolor = tex2D(_MainTex,i.uv);
// 划过的轨迹r值为1,所以1 - r作为原图片的alpha值输出
float mask = tex2D(_BlitTex, i.paintPos).r;
return fixed4(texcolor.rgb, 1 - mask);
ENDCG
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
C#代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GuaCaiPiaoSub : MonoBehaviour
public Camera rtCamera;
public Transform brush;
RenderTexture renderTexture;
public Material renderMaterial;
void Start ()
renderTexture = rtCamera.targetTexture;
renderMaterial.SetTexture("BlitTex", renderTexture);
renderMaterial.SetMatrix("paintCameraVP", rtCamera.nonJitteredProjectionMatrix * rtCamera.worldToCameraMatrix);
void OnMouseDrag()
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo))
brush.position = hitInfo.point;
rtCamera.Render();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
到这一步的实现效果:
在这里插入图片描述
很容易看出,滑动慢的时候可以连成一条线,但是快速滑动时候就变成了分开的点了。为避免这种情况出现就是把相邻两帧的点连接起来,再进行渲染。下面就要说要优化效果相关的了。 参考技术B 原理:Unity采用擦除算法来对垃圾进行预处理。它将对象图中的所有可达对象标识为活动对象,而不可达对象被标识为空闲对象。空闲对象会被收集器根据需要擦除,以减少内存的使用。 参考技术C Unity的擦除原理是通过将渲染器设置为不可见来实现的。当渲染器设置为不可见时,它将不会被渲染到屏幕上,从而达到擦除的效果。
Unity刮刮乐效果(擦除图片像素值)
实现类似刮刮乐效果,擦除图片指定像素值(修改图片Alfa通道)
参考Unity刮刮乐工程源码的实现原理,对实现方式有一些调整
这里RawImage需要保持原图大小,不能缩放,不然坐标计算会有偏差
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
[RequireComponent(typeof(RawImage))]
public class EraseHandler : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
//擦除完成调用事件
public Action eraseFinishEvent;
//笔刷半径
[SerializeField] int brushRadius = 50;
//擦除比例,擦除比例高于该值,是为擦除完成,自动擦除剩余部分
[SerializeField] float finishPercent = 0.9f;
//擦除点偏移量,距离上个擦除点>=该值时开始新的擦除点
[SerializeField] float drawOffset = 10f;
//是否以擦除完成
bool isFinish;
//要擦除的图片
RawImage eraseImage;
Texture2D eraseTexture;
//图片长宽
int textureWidth;
int textureHeight;
//图片大小
float textureLength;
//擦除部分图片大小
float eraseLength;
Camera mainCamera;
void Awake()
eraseImage = GetComponent<RawImage>();
mainCamera = Camera.main;
void Start()
Init();
void Init()
isFinish = false;
eraseLength = 0;
//原擦除图片
//Texture2D originalTexture = (Texture2D)eraseImage.mainTexture;
Texture2D originalTexture = (Texture2D)eraseImage.texture;
//被擦除的图片,展示擦除过程
eraseTexture = new Texture2D(originalTexture.width, originalTexture.height, TextureFormat.ARGB32, false);
textureWidth = eraseTexture.width;
textureHeight = eraseTexture.height;
eraseTexture.SetPixels(originalTexture.GetPixels());
eraseTexture.Apply();
Debug.Log(textureWidth + " - " + textureHeight);
eraseImage.texture = eraseTexture;
textureLength = eraseTexture.GetPixels().Length;
#region Pointer Event
public void OnPointerDown(PointerEventData eventData)
if (isFinish)
return;
tempLastPoint = eventData.position;
ErasePoint(eventData.position);
Vector2 tempEventPoint;
Vector2 tempLastPoint;
public void OnDrag(PointerEventData eventData)
if (isFinish)
return;
tempEventPoint = eventData.position;
//距离上个擦除点 >= 该值时开始新的擦除点
if ((tempEventPoint - tempLastPoint).sqrMagnitude < drawOffset * drawOffset)
return;
//擦除点
ErasePoint(tempEventPoint);
//记录点
tempLastPoint = tempEventPoint;
public void OnPointerUp(PointerEventData eventData)
if (isFinish)
return;
ErasePoint(eventData.position);
#endregion
Vector3 tempWorldPoint;
Vector3 tempLocalPoint;
Vector2Int pixelPos;
void ErasePoint(Vector2 screenPos)
//点击位置坐标转换,正交相机无效,试着改成平行相机试一下
tempWorldPoint = mainCamera.ScreenToWorldPoint(screenPos);
tempLocalPoint = transform.InverseTransformPoint(tempWorldPoint);
//相对图片像素点坐标
pixelPos.x = (int)tempLocalPoint.x + textureWidth / 2;
pixelPos.y = (int)tempLocalPoint.y + textureHeight / 2;
//点击位置是否在图片范围内
if (pixelPos.x < 0 || pixelPos.x >= textureWidth || pixelPos.y < 0 || pixelPos.y >= textureHeight)
return;
//遍历笔刷长宽范围内像素点
for (int i = -brushRadius; i <= brushRadius; i++)
//超左/右边界
if (pixelPos.x + i < 0 || pixelPos.x + i >= textureWidth)
continue;
for (int j = -brushRadius; j <= brushRadius; j++)
//超上/下边界
if (pixelPos.y + j < 0 || pixelPos.y + j >= textureHeight)
continue;
//是否在圆形范围内
if (Mathf.Pow(i, 2) + Mathf.Pow(j, 2) > Mathf.Pow(brushRadius, 2))
continue;
//像素点色值
Color color = eraseTexture.GetPixel(pixelPos.x + i, pixelPos.y + j);
//判断透明度,是否已擦除
if (Mathf.Approximately(color.a, 0))
continue;
//修改像素点透明度
color.a = 0;
eraseTexture.SetPixel(pixelPos.x + i, pixelPos.y + j, color);
//擦除数量统计
eraseLength++;
eraseTexture.Apply();
//判断擦除进度
RefreshErasePercent();
float tempPercent;
void RefreshErasePercent()
if (isFinish)
return;
tempPercent = eraseLength / textureLength;
tempPercent = (float)Math.Round(tempPercent, 2);
Debug.Log("擦除百分比 : " + tempPercent);
if (tempPercent >= finishPercent)
isFinish = true;
eraseImage.enabled = false;
//触发结束事件
if (eraseFinishEvent != null)
eraseFinishEvent.Invoke();
以上是关于unity擦除原理的主要内容,如果未能解决你的问题,请参考以下文章