一些特殊的动画,在unity中并不能使用。最近需要实现一种线条慢慢生长的过程,从无到有,还要掉头。这种动画貌似可以使用一些插件来使用,但想想更好的方案就是利用Shader来实现,虽然学习Shader很长时间了,但具体用它来做功能的项目还是比较少。这也算是个相对简单的Shader,其中用到的算法也比较初级,但基本功能也初步实现了,故mark一下。
方案
1.以空物体做标记记录关键点
如下图所示,如果模型有多个转弯的点,就可以制作多少个节点,不只局限于cube,可以是很多弯头的线。
2.以线为轴心计算相对半径
利用脚本将当前进度的点的列表传入到Shader中,然后判断是否满足渲染的要求。
3.舍去不在区域内的像素
在半径内的点渲染出来,不在半径内的点舍去
源码
using System;
using UnityEngine;
using System.Collections;
public class ClipLine : MonoBehaviour
{
public string matName;
public float speed = 0.2f;
public bool once = true;
public float span = 0.01f;
private Material mat;
private float lastdistence;
private float distenceAll;
private float distence;
private Vector4[] posList;
private float[] distenceList;
private int count;
void Start()
{
Renderer render = GetComponent<Renderer>();
if (string.IsNullOrEmpty(matName))
{
mat = render.material;
}
else
{
mat = Array.Find<Material>(render.materials, x => x.name == matName);
}
if (!mat || transform.childCount < 2)
{
this.enabled = false;
}
else
{
distenceList = new float[transform.childCount - 1];
for (int i = 0; i < transform.childCount; i++)
{
if (i > 0)
{
distenceList[i - 1] = Vector3.Distance(transform.GetChild(i - 1).position, transform.GetChild(i).position);
distenceAll += distenceList[i - 1];
}
}
}
}
void Update()
{
distence += Time.deltaTime * speed;
if (distence > distenceAll)
{
distence = 0;
if (once)
{
mat.SetInt("_Count", 0);
enabled = false;
return;
}
}
if (Mathf.Abs(distence - lastdistence) > 0.01f)
{
CalcutPoistionList();
OnPositionChanged();
lastdistence = distence;
}
}
void CalcutPoistionList()
{
float distenceTemp = 0f;
count = 1;
for (int i = 0; i < distenceList.Length; i++)
{
count++;
distenceTemp += distenceList[i];
if (distenceTemp > distence)
{
distenceTemp -= distenceList[i];
break;
}
}
posList = new Vector4[20];
int index = 0;
for (; index < count - 1; index++)
{
posList[index] = transform.GetChild(index).localPosition;
posList[index].w = 1;
}
posList[index] = Vector4.Lerp(transform.GetChild(index - 1).localPosition, transform.GetChild(index).localPosition,
(distence - distenceTemp) / distenceList[index - 1]);
}
void OnPositionChanged()
{
mat.SetInt("_Count", count);
#if UNITY_5_6_OR_NEWER
mat.SetVectorArray("_Pos", posList);
#elif UNITY_5_3_OR_NEWER
for (int i = 0; i < posList.Length; i++)
{
mat.SetVector("_Pos" + i, posList[i]);
}
#endif
}
}
Shader "Unlit/ClipLine"
{
Properties
{
_Color("Color",Color) = (1,1,1,1)
_MainTex("Texture", 2D) = "white" {}
_Round("Round",Float) = 0
}
SubShader
{
Tags{ "RenderType" = "Opaque" }
LOD 100
Pass
{
Cull Back
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
bool IsInsidePosList(float4 vec);
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Color;
float _Round;
int _Count;
uniform vector _Pos[20];
int _Index;//区域
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 op :TEXCOORD1;
};
v2f vert(appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.op = v.vertex;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
if (!IsInsidePosList(i.op)){
clip(-1);
}
return col * _Color;
}
///在指定的区域内
bool IsInsidePosList(float4 vec)
{
if (_Count == 0) return true;
for (int i = 0; i < _Count -1; i++)
{
float3 dir1 = vec.xyz - _Pos[i + 1].xyz;
float3 dir2 = vec.xyz - _Pos[i].xyz;
float3 dirtemp = _Pos[i].xyz - _Pos[i + 1].xyz;
float dot1 = dot(dir1, dirtemp);
float dot2 = dot(dir2,-dirtemp);
if (dot1 * dot2 > 0)
{
float angle = acos(dot1 / (length(vec.xyz - _Pos[i + 1].xyz) * length(_Pos[i].xyz - _Pos[i + 1].xyz)));
float dis = length(vec.xyz - _Pos[i + 1].xyz)*sin(angle);
if (dis < _Round)
{
return true;
}
}
}
return false;
}
ENDCG
}
}
}
说明
1.距离问题
如果模型本身带有缩放,那么shader中的距离需要相应的调整
2.版本问题
新版本(5.6.0)的Shader格式不一样,需要将首行重写
3.点集问题
5.6.0传一组数据,长度要和Shader中一样