原理:
遊戲用到的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; } }
沒有留言:
張貼留言