美文网首页js css html
Unity杂文——UGUI中粒子的遮罩与裁剪

Unity杂文——UGUI中粒子的遮罩与裁剪

作者: 脸白 | 来源:发表于2023-09-27 10:54 被阅读0次

    原文地址

    前言

    在Unity开发中,在使用UGUI的mask的时候,如果子节点存在粒子特效,发现mask并不能裁剪粒子,比如笔者在开发中,有一个滑动列表,列表中的button上面存在按钮特效,在滑动的时候滑动的mask并不能裁剪粒子,因此笔者从网上找到了一些解决方案,并应用了一下,用着还可以。

    原理

    粒子的裁剪是用shader制作的,但是仅仅用shader是并不能满足需求的,因为特效有可能是会动的,这样剪裁区域就会发生变化,所以需要一个脚本把裁剪的区域传递给shader,然后shader在进行裁剪处理。

    C#代码

    public class TailorParticle : MonoBehaviour
    {
        private Material material;
        private Mask mask;
        private RectMask2D rectmask2d;
        public void Start()
        {
            material = GetComponentInChildren<ParticleSystem>().GetComponent<Renderer>().material;
            mask = GetComponentInParent<Mask>();
            rectmask2d = GetComponentInParent<RectMask2D>();
            SetClip();
            //如果运行时裁剪区域不会发生改变,可以注释掉下面这句代码
            var scrollrecct = GetComponentInParent<ScrollRect>();
            if (scrollrecct)
            {
                scrollrecct.onValueChanged.AddListener(v => { SetClip(); });
            }
        }
    
        private bool isMask;
        private Vector3[] corners = new Vector3[4];
        private Vector3[] cornerstemp = new Vector3[4];
        public void SetClip()
        {
            //获取到需要裁剪的区域
            isMask = false;
            if (mask) 
            {
                mask.GetComponent<RectTransform>().GetWorldCorners(corners);
                isMask = true;
            }
            if(rectmask2d)
            {
                rectmask2d.GetComponent<RectTransform>().GetWorldCorners(cornerstemp);
                if (isMask)
                {
                    corners[0].x = Mathf.Min(corners[0].x, cornerstemp[0].x);
                    corners[0].y = Mathf.Min(corners[0].y, cornerstemp[0].y);
                    corners[2].x = Mathf.Max(corners[2].x, cornerstemp[2].x);
                    corners[2].y = Mathf.Max(corners[2].y, cornerstemp[2].y);
                }
                else
                {
                    isMask = true;
                }
            }
            if (material && isMask)
            {
                //将裁剪区域传入到Shader中
                material.SetFloat("_MinX", corners[0].x);
                material.SetFloat("_MinY", corners[0].y);
                material.SetFloat("_MaxX", corners[2].x);
                material.SetFloat("_MaxY", corners[2].y);
            }
        }
    }
    

    代码分析

    material = GetComponentInChildren<ParticleSystem>().GetComponent<Renderer>().material;
    mask = GetComponentInParent<Mask>();
    rectmask2d = GetComponentInParent<RectMask2D>();
    SetClip();
    //如果运行时裁剪区域不会发生改变,可以注释掉下面这句代码
    var scrollrecct = GetComponentInParent<ScrollRect>();
    if (scrollrecct)
    {
        scrollrecct.onValueChanged.AddListener(v => { SetClip(); });
    }
    

    首先我们先看Start函数中,material是获取子节点的粒子特效的材质(如果粒子多了可以自己扩展成组),mask是获取父节点的Mask遮罩,rectmask2d和mask一样是获取父节点的RectMask2D组件,笔者之所以获取RectMask2D这个组件是因为笔者有些裁剪是用这个做的。接着就是进行裁剪函数,这里后面介绍。正常的裁剪到这里就结束了,但是我们如果想裁剪区域进行动态变化,那我们就要进行动态刷新shader,笔者这里只是简单的用ScrollRect进行举例,大家可以根据自己的项目进行监听。只要当变化的时候刷新一下裁剪就行了。

    isMask = false;
    if (mask) 
    {
        mask.GetComponent<RectTransform>().GetWorldCorners(corners);
        isMask = true;
    }
    if(rectmask2d)
    {
        rectmask2d.GetComponent<RectTransform>().GetWorldCorners(cornerstemp);
        if (isMask)
        {
            corners[0].x = Mathf.Min(corners[0].x, cornerstemp[0].x);
            corners[0].y = Mathf.Min(corners[0].y, cornerstemp[0].y);
            corners[2].x = Mathf.Max(corners[2].x, cornerstemp[2].x);
            corners[2].y = Mathf.Max(corners[2].y, cornerstemp[2].y);
        }
        else
        {
            isMask = true;
        }
    }
    

    接着我们来看一下裁剪的代码,首先是标记不需要裁剪,只有当父节点存在Mask的时候才进行裁剪,然后就是获取父节点的剪裁区域,笔者这里是把两个Mask进行融合,获取最小的范围,这里可以根据自己的需求进行变化,然后就是关键性的代码

    if (material && isMask)
    {
        //将裁剪区域传入到Shader中
        material.SetFloat("_MinX", corners[0].x);
        material.SetFloat("_MinY", corners[0].y);
        material.SetFloat("_MaxX", corners[2].x);
        material.SetFloat("_MaxY", corners[2].y);
    }
    

    这里就是将剪裁区域传递给次材质的shader,然后shader进行裁剪。

    Shader关键代码

    Properties {
        
        ...
    
        _MinX ("Min X", Float) = -10
        _MaxX ("Max X", Float) = 10
        _MinY ("Min Y", Float) = -10
        _MaxY ("Max Y", Float) = 10
    }
    
    SubShader 
    {
        Pass {
    
            ...
    
            float _MinX;
            float _MaxX;
            float _MinY;
            float _MaxY;
            
            ...
    
            float4 frag(VertexOutput i) : COLOR {
                
                ...
            
                c.a *= (i.vpos.x >= _MinX );
                c.a *= (i.vpos.x <= _MaxX);
                c.a *= (i.vpos.y >= _MinY);
                c.a *= (i.vpos.y <= _MaxY);
    
                c.rgb *= c.a;
    
                return c;
            }
    
            ...
        }
    }
    

    shader的代码也比较简单,就是将传过来的区域进行判断,如果在区域内据显示,如果超出区域就将颜色的透明度设置为0,也就看不见了。

    相关文章

      网友评论

        本文标题:Unity杂文——UGUI中粒子的遮罩与裁剪

        本文链接:https://www.haomeiwen.com/subject/uvrkbdtx.html