原理:
遊戲用到的Motion Trail原理其實很簡單,就是利用一個Queue,將兩參考點於每個Frame的位置記錄下來,這記錄通常是有條件的,也許是兩參考點與上一個Frame的紀錄比較距離,若高過某一threadhould就push in,此外在每個Frame裡也檢查已經在Queue中的其它點,生命周期是不是已經到了,如果是就將這次的紀錄pop out。Pseudo Code如下所示:
Update() { float now = Time.time;
Vector3 P1 = m_CurPointA;
Vector3 P2 = m_CurPointB;
Vector3 preP1 = m_Queue[0].PointA;
Vector3 preP2 = m_Queue[0].PointB;
if( (P1 - preP1).length > 0.5f ||
(P2 - preP2).length > 0.5f ) {
m_Queue.push( p1, p2, now );
}
while( m_Queue.count > 0 &&
now - m_Queue[end].time > 1.0f ) {
m_Queue.pop( end );
}
}
接下來的問題就是,將這Queue裡的“點”加上建構三角面的Index及計算出貼圖UV,進行Rendering,即可得到兩點間的移動軌跡,加上貼圖就如同上圖所示了。Pseudo Code如下所示://Generate triangles indices:
trailMesh.triangles = new int[(m_Queue.count-1)*2*3];
for( int i=0; i trailMesh.triangles[i * 6 + 0] = i * 2; i++ ) {
trailMesh.triangles[i * 6 + 0] = i * 2;
trailMesh.triangles[i * 6 + 1] = i * 2 + 1;
trailMesh.triangles[i * 6 + 2] = i * 2 + 2;
trailMesh.triangles[i * 6 + 3] = i * 2 + 2;
trailMesh.triangles[i * 6 + 4] = i * 2 + 1;
trailMesh.triangles[i * 6 + 5] = i * 2 + 3;
}
進階修飾:
如果只單純的這樣產生Motion Trail,你因該會發現,畫出來的弧線會菱菱角角的不是很好看。至於要怎麼加強這個部分呢?很簡單,就拿曲線演算來使用吧!在這裡使用的是Catmull-Rom Spline,它有幾個特點:
1. Catmull-Rom保證,曲線一定通過控制點
2. Spline C1 contunuous,這表示在tangent的方向與長度上,不會有區別
3. 計算簡單
數學表示式如下:
q(t) = 0.5 *( (2 * P1) + (-P0 + P2) * t + (2*P0 - 5*P1 + 4*P2 - P3) * t2 + (-P0 + 3*P1- 3*P2 + P3) * t3)
Pseudo Code如下所示:
public static TrailSection Catmull_Rom(
TrailSection p0, TrailSection p1,
TrailSection p2, TrailSection p3,
float t )
{
TrailSection section = new TrailSection();
float t2 = t * t;
float t3 = t2 * t;
float a0 = -t3 + 2*t2 - t;
float a1 = 3*t3 - 5*t2 + 2;
float a2 = -3*t3 + 4*t2 + t;
float a3 = t3 - t2;
section.pointS = (a0*p0.pointS + a1*p1.pointS + a2*p2.pointS + a3*p3.pointS) * 0.5f;
section.pointE = (a0*p0.pointE + a1*p1.pointE + a2*p2.pointE + a3*p3.pointE) * 0.5f;
section.time = (a0*p0.time + a1*p1.time + a2*p2.time + a3*p3.time) * 0.5f;
return section;
}
完整Script:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
class TrailSection
{
public static TrailSection Catmull_Rom( TrailSection p0, TrailSection p1,
TrailSection p2, TrailSection p3,
float t )
{
TrailSection section = new TrailSection();
float t2 = t * t;
float t3 = t2 * t;
float a0 = -t3 + 2*t2 - t;
float a1 = 3*t3 - 5*t2 + 2;
float a2 = -3*t3 + 4*t2 + t;
float a3 = t3 - t2;
section.pointS = (a0*p0.pointS + a1*p1.pointS + a2*p2.pointS + a3*p3.pointS) * 0.5f;
section.pointE = (a0*p0.pointE + a1*p1.pointE + a2*p2.pointE + a3*p3.pointE) * 0.5f;
section.time = (a0*p0.time + a1*p1.time + a2*p2.time + a3*p3.time) * 0.5f;
return section;
}
public Vector3 pointS;
public Vector3 pointE;
public float time;
}
class TrailMesh
{
public Vector3[] vertices = null;
public Color[] colors = null;
public Vector2[] uv = null;
public int[] triangles = null;
}
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class MotionTrail : MonoBehaviour {
public Vector3 m_refStartPoint = Vector3.zero;
public Vector3 m_refEndPoint = Vector3.zero;
public float m_time = 2.0f;
public float m_minDistance = 0.1f;
public Color m_startColor = Color.white;
public Color m_endColor = new Color(1, 1, 1, 0);
public float m_trailScale = 1.0f;
public int m_MaxSections = 10;
public int m_MaxSegments = 3;
private List m_sections = new List ();
private List m_sectionsT = new List ();
private Mesh m_mesh;
protected void Start () {
m_mesh = GetComponent ().mesh;
}
protected void LateUpdate () {
Matrix4x4 mat = transform.localToWorldMatrix;
Vector3 S = mat.MultiplyPoint( m_refStartPoint );
Vector3 E = mat.MultiplyPoint( m_refEndPoint );
float now = Time.time;
//Add a new trail section:
if( m_sections.Count == 0 ||
(m_sections[0].pointS - S).sqrMagnitude > m_minDistance * m_minDistance ||
(m_sections[0].pointE - E).sqrMagnitude > m_minDistance * m_minDistance)
{
TrailSection section = new TrailSection();
section.pointS = S;
section.pointE = E;
section.time = now;
m_sections.Insert( 0, section );
}
//Remove old sections:
while( m_sections.Count > 0 &&
(now > m_sections[m_sections.Count - 1].time + m_time ||
m_sections.Count > m_MaxSections))
{
m_sections.RemoveAt( m_sections.Count-1 );
}
//Rebuild mesh:
m_mesh.Clear();
if( m_sections.Count < 4 )
return;
//Generate mesh:
TrailMesh trailMesh = GenerateTrailMesh();
//Assign to mesh:
if( trailMesh != null ) {
m_mesh.vertices = trailMesh.vertices;
m_mesh.colors = trailMesh.colors;
m_mesh.uv = trailMesh.uv;
m_mesh.triangles = trailMesh.triangles;
}
}
protected void OnDrawGizmosSelected() {
Matrix4x4 mat = transform.localToWorldMatrix;
mat.MultiplyPoint( m_refStartPoint );
Vector3 S = mat.MultiplyPoint( m_refStartPoint );
Vector3 E = mat.MultiplyPoint( m_refEndPoint );
Gizmos.DrawWireSphere( S, 0.02f );
Gizmos.DrawWireSphere( E, 0.02f );
Gizmos.DrawLine( S, E );
}
private TrailMesh GenerateTrailMesh () {
List sections = InterpolateTrailMesh();
TrailMesh trailMesh = new TrailMesh();
trailMesh.vertices = new Vector3[sections.Count*2];
trailMesh.colors = new Color[sections.Count*2];
trailMesh.uv = new Vector2[sections.Count*2];
TrailSection curSection = sections[0];
//Use matrix instead of transform.TransformPoint for performance reasons
Matrix4x4 localSpaceTransform = transform.worldToLocalMatrix;
//Generate vertex, uv and colors:
Vector2 uv1 = Vector2.zero;
Vector2 uv2 = Vector2.zero;
for( int i=0; i curSection = sections[i];
//Calculate u for texture uv and color interpolation:
float u = 0.0f;
//if (i != 0)
// u = Mathf.Clamp01( (Time.time - curSection.time) / m_time );
u = (1.0f/(float)(sections.Count-1)) * i ;
//Generate vertices:
float scale = (1.0f/(float)(sections.Count-1)) * i * 0.5f * m_trailScale;
Vector3 dirToS = curSection.pointS - curSection.pointE;
float l = dirToS.magnitude;
dirToS.Normalize();
curSection.pointS += ( l * scale * -dirToS );
curSection.pointE += ( l * scale * dirToS );
trailMesh.vertices[i * 2 + 0] = localSpaceTransform.MultiplyPoint( curSection.pointS );
trailMesh.vertices[i * 2 + 1] = localSpaceTransform.MultiplyPoint( curSection.pointE );
uv1.x = u; uv1.y = 0;
uv2.x = u; uv2.y = 1;
trailMesh.uv[i * 2 + 0] = uv1;
trailMesh.uv[i * 2 + 1] = uv2;
//fade colors out over time:
Color interpolatedColor = Color.Lerp(m_startColor, m_endColor, u);
trailMesh.colors[i * 2 + 0] = interpolatedColor;
trailMesh.colors[i * 2 + 1] = interpolatedColor;
}
//Generate triangles indices:
trailMesh.triangles = new int[(sections.Count-1)*2*3];
for( int i=0; i trailMesh.triangles[i * 6 + 0] = i * 2;
trailMesh.triangles[i * 6 + 1] = i * 2 + 1;
trailMesh.triangles[i * 6 + 2] = i * 2 + 2;
trailMesh.triangles[i * 6 + 3] = i * 2 + 2;
trailMesh.triangles[i * 6 + 4] = i * 2 + 1;
trailMesh.triangles[i * 6 + 5] = i * 2 + 3;
}
return trailMesh;
}
private List InterpolateTrailMesh () {
if( m_MaxSegments <= 0 )
m_MaxSegments = 1;
int total = (m_sections.Count-1)*m_MaxSegments;
while( m_sectionsT.Count > total )
m_sectionsT.RemoveAt(0);
for( int i=0; i TrailSection p0, p1, p2, p3;
if( i == 0 )
p0 = m_sections[i];
else
p0 = m_sections[i-1];
p1 = m_sections[i];
p2 = m_sections[i+1];
if( i+2 >= m_sections.Count-1 )
p3 = m_sections[i+1];
else
p3 = m_sections[i+2];
for( int delta=0; delta float t = (float)delta/(float)m_MaxSegments;
int index = (i*m_MaxSegments) + delta;
if( index < m_sectionsT.Count )
m_sectionsT[index] = TrailSection.Catmull_Rom( p0, p1, p2, p3, t );
else
m_sectionsT.Add( TrailSection.Catmull_Rom( p0, p1, p2, p3, t ) );
}
}
return m_sectionsT;
}
}
沒有留言:
張貼留言