C for Graphic:MacOS纹理动画

     年底了公司展开了一年的工作总结,我同时整理了一下自己的开发框架,整理到ui框架部分顺便想扩充一下动画系统。一直以来我都是用animationcurve+tween去做启动关闭动画,比如坐标移动、大小缩放等。同时市面上大部分应用和游戏也是这种效果,不过仅限于windows系统,如果是mac系统,它那个特有的隐藏显现的动画就很常见了,效果如下:

     

      这效果看起来还可以,主要是相对于常见的ui动画来说,所以我就想去实现一下。

      我思考过几种做法:

      1.使用transform的scale+animationcurve,这个显然不可能,达不到这种效果

      2.美术做一个矩形网格动画,然后将ui面板附着网格动画随着运动,可以完成功能,只需要我们采样出ui面板的当前渲染纹理,然后映射到网格uv上,接着随着运动即可,但是太死板,因为动画是固定的,而且ui面板也并非一个固定的分辨率比例。

      3.我自己写shader处理,先采样出ui面板的渲染纹理,然后构建等比例矩形网格,将纹理附着于矩形网格,然后通过特定的算法计算出这个动画,这个比较ok,代码也通用。

      首先我们仔细观察这个动画,同时分解出动画的组成部分。我先说一下我的观察结果,大致的组成(并不精准哈,有同学深入研究比我更完善当然更好)如下:

     1.矩形变成漏斗形,效果如下:

    

     我们先来想一下怎么处理这种形变,可能有朋友会说,用网格顶点动画,可是你看mac的ui线性平滑度,顶点少了肯定做不到这么平滑,所以我们只能使用uv动画,将这个矩形纹理的uv使用方程做变换,具体的方程组合法我们先看一张分析图:

    

     这里我简化了一下示意图,首先我简化了两边的曲线和底部的质点(我们暂时把A2和B2的交汇点成为质点),当然和macos的原型动画不太相同(macos的原型ui动画是变换成底部titlebar的小的矩形图标,那么大的矩形和小的矩形四个顶点uv是必须通过方程映射的,因为我个人的话unity是缩放到一个质点所以就简化了方程计算过程,当然朋友们可以根据需求去任意修改了,这属于数学问题),那么我们思考一下什么样的方程能够将二维uv矩阵变换二维uv倒三角形,如果我们用一个2x2矩阵是不是很好(没有平移所以不需要用3x3矩阵)?但是仔细一想这个不通用,因为我们只是简化了示意图模型,实际上我们这个“倒三角形”的两边是个曲线的,那么我们分两步走:1.通过A和B和质点得到三角形两条边的方程,那么我们就可以得到任意V坐标轴上A1和B1的uv值。2.得到A1和B1后,可以将原矩形uv线性插值到A1和B1之间,则得到了二维三角形uv数组。

     还是写一点计算过程方便理解,如下:

      我们计算出两条直线的方程,然后计算V轴上uv坐标,直接写代码加演示吧:

Shader "Custom/MacosUIShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _M1("Mass 1",Range(0,1)) = 0.5
        _M2("Mass 2",Range(0,1)) = 0.5
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 100

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            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 _M1;
            float _M2;
            
            //将矩形uv映射到三角形uv
            //x<=x1或x>=x2映射到-1透掉
            //x>x1且x<x2映射到0-1
            float2 mapuv(float2 uv,float x1,float x2)
            {
                float y = uv.y;
                float x = uv.x;
                if(x<=x1)
                {
                    x = -1;
                }else if(x>=x2)
                {
                    x = -1;
                }else if(x>x1 && x<x2)
                {
                    x = (x-x1)/(x2-x1);  
                }
                return float2(x,y);
            }

            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
            {
                float y = i.uv.y;
                float x1 = -_M1*y+_M1;
                float x2 = (1-_M2)*y+_M2;
                i.uv = mapuv(i.uv,x1,x2);
                fixed4 col = tex2D(_MainTex, i.uv);
                if(i.uv.x < 0)
                {
                    col.a = 0;
                }
                return col;
            }
            ENDCG
        }
    }
}

       演示图:

 

        当然了,如果我们在l1 l2的方程加上sin cos加权,就能做成曲线了,如下:

        这里我建议可以简洁的将AP的sin加权用y轴的加权去处理,为了性能考虑,同时效果也差不了多少,代码如下:

fixed4 frag (v2f i) : SV_Target
            {
                float y = i.uv.y;
                float x1 = -_M1*y+_M1;
                float sx1 = sin(y*2*PI)*_Power1;
                x1 += sx1;

                float x2 = (1-_M2)*y+_M2;
                float sx2 = sin(y*2*PI)*_Power2;
                x2 += sx2;

                i.uv = mapuv(i.uv,x1,x2);
                fixed4 col = tex2D(_MainTex, i.uv);
                if(i.uv.x < 0)
                {
                    col.a = 0;
                }
                return col;
            }

        效果如下:

  

        接下来就是uv矩阵收缩到质点就行了(当然macos是将AB点收缩到对应的小矩形图标的A2B2点,这里也是数学算法不同而已,同学们有兴趣扩展一下数学计算就行了,我这里因为只需要收缩到质点就ok),关于如何收缩到质点,我们采用2x2矩阵进行收缩,先写代码:

float2x2 mats = float2x2(
                    _Sca,0,
                    0,_Sca
                );
                i.uv = mul(mats,i.uv);

      再frag中先用一个二维矩阵去缩放uv数组,效果如下:

   

      可以看的出来缩放是按(0,0)uv矩阵原点进行的,而我们需要按照质点去缩放,所以还需要一边缩放一边平移,所以我们就需要使用3x3的矩阵去做缩放平移处理,修改一下frag中uv的预处理代码:

float3x3 matt = float3x3
                (
                    _Sca,0,_M1 - _Sca*_M1,
                    0,_Sca,0,
                    0,0,0         
                );
                float3 homuv = float3(i.uv.x,i.uv.y,1);
                homuv = mul(matt,homuv);
                i.uv = float2(homuv.x,homuv.y);

        矩阵中_Sca就是缩放值,_M1 - _Sca*_M1,就是随着sca缩放,需要平移的量,大家脑海中想象一下scale不断的放大,漏斗逐渐缩小,其质点向左平移,则需要_M1 - _Sca*_M1向右平移还原质点位置,效果如下:

 

       这样我们大致完成了macos的界面动画的几个组成部分,所以接下来就需要借助一下c#进行动画逻辑处理,当然我们在shader中处理也可以,但不建议这么做,使用CPU语言处理逻辑更加灵活,代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class MacOSUILogic : MonoBehaviour
{
    [SerializeField] private Material MacOSMat;
    [Range(0.1f,2f)]
    [SerializeField] public float CombTime = 1f;
    [Range(0.1f, 2f)]
    [SerializeField] public float PowTime = 1f;
    [Range(0.1f, 2f)]
    [SerializeField] public float ShrinkTime = 1f;
    [SerializeField] public AnimationCurve ShrinkCurve;
    [SerializeField] public AnimationCurve ExpandCurve;


    void Start()
    {
        
    }

    private void OnGUI()
    {
        if (GUI.Button(new Rect(0, 0, 100, 100), "Shrink"))
        {
            MacOSMat.DOFloat(0.98f, "_M1", CombTime);
            MacOSMat.DOFloat(0.98f, "_M2", CombTime);
            MacOSMat.DOFloat(0.1f, "_Power1", PowTime);
            MacOSMat.DOFloat(0.02f, "_Power2", PowTime);
            MacOSMat.DOFloat(50f, "_Sca", ShrinkTime).SetDelay(CombTime).SetEase(ShrinkCurve);
        }
        if (GUI.Button(new Rect(0, 200, 100, 100), "Expand"))
        {
            MacOSMat.DOFloat(1f, "_Sca", ShrinkTime).SetEase(ExpandCurve);
            MacOSMat.DOFloat(0, "_M1", CombTime).SetDelay(ShrinkTime);
            MacOSMat.DOFloat(1, "_M2", CombTime).SetDelay(ShrinkTime);
            MacOSMat.DOFloat(0, "_Power1", PowTime).SetDelay(ShrinkTime);
            MacOSMat.DOFloat(0, "_Power2", PowTime).SetDelay(ShrinkTime);
        }
        if (GUI.Button(new Rect(0, 400, 100, 100), "Restore"))
        {
            MacOSMat.SetFloat("_M1", 0);
            MacOSMat.SetFloat("_M2", 1);
            MacOSMat.SetFloat("_Power1", 0);
            MacOSMat.SetFloat("_Power2", 0);
            MacOSMat.SetFloat("_Sca", 1);
        }
    }
}

       我自己也很懒的,所以就用dotween处理了,效果如下:

  

      当然严格来说MacOS的界面动画肯定比我这个算法更加复杂,比如缩放矩阵处理的同时加上曲线浮动采样,达到一种非线性的效果,有兴趣深入的朋友可以自行研究。

      现在年前玩路易鬼屋搞得没时间写博客,今天整理年终总结来一篇。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值