Unity随记(三) 方形头像与圆形头像的切换过度效果

20 篇文章 1 订阅

先上效果图
在这里插入图片描述
这应该不太陌生的吧,这就是雨松大牛Unity3D研究院里面的头像效果,最开始逛他的文章就发现这个效果有点意思,于是自己动手在Unity里面实现一个类似的效果。
其实关键就两步操作,1.实现图片的圆形裁剪,2.控制图片的旋转

圆形裁剪

要做圆形裁剪,首先想到的就是用Shader来处理了,通过图片的UV值可以明确的知道,裁剪后的圆的圆心处的UV值其实是(0.5,0.5),而左下角的值是(0,0),右上角是(1,1)
在这里插入图片描述
任一点A到O的距离如果大于半径(0.5)则进行裁剪操作,而其他部分则保留,就能得到一个正方形的内切圆,而最远距离P到O则为根号2的一半。
frag中的关键代码:

//裁剪 step(a,x)  x >= a  return 1,  x < a return 0
col.a = step(distance(i.uv, float2(0.5, 0.5)), _ClipRange);

其实上面可以这样写:

if(distance(i.uv, float2(0.5, 0.5)) > _ClipRange)
{
	discard;
}

只是为了避免使用discard操作,而选择设置透明度,这个就得配合blend操作了,比如这里设置的是blend SrcAlpha OneMinusSrcAlpha

效果如下:
在这里插入图片描述
这里有个Outline,其实是为了打算让裁剪后的头像有个外边框,比如当前这张图没有外边框,裁剪后的效果:
在这里插入图片描述
添加Outline的关键代码如下:

//添加Outline
if(distance(i.uv, float2(0.5, 0.5)) > _ClipRange - _Outline)
{
	col.rgb = _OutlineColor.rgb;
}

完整的Shader代码如下:

Shader "vitens/circle"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _ClipRange("Clip Range", Range(0, 0.7071)) = 0.5
        _Outline("Outline", Range(0, 0.2)) = 0
        _OutlineColor("Outline color", COLOR) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _ClipRange;
            float _Outline;
            fixed4 _OutlineColor;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);

                //裁剪 step(a,x)  x >= a  return 1,  x < a return 0
                col.a = step(distance(i.uv, float2(0.5, 0.5)), _ClipRange);

                //添加Outline
                if(distance(i.uv, float2(0.5, 0.5)) > _ClipRange - _Outline)
                {
                    col.rgb = _OutlineColor.rgb;
                }

                return col;
            }
            ENDCG
        }
    }
}

接下来就是旋转的控制了

图片旋转

旋转的话控制就方便了,还有DoTween之类的,直接使用即可,这里就自己来简单实现,直接控制Transform的Inspector面板中Rotation中的Z值。

为了达到平滑过渡的效果,则是在旋转的过程中通过插值的方式(lerp)来控制裁剪与Outline的值,而旋转的节奏则是通过AnimtionCurve来控制的,完整代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class HeadChange : MonoBehaviour, IPointerClickHandler
{
    [Range(1, 5)]
    public int rotateCount = 3;//旋转圈数
    [Range(0, 2)]
    public float speed = 5;//速度

    public AnimationCurve timeCurve;

    const float SQUARE_ROOT_OF_2 = 1.4142f;
    const float SQUARE_CIRCLE_CLIP = SQUARE_ROOT_OF_2 / 2;
    const float CIRCLE_CLIP = 0.5f;
    const float OUTLINE = 0.02f;

    Transform _ts;
    Material _mat;
    float _tick = 1;
    float _progress;
    bool _stop = false;
    bool _squareToCircle = false;
    bool _circleToSquare = false;

    // Start is called before the first frame update
    void Start()
    {
        _ts = transform;
        _mat = GetComponent<RawImage>().material;
        _mat.SetFloat("_ClipRange", SQUARE_ROOT_OF_2 / 2);

        _tick = 1 / speed;
        _progress = 0;
        _stop = false;
        _squareToCircle = false;
        _circleToSquare = false;
    }

    // Update is called once per frame
    void Update()
    {
        if (_stop)
        {
            return;
        }

        _tick += Time.deltaTime;
        _progress = timeCurve.Evaluate(_tick * speed);

        //结束标志
        if (_progress >= 1)
        {
            _stop = true;
        }

        if (_squareToCircle)
        {
            _mat.SetFloat("_ClipRange", Mathf.Lerp(SQUARE_CIRCLE_CLIP, CIRCLE_CLIP, _progress));
            _mat.SetFloat("_Outline", Mathf.Lerp(0, OUTLINE, _progress));
            _ts.localEulerAngles = new Vector3(0, 0, Mathf.Lerp(0, -360 * rotateCount, _progress));
        }
        else if (_circleToSquare)
        {
            _mat.SetFloat("_ClipRange", Mathf.Lerp(CIRCLE_CLIP, SQUARE_CIRCLE_CLIP, _progress));
            _mat.SetFloat("_Outline", Mathf.Lerp(OUTLINE, 0, _progress));
            _ts.localEulerAngles = new Vector3(0, 0, Mathf.Lerp(0, 360 * rotateCount, _progress));
        }
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        //重置结束标志
        _stop = false;

        //记录当前进度,以便反向旋转时从当前进度开始反向播放动画
        _progress = 1 - _progress;
        _tick = (1 - _tick * speed) / speed;

        if (!_squareToCircle)
        {
            _squareToCircle = true;
            _circleToSquare = false;
        }
        else
        {
            _circleToSquare = true;
            _squareToCircle = false;
        }
    }

    private void OnDestroy()
    {
        //材质球属性还原
        _mat.SetFloat("_ClipRange", 1);
        _mat.SetFloat("_Outline", 0);
    }
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值