先上效果图
这应该不太陌生的吧,这就是雨松大牛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);
}
}