? ? ? ? 前一段被要求模擬一根繩索,因為種種原因,筆者最后決定通過數(shù)學函數(shù)和Mesh模擬出一根繩索,具體的思路是首先利用Verlet函數(shù)模擬繩索的剛性,之后利用Mesh渲染圓柱體將繩索模擬出來。
????????
? ? ? ? 首先,利用Verlet進行邏輯上的繩索創(chuàng)建,具體思路參考了這篇文章:
在Unity使用Verlet積分實現(xiàn)逼真的繩索 - 知乎
? ? ? ? 不過這篇文章的作者是利用LineRenderer模擬的2D繩索,簡單來講就是將繩子看成許多節(jié),每個節(jié)點都包含兩個端點,端點會產(chǎn)生一個力來約束繩索,有了這些基礎就可以模擬繩子的物理特性了。
/// <summary>
/// Verlet最小節(jié)點
/// </summary>
public class Particle
{
public Vector3 position;
public Vector3 oldPosition;
public bool locked;
}
/// <summary>
/// 抓鉤段長
/// </summary>
public class Stick
{
public Particle particleA;
public Particle particleB;
public float length;
public Stick(Particle a, Particle b)
{
particleA = a;
particleB = b;
length = (a.position - b.position).magnitude;
}
}
? ? ? ? 而因為需要建立在三維空間中,所以不采用LineRenderer,而是直接用一組Vector3來代表這些繩子的節(jié)點,在邏輯上將繩子模擬出來,以待后續(xù)的渲染。
public class Hook : MonoBehaviour
{
[Header("節(jié)點數(shù)")]
[Range(30, 100)]
public float points = 80;
[Header("半徑")]
public float lineRadius = 0.02f;
[Header("重力")]
[SerializeField] public float gravity = 1f;
[Header("作用力")]
[SerializeField] public int stiffness = 200;
[SerializeField] private bool startPointLock; //鎖定節(jié)點
[SerializeField] private bool endPointLock;
[SerializeField] private bool isfollow; //起始點隨父物體移動
private Vector3 _startPosition; //開始位置
private Vector3 _endPosition; //結束位置
public Material _material;
private List<Vector3> _vector3s = new List<Vector3>(); //抓鉤信息存儲列表
private List<Particle> _particles = new List<Particle>();
private List<Stick> _sticks = new List<Stick>();
private Dictionary<int, UnityEngine.Mesh> _meshMap = new Dictionary<int, UnityEngine.Mesh>();
private bool _isExist = false; //是否已存在抓鉤
private int _index = 0; //抓鉤節(jié)點計數(shù)
/// <summary>
/// 初始化
/// </summary>
public void Init(Vector3 startV3, Vector3 endV3)
{
_startPosition = startV3;
_endPosition = endV3;
Initialization();
SetParticlesLockSet();
_isExist = true;
}
private void FixedUpdate()
{
Simulation();
}
/// <summary>
/// 抓鉤節(jié)點設置
/// </summary>
private void Initialization()
{
_vector3s.Clear();
_particles.Clear();
_sticks.Clear();
_meshMap.Clear();
for (int i = 0; i <= points; i++)
{
float t = i / points;
_vector3s.Add(Vector3.Lerp(_startPosition, _endPosition, t));
}
for (int i = 0; i < _vector3s.Count; i++)
{
_particles.Add(new Particle() { position = _vector3s[i], oldPosition = _vector3s[i] });
}
for (int i = 0; i < _particles.Count - 1; i++)
{
_sticks.Add(new Stick(_particles[i], _particles[i + 1]));
}
}
/// <summary>
/// 抓鉤節(jié)點鎖定設置
/// </summary>
private void SetParticlesLockSet()
{
if (startPointLock)
{
_particles[0].locked = true;
}
if (endPointLock)
{
_particles[_particles.Count - 1].locked = true;
}
if (isfollow)
{
_particles[0].locked = true;
}
}
/// <summary>
/// 抓鉤特性賦值
/// </summary>
private void Simulation()
{
//遍歷
for (int i = 0; i < _particles.Count; i++)
{
Particle p = _particles[i];
if (p.locked == false)
{
Vector3 temp = p.position;
//Verlet積分
p.position = p.position + (p.position - p.oldPosition) + Time.fixedDeltaTime * Time.fixedDeltaTime * new Vector3(0, -gravity, 0);
p.oldPosition = temp;
}
}
//迭代次數(shù),控制剛性
for (int i = 0; i < stiffness; i++)
{
for (int j = 0; j < _sticks.Count; j++)
{
Stick stick = _sticks[j];
Vector3 delta = stick.particleB.position - stick.particleA.position;
float deltaLength = delta.magnitude;
float diff = (deltaLength - stick.length) / deltaLength;
if (stick.particleA.locked == false)
stick.particleA.position += 0.5f * diff * delta;
if (stick.particleB.locked == false)
stick.particleB.position -= 0.5f * diff * delta;
}
}
if (isfollow && _particles.Count > 0)
{
_particles[0].position = Player.instance.transform.position;
}
}
}
????????接下來便是考慮如何渲染3D的繩索了,最優(yōu)的方案如下,利用所得節(jié)點渲染不平行的柱體,這是以下這篇Unreal的繩索文檔思路,不過這個文章并沒有提供源碼,所有筆者準備改一種更簡單的方法。
https://docs.unrealengine.com/4.27/en-US/Basics/Components/CableComponent/
? ? ? ??將兩個節(jié)點之間渲染成不平行圓柱體實現(xiàn)起來比較麻煩,而渲染成平行的就簡單很多了,但隨之面臨的問題是不平行的圓柱體之間會有縫隙在繩索彎曲的時候,但利用積分的思想,假設繩子上的節(jié)點越多,那么繩子間的縫隙影響就會越小,當節(jié)點夠多時 (目前嘗試的,其實50-80個的時候基本就看不到了,前提時繩子不要太粗),就看不到縫隙了。
? ? ? ? 所以說,接下來只要對各個節(jié)點進行圓柱體繪制就行了:文章來源:http://www.zghlxwxcb.cn/news/detail-861582.html
private void LateUpdate()
{
if (_isExist)
{
StartCoroutine(HookRendering(true));
}
Rendering();
}
/// <summary>
/// 繪制抓鉤
/// </summary>
private void Rendering()
{
for (int i = 0; i < _index; i++)
{
DrawCylinder(_particles[i].position, _particles[i + 1].position);
}
}
/// <summary>
/// 繪制抓鉤攜程,true為延伸,false為收縮
/// </summary>
/// <param name="isExtend"></param>
/// <returns></returns>
IEnumerator HookRendering(bool isExtend)
{
endPointLock = isExtend;
_isExist = false;
if (isExtend)
{
while (_index < _particles.Count - 1)
{
_index++;
yield return new WaitForSeconds(0.01f);
}
//Player.instance.MoveControl(_endPosition, true);
//StartCoroutine(HookRendering(false));
}
else
{
while (_index > 0)
{
_index--;
yield return new WaitForSeconds(0.01f);
}
}
}
/// <summary>
/// Mesh繪制
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
private void DrawCylinder(Vector3 a, Vector3 b)
{
if (isNaN(a) || isNaN(b)) { return; }
float length = (a - b).magnitude;
if ((a - b).magnitude > 0.001f)
{
Graphics.DrawMesh(GetCylinderMesh(length),
Matrix4x4.TRS(a,
Quaternion.LookRotation(b - a),
new Vector3(transform.lossyScale.x, transform.lossyScale.x, 1)),
_material,
gameObject.layer,
null, 0, null, true);
}
}
/// <summary>
/// Mesh獲取
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
private UnityEngine.Mesh GetCylinderMesh(float length)
{
const float CYLINDER_MESH_RESOLUTION = 0.1f;
int lengthKey = Mathf.RoundToInt(length * 100 / CYLINDER_MESH_RESOLUTION);
UnityEngine.Mesh mesh;
if (_meshMap.TryGetValue(lengthKey, out mesh))
{
return mesh;
}
mesh = new UnityEngine.Mesh();
mesh.hideFlags = HideFlags.DontSave;
List<Vector3> verts = new List<Vector3>();
List<Color> colors = new List<Color>();
List<int> tris = new List<int>();
Vector3 p0 = Vector3.zero;
Vector3 p1 = Vector3.forward * length;
int _cylinderResolution = 12;
float _cylinderRadius = lineRadius;
for (int i = 0; i < _cylinderResolution; i++)
{
float angle = (Mathf.PI * 2.0f * i) / _cylinderResolution;
float dx = _cylinderRadius * Mathf.Cos(angle);
float dy = _cylinderRadius * Mathf.Sin(angle);
Vector3 spoke = new Vector3(dx, dy, 0);
verts.Add(p0 + spoke);
verts.Add(p1 + spoke);
colors.Add(Color.white);
colors.Add(Color.white);
int triStart = verts.Count;
int triCap = _cylinderResolution * 2;
tris.Add((triStart + 0) % triCap);
tris.Add((triStart + 2) % triCap);
tris.Add((triStart + 1) % triCap);
tris.Add((triStart + 2) % triCap);
tris.Add((triStart + 3) % triCap);
tris.Add((triStart + 1) % triCap);
}
mesh.SetVertices(verts);
mesh.SetIndices(tris.ToArray(), MeshTopology.Triangles, 0);
mesh.RecalculateBounds();
mesh.RecalculateNormals();
mesh.UploadMeshData(true);
_meshMap[lengthKey] = mesh;
return mesh;
}
private bool isNaN(Vector3 v)
{
return float.IsNaN(v.x) || float.IsNaN(v.y) || float.IsNaN(v.z);
}
最后,如果這篇文章幫助到你,麻煩點個贊吧。文章來源地址http://www.zghlxwxcb.cn/news/detail-861582.html
到了這里,關于【Unity 3D繩索】基于圖形渲染的3D繩索模擬的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!