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擦除原理的主要内容,如果未能解决你的问题,请参考以下文章

java泛型泛型的内部原理:类型擦除以及类型擦除带来的问题

说一下范型原理

泛型的内部原理:类型擦除以及类型擦除带来的问题

java泛型 泛型的内部原理:类型擦除以及类型擦除带来的问题

JAVA泛型实现原理

Keep面经汇总