Unity Shader Graph:为UI组件编写自定义shader graph

  1. 使用voronoi做动态背景
  2. Unity shader graph:为UI组件编写自定义shader graph
  3. 点击UI按钮,触发水波涟漪效果(使用脚本操作UI)
  4. 水面波光效果

1. 使用voronoi做动态背景

如何把彩色渐变映射给灰度图?
答:直接把灰度图连给彩色渐变的UV输入即可

为什么像素图可以充当彩色渐变?
答:像素图经过图像插值(image interpolation),在Unity里自动呈现渐变效果。

没有Photoshop的话,强烈推荐一个在线绘制像素图的网站:https://www.pixilart.com

2. Unity shader graph:为UI组件编写自定义shader graph

以Image 为例:Unity - Manual: Image (unity3d.com)

Image 组件下有2个属性:Source Image(即sprite)和Color。
这样做的好处是我们可以直接修改这两个参数,而不需要给每个Image都创建一个材质!

我们也可以使用自定义的材质球:

  • Source Image(即sprite)对应材质球shader中的“_MainTex
  • Color对应Vertex Color,——sprite使用mesh的vertex color作为颜色

如果shader里贴图的reference为“_MainTex”,那么最终呈现的是Image 组件下的Source Image,它会覆盖Material下的Texture输入;

如果你不想使用Image 组件下的Source Image,你就是想让Material下的Texture输入生效,那么保持Source Image为空,并且shader里贴图的reference别叫“_MainTex”,叫其他啥名都OK。

3. 点击UI按钮触发水波涟漪效果(使用脚本操作UI)

Shader Graph主要参考这篇教程制作涟漪效果:

傲娇的露易丝:Unity2018 Shader Graph 学习笔记(八) 水波涟漪的实现45 赞同 · 18 评论文章正在上传…重新上传取消​

在Update()里控制Amplitude逐渐归零,就可以模拟涟漪逐渐消失的效果:

using UnityEngine;
using UnityEngine.UI;

public Material ripple_mat;
float temp;

void Start()
    {
        ripple_mat = this.GetComponent<Image>().material;
    }
void Update()
    {
        if (temp < 0.02f)
        {
            ripple_mat.SetFloat("_Amplitude", 0);
        }
        else
        {
            ripple_mat.SetFloat("_Amplitude", Mathf.Lerp(temp, 0, 3.8f*Time.deltaTime));
            temp = ripple_mat.GetFloat("_Amplitude");   
        }
    }

接下来要解决的问题是:鼠标点击UI按钮任一位置,并以此为中心触发涟漪

在shader graph里我添加了2个参数:
UIsize获取UI图片的尺寸(为了让涟漪是个正圆),RippleCenter决定涟漪的中心位置

假设UI图片的尺寸是312x100,那么RippleCenter.x的取值范围就是[0,312],RippleCenter.y的取值范围就是[0,100]

大致思路:
1. 监听按钮点击,获取鼠标在屏幕空间的坐标,即屏幕坐标,Input.mousePosition
2. 使用RectTransformUtility.ScreenPointToLocalPointInRectangle函数,把屏幕坐标转换为UI坐标。关于这个函数的解释:

絮酱酱:【Unity API 翻译】UGUI 屏幕坐标转UI坐标方法 – ScreenPointToLocalPointInRectangle()23 赞同 · 11 评论文章正在上传…重新上传取消​

UI图片以正中心为原点,所以UI坐标的取值范围是:
[-width/2,+width/2],[-height/2,+height/2],并且与scale无关!

3. 显然UI坐标和我们需要的RippleCenter不在一个区间,需要转换一下。
完整的脚本:

using UnityEngine;
using UnityEngine.UI;

public class RippleOnClickUI : MonoBehaviour
{
    public bool enable_Ripple = false;
    public Material ripple_mat;
    public GameObject obj_UI;
    public float amp = 0.2f; //涟漪从这个振幅值开始衰减
    private float temp;//临时记录涟漪振幅的变量
    private Vector2 center;//shader graph里的变量"RippleCenter"
    private Vector2 uiSize;//UI的尺寸,同时也是shader graph里的变量"UIsize"

    void Start()
    {
        ripple_mat = this.GetComponent<Image>().material;
        
        obj_UI=this.transform.gameObject;//获取当前transform组件所在的gameObject
        obj_UI.GetComponent<Button>().onClick.AddListener(Click);
        
        uiSize = obj_UI.GetComponent<RectTransform>().sizeDelta;
        ripple_mat.SetVector("_UIsize", uiSize);
    }

    void Update()
    {
        if (temp < 0.02f)
        {
            ripple_mat.SetFloat("_Amplitude", 0);
        }
        else
        {
            ripple_mat.SetFloat("_Amplitude", Mathf.Lerp(temp, 0, 3.8f*Time.deltaTime));
            temp = ripple_mat.GetFloat("_Amplitude");   
        }
    }

    public void Click()
    {
        if (enable_Ripple)
        {
            ripple_mat.SetFloat("_Amplitude", amp);
            temp = ripple_mat.GetFloat("_Amplitude");

            RectTransformUtility.ScreenPointToLocalPointInRectangle(
            obj_UI.GetComponent<RectTransform>(),//The RectTransform to find a point inside.
            Input.mousePosition,//Screen space position.
            Camera.main,//The camera associated with the screen space position.
            out Vector2 localPoint);//Point in local space of the rect transform.

            center.x = localPoint.x + uiSize.x / 2;
            center.y = localPoint.y + uiSize.y / 2;

            ripple_mat.SetVector("_Center", center);
        }
    }
}

4. 水面波光效果

教程:【シェーダーグラフメモ その50】Voronoiを利用した水面エフェクト - rn.log (hatenablog.com)

----------------------------------------------------------

上面截图里的文字都是图片,换成text就惨了,啊我没有想到过text不是image!而是等价于佷多张image,一个字符就是一张图片,并且他们来自同一张图集(atlas)!

  • text由一个个字符组成(character),如果不给Alpha或者shader里没有"_MainTex"的话,出来的就是一个个矩形方块,相当于一张张小图片
  • 我猜啊,text组件根据字体和文本,背着我们生成了一张Font Texture(图集,atlas),上面排列着文本包含的字符形象(乱序),然后每个字符方块在这张临时的Font Texture上有自己对应的坐标,对应自己的字符形象
  • 所以,给text做水波涟漪效果,相当于是在好几张小图片上做效果,这肯定不对啊……唉,我搞不定了……可能需要把text写成texture再当成image来处理吧……

同理,如果你的sprite来自于sprite atlas(图集),它将和text一样不能适用这个shader graph。切分sprite只是方便操作,实际渲染的时候其实只有一张图集。shader graph只获取贴图和UV数据,而无从得知单个被切分的sprite的信息。
参考:

https://forum.unity.com/threads/shader-graph-being-shared-by-all-the-images-of-multiple-sprite.734318/​forum.unity.com/threads/shader-graph-being-shared-by-all-the-images-of-multiple-sprite.734318/

https://forum.unity.com/threads/shader-graph-and-multiple-sprite-mode.706733/​forum.unity.com/threads/shader-graph-and-multiple-sprite-mode.70673