使用Unity ShaderGraph实现刮刮乐的刮卡剔除效果,感受一下刮中500万的时刻

一、前言

点关注不迷路,持续输出Unity干货文章。

嗨,大家好,我是新发。昨天写了篇文章:《使用Unity ShaderGraph实现在模型上涂鸦的效果,那么,纹个手吧》
这个原理,可以触类旁通,比如刮刮乐的刮卡效果,也可以利用这个原理来实现,今天就教大家如何使用Unity ShaderGraph实现刮刮乐的刮卡剔除效果。
声明:本文介绍的办法不是做刮刮乐效果的最好办法,只是单纯地进行演示。

本文最终效果如下:
在这里插入图片描述
本文Demo工程已上传到CodeChina(最近GitHub貌似有问题,经常连不上),感兴趣的同学可自行下载学习。
CodeChina地址:https://codechina.csdn.net/linxinfa/UnityShaderGraphScratchTicket

注意,我使用的Unity版本是2020.2.7f1c1ShaderGraph版本是Version 10.3.2 - March 01, 2021,如果你使用的版本比我的版本低,则可能运行我的Demo工程会有问题。
在这里插入图片描述

二、原理

原理就是利用Alpha通道,Alpha0时透明,我们可以使用一张RenderTexture作为Alpha通道图,与UI的主贴图的Alpha通道图相乘即可。
根据鼠标刮的位置,在RenderTexture上对应的位置画笔刷印记即可。

三、实操

1、RenderTexture

首先,创建一个Render Texture
在这里插入图片描述
设置一下Render Texture的尺寸和格式。
在这里插入图片描述

2、笔刷图案

photoshop做两张图,一张纯黑色的方图(用于初始化填充Render Texture),一张笔刷图,简单起见,笔刷图案我就用一个白点。
如下:
在这里插入图片描述

3、写脚本:ScratchUI.cs

开始写代码,就一个脚本:ScratchUI.cs。代码的注释我写得比较清晰了,大家应该能看懂。
代码如下:

// ScratchUI.cs

using UnityEngine;
using UnityEngine.EventSystems;


/// <summary>
/// 刮刮乐UI
/// </summary>
public class ScratchUI : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
   
    /// <summary>
    /// 绘制的目标图片
    /// </summary>
    public RenderTexture renderTexture;
    /// <summary>
    /// 笔刷
    /// </summary>
    public Texture brushTexture;

    /// <summary>
    /// 空白图
    /// </summary>
    public Texture blankTexture;

    /// <summary>
    /// mask的RectTransform
    /// </summary>
    public RectTransform rectTransform;
    /// <summary>
    /// 画布
    /// </summary>
    public Canvas canvas;

    private bool m_isMove = false;

    private void Start()
    {
   
        DrawBlank();
    }

    /// <summary>
    /// 初始化RenderTexture
    /// </summary>
    private void DrawBlank()
    {
   
        // 激活rt
        RenderTexture.active = renderTexture;
        // 保存当前状态
        GL.PushMatrix();
        // 设置矩阵
        GL.LoadPixelMatrix(0, renderTexture.width, renderTexture.height, 0);

        // 绘制贴图
        Rect rect = new Rect(0, 0, renderTexture.width, renderTexture.height);
        Graphics.DrawTexture(rect, blankTexture);

        // 弹出改变
        GL.PopMatrix();

        RenderTexture.active = null;
    }

    /// <summary>
    /// 在RenderTexture的(x,y)坐标处画笔刷图案
    /// </summary>
    /// <param name="x">Graphics坐标系下的x</param>
    /// <param name="y">Graphics坐标系下的y</param>
    private void Draw(int x, int y)
    {
   
        // 激活rt
        RenderTexture.active = renderTexture;
        // 保存当前状态
        GL.PushMatrix();
        // 设置矩阵
        GL.LoadPixelMatrix(0, renderTexture.width, renderTexture.height, 0);


        // 绘制笔刷图案
        x -= (int)(brushTexture.width * 0.5f);
        y -= (int)(brushTexture.height * 0.5f);
        Rect rect = new Rect(x, y, brushTexture.width, brushTexture.height);
        Graphics.DrawTexture(rect, brushTexture);

        // 弹出改变
        GL.PopMatrix();

        RenderTexture.active = null;
    }

    /// <summary>
    /// 按下
    /// </summary>
    public void OnPointerDown(PointerEventData data)
    {
   
        m_isMove = true;
    }

    /// <summary>
    /// 抬起
    /// </summary>
    public void OnPointerUp(PointerEventData data)
    {
   
        m_isMove = false;
    }

    private void Update()
    {
   
        if (m_isMove)
        {
   
            OnMouseMove(Input.mousePosition);
        }
    }

    /// <summary>
    /// 刮卡
    /// </summary>
    /// <param name="position">刮卡的屏幕坐标</param>
    private void OnMouseMove(Vector2 position)
    {
   
        // 获取刮的位置的ui局部坐标
        var uiLocalPos = ScreenPosToUiLocalPos(position, rectTransform, canvas.worldCamera);
        // 将局部坐标转化为uv坐标
        var uvX = (rectTransform.sizeDelta.x / 2f + uiLocalPos.x) / rectTransform.sizeDelta.x;
        var uvY = (rectTransform.sizeDelta.y / 2f + uiLocalPos.y) / rectTransform.sizeDelta.y;
        // 将uv坐标转化为Graphics坐标
        var x = (int)(uvX * renderTexture.width);
        // 注意,uv坐标系和Graphics坐标系的y轴方向相反
        var y = (int)(renderTexture.height - uvY * renderTexture.height);

        Draw(x, y);
    }

    /// <summary>
    /// 将屏幕坐标抓话为目标RectTransform的局部坐标
    /// </summary>
    /// <param name="screenPos">屏幕坐标</param>
    /// <param name="transform">目标RectTransform</param>
    /// <param name="cam">摄像机</param>
    /// <returns>ui局部坐标</returns>
    private Vector2 ScreenPosToUiLocalPos(Vector3 screenPos, RectTransform transform, Camera cam)
    {
   
        Vector2 uiLocalPos;

        if (RectTransformUtility.ScreenPointToLocalPointInRectangle(transform, screenPos, cam, out uiLocalPos))
        {
   
            return uiLocalPos;
        }
        return Vector2.zero;
    }
}

4、ShaderGraph

创建一个Unlit ShaderGraph,实现UI主贴图的Alpha通道与和RenderTexture的相乘。
在这里插入图片描述
注意Graph Settings设置SurfaceTransparentBlend设置为Alpha
在这里插入图片描述
暴露出两个变量,方便在材质球中设置参数。
在这里插入图片描述

5、UI制作

准备刮刮乐的UI图片,导入Unity中。
在这里插入图片描述 在这里插入图片描述
拼成界面,如下。mask层就是要被刮掉的层。
在这里插入图片描述
在这里插入图片描述

6、材质球

创建一个材质球ScratchMaterial,使用上面做的ShaderGraph,给材质球赋值贴图。
在这里插入图片描述
在这里插入图片描述
最后将材质赋给maskMaterial
在这里插入图片描述

7、挂脚本

ScratchUI.cs脚本挂到mask上,并设置好参数。
RenderTexture:用于Alpha通道图;
Brush Texture:笔刷图案,一个白点;
Blank Texture:一张纯黑色的空白图;
RectTransformmaskRectTransform,用于坐标转换;
Canvas:用于坐标转换。
在这里插入图片描述

四、运行测试

运行Unity,测试效果如下:
在这里插入图片描述