主要參考鏈接:Mesh Deformation, a Unity C# Tutorial(本文為其翻譯版)
unity項(xiàng)目下載鏈接:https://download.csdn.net/download/weixin_43042683/87679832
- 在物體上投射射線(xiàn)并畫(huà)出調(diào)試線(xiàn)。
- 將力轉(zhuǎn)換為頂點(diǎn)的速度。
- 用彈簧和阻尼保持形狀。
- 補(bǔ)償物體變形。
本教程是一個(gè)關(guān)于網(wǎng)格變形的介紹。我們將把一個(gè)網(wǎng)格變成有彈性的質(zhì)量,并對(duì)其進(jìn)行戳穿。它適用于Unity 5.0.1及以上版本。
1. 場(chǎng)景設(shè)置
我們從一個(gè)場(chǎng)景開(kāi)始,這個(gè)場(chǎng)景的中心有一個(gè)單一的立方體球體對(duì)象。
為了得到一個(gè)平滑的變形,球體應(yīng)該包含相當(dāng)數(shù)量的頂點(diǎn)。把球體的網(wǎng)格大小設(shè)置為20,半徑為1。

2. 網(wǎng)格變形器
創(chuàng)建一個(gè)新的MeshDeformer腳本來(lái)處理變形問(wèn)題。就像立方體球體組件一樣,它需要一個(gè)網(wǎng)格過(guò)濾器來(lái)工作。
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
public class MeshDeformer : MonoBehaviour {
}
將新的組件添加到立方體球體中。

?請(qǐng)注意,我們只需要一個(gè)網(wǎng)格過(guò)濾器。我們并不關(guān)心它是如何得到一個(gè)網(wǎng)格的?,F(xiàn)在我們使用的是程序化的立方體球體,但它可以是任何網(wǎng)格。
2.1 準(zhǔn)備工作
要進(jìn)行變形,我們需要訪問(wèn)網(wǎng)格。一旦我們有了網(wǎng)格,我們就可以提取原始頂點(diǎn)的位置。在變形過(guò)程中,我們還需要跟蹤位移的頂點(diǎn)。
Mesh deformingMesh;
Vector3[] originalVertices, displacedVertices;
在Start方法中獲取網(wǎng)格及其頂點(diǎn),并將原始頂點(diǎn)復(fù)制到位移頂點(diǎn)上。
void Start () {
deformingMesh = GetComponent<MeshFilter>().mesh;
originalVertices = deformingMesh.vertices;
displacedVertices = new Vector3[originalVertices.Length];
for (int i = 0; i < originalVertices.Length; i++) {
displacedVertices[i] = originalVertices[i];
}
}
我們使用Start,所以程序性網(wǎng)格可以在Awake中生成,Awake總是被首先調(diào)用的。這種方法依賴(lài)于其他組件在Awake中處理他們的事情,所以它不能保證一定被首先調(diào)用。你也可以調(diào)整腳本的執(zhí)行順序來(lái)強(qiáng)制執(zhí)行誰(shuí)先誰(shuí)后。
2.2 頂點(diǎn)速度
頂點(diǎn)會(huì)隨著網(wǎng)格的變形而移動(dòng)。所以我們也必須存儲(chǔ)每個(gè)頂點(diǎn)的速度。
Vector3[] vertexVelocities;
void Start () {
…
vertexVelocities = new Vector3[originalVertices.Length];
}
現(xiàn)在我們有了支持網(wǎng)格變形的基本要素。
3. 網(wǎng)格變形器的輸入
我們需要一些方法來(lái)控制網(wǎng)格的變形方式。我們將使用用戶(hù)的輸入,所以它是互動(dòng)的。每當(dāng)用戶(hù)接觸到我們的物體時(shí),我們將在該點(diǎn)施加一個(gè)力。
MeshDeformer組件負(fù)責(zé)實(shí)際的變形,但它并不關(guān)心輸入方法。我們應(yīng)該創(chuàng)建一個(gè)單獨(dú)的組件來(lái)處理用戶(hù)輸入的問(wèn)題。給它一個(gè)可配置的輸入力。
using UnityEngine;
public class MeshDeformerInput : MonoBehaviour {
public float force = 10f;
}
把這個(gè)組件附加到攝像機(jī)上是最合理的,因?yàn)樗砹擞脩?hù)的視角。我們不應(yīng)該把它附加到變形網(wǎng)格對(duì)象上,因?yàn)閳?chǎng)景中可能有多個(gè)變形網(wǎng)格。

3.1? 檢測(cè)輸入
只要默認(rèn)的鼠標(biāo)按鈕被按住,我們就會(huì)處理用戶(hù)的輸入。因此,只要有點(diǎn)擊或拖動(dòng),就認(rèn)為用戶(hù)一直按著方形球。
void Update () {
if (Input.GetMouseButton(0)) {
HandleInput();
}
}
現(xiàn)在我們必須弄清楚用戶(hù)的指向。我們通過(guò)從攝像機(jī)向場(chǎng)景中投射一條射線(xiàn)來(lái)完成這個(gè)任務(wù)。我們將抓取場(chǎng)景中的主攝像機(jī),并使用它來(lái)將光標(biāo)位置轉(zhuǎn)換為射線(xiàn)。
void HandleInput () {
Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);
}
我們使用物理引擎來(lái)投射射線(xiàn)并存儲(chǔ)它所擊中的信息。如果射線(xiàn)撞到了什么東西,我們可以從被撞到的物體中獲取MeshDeformer組件。
Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(inputRay, out hit)) {
MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();
}
Physics.Raycast是如何工作的?
Physics.Raycast是一個(gè)靜態(tài)的方法,用于將射線(xiàn)投射到3D場(chǎng)景中。它有各種不同的變體。最簡(jiǎn)單的版本有一個(gè)射線(xiàn)參數(shù),并返回它是否擊中了什么。
我們所使用的版本有一個(gè)額外的參數(shù)。它是一個(gè)類(lèi)型為RaycastHit的輸出參數(shù)。這是一個(gè)結(jié)構(gòu),包含關(guān)于被擊中的東西和接觸點(diǎn)的信息。
3.2?施加力
?如果我們撞到了什么東西,而那個(gè)東西有一個(gè)MeshDeformer組件,那么我們就可以對(duì)那個(gè)東西進(jìn)行變形!所以請(qǐng)繼續(xù)在接觸點(diǎn)添加一個(gè)變形力。
MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();
if (deformer) {
Vector3 point = hit.point;
deformer.AddDeformingForce(point, force);
}
當(dāng)然這要假設(shè)我們的MeshDeformer組件有一個(gè)AddDeformingForce方法。所以要添加這個(gè)方法。不過(guò),我們先不要做任何變形。首先,從主攝像機(jī)到該點(diǎn)畫(huà)一條調(diào)試線(xiàn),以使射線(xiàn)可視化。
public void AddDeformingForce (Vector3 point, float force) {
Debug.DrawLine(Camera.main.transform.position, point);
}

我在哪里可以看到調(diào)試線(xiàn)?
它顯示在場(chǎng)景視圖中,所以在游戲模式下,你必須保持游戲視圖和場(chǎng)景視圖都是可見(jiàn)的。?
3.3 力量偏移
我們?cè)噲D喚起的體驗(yàn)是,網(wǎng)格被用戶(hù)捅破了,凹陷了。這就要求靠近接觸點(diǎn)的頂點(diǎn)被推到表面。然而,這個(gè)變形力并沒(méi)有一個(gè)固有的方向。它將在所有方向上平等地施加。這將導(dǎo)致平面上的頂點(diǎn)被推開(kāi),而不是被推入。
我們可以通過(guò)把力點(diǎn)從曲面上拉開(kāi)來(lái)增加一個(gè)方向。一個(gè)輕微的偏移已經(jīng)保證了頂點(diǎn)總是被推入曲面。接觸點(diǎn)的法線(xiàn)可以作為偏移方向。?

public float forceOffset = 0.1f;
void HandleInput () {
Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(inputRay, out hit)) {
MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();
if (deformer) {
Vector3 point = hit.point;
point += hit.normal * forceOffset;
deformer.AddDeformingForce(point, force);
}
}
}

4.?基本變形
現(xiàn)在是時(shí)候做一些真正的位移了。MeshDeformer.AddDeformingForce必須循環(huán)瀏覽所有當(dāng)前位移的頂點(diǎn),并對(duì)每個(gè)頂點(diǎn)單獨(dú)施加變形力。
public void AddDeformingForce (Vector3 point, float force) {
for (int i = 0; i < displacedVertices.Length; i++) {
AddForceToVertex(i, point, force);
}
}
void AddForceToVertex (int i, Vector3 point, float force) {
}
4.1 將力轉(zhuǎn)換為速度
網(wǎng)格之所以變形,是因?yàn)槊總€(gè)頂點(diǎn)都受到了力的作用。當(dāng)頂點(diǎn)被推動(dòng)時(shí),它們會(huì)獲得一個(gè)速度。隨著時(shí)間的推移,這些頂點(diǎn)都會(huì)改變它們的位置。如果所有頂點(diǎn)都經(jīng)歷完全相同的力,整個(gè)物體就會(huì)移動(dòng)而不改變其形狀。
想想看,一個(gè)大爆炸。如果你在地面上,你就會(huì)死。如果你在附近,你會(huì)被擊倒。如果你在遠(yuǎn)處,就沒(méi)有問(wèn)題。力量隨著距離的增加而減弱。結(jié)合方向上的差異,這種衰減是造成我們物體變形的原因。
所以我們需要知道每個(gè)頂點(diǎn)的變形力的方向和距離。兩者都可以從一個(gè)從力點(diǎn)指向頂點(diǎn)位置的矢量中得到。
void AddForceToVertex (int i, Vector3 point, float force) {
Vector3 pointToVertex = displacedVertices[i] - point;
}
現(xiàn)在可以用反平方定律找到衰減的力。只要用原力除以距離的平方就可以了,?。實(shí)際上,我除以1加上距離的平方,。這就保證了當(dāng)距離為零時(shí),力是全開(kāi)的。否則,當(dāng)距離為1時(shí),力就會(huì)處于全盛狀態(tài),而當(dāng)你越接近該點(diǎn)時(shí),它就會(huì)向無(wú)窮遠(yuǎn)處射去。
Vector3 pointToVertex = displacedVertices[i] - point;
float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
現(xiàn)在我們有了我們的力,我們可以把它轉(zhuǎn)換為速度Δ。實(shí)際上,這個(gè)力首先通過(guò)以下方式轉(zhuǎn)換為一個(gè)加速度a=F/m? 那么速度的變化可以通過(guò)以下方式找到Δv=aΔt,為了簡(jiǎn)單起見(jiàn),我們將忽略質(zhì)量,就像每個(gè)頂點(diǎn)都是一個(gè)一樣。因此,我們最終會(huì)得到Δv=FΔt。
Vector3 pointToVertex = displacedVertices[i] - point;
float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
float velocity = attenuatedForce * Time.deltaTime;
在這一點(diǎn)上,我們有一個(gè)速度Δ,但還沒(méi)有一個(gè)方向。我們通過(guò)對(duì)開(kāi)始時(shí)的矢量進(jìn)行歸一化來(lái)找到它。然后我們可以把結(jié)果加到頂點(diǎn)速度上。
Vector3 pointToVertex = displacedVertices[i] - point;
float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
float velocity = attenuatedForce * Time.deltaTime;
vertexVelocities[i] += pointToVertex.normalized * velocity;
4.2 移動(dòng)頂點(diǎn)
現(xiàn)在,頂點(diǎn)有了速度,我們可以移動(dòng)它們。添加一個(gè)更新方法來(lái)處理每個(gè)頂點(diǎn)。之后,將位移頂點(diǎn)分配給網(wǎng)格,使其實(shí)際發(fā)生變化。因?yàn)榫W(wǎng)格的形狀不再是恒定的,我們也必須重新計(jì)算它的法線(xiàn)。
void Update () {
for (int i = 0; i < displacedVertices.Length; i++) {
UpdateVertex(i);
}
deformingMesh.vertices = displacedVertices;
deformingMesh.RecalculateNormals();
}
更新一個(gè)頂點(diǎn)是一個(gè)調(diào)整其位置的問(wèn)題,通過(guò)Δp=vΔt。
void UpdateVertex (int i) {
Vector3 velocity = vertexVelocities[i];
displacedVertices[i] += velocity * Time.deltaTime;
}
頂點(diǎn)是否一直在更新?
是的,每次更新時(shí),所有頂點(diǎn)都會(huì)被移位,分配給網(wǎng)格,法線(xiàn)也會(huì)重新計(jì)算。即使在沒(méi)有施加任何力的情況下。如果用戶(hù)沒(méi)有對(duì)網(wǎng)格進(jìn)行變形,那么可以認(rèn)為是在浪費(fèi)時(shí)間。所以只有在需要不斷地使網(wǎng)格變形的時(shí)候才使用這個(gè)功能。

?5. 保持體形
現(xiàn)在,只要我們對(duì)它們施加一些力,頂點(diǎn)就開(kāi)始移動(dòng)。但它們不會(huì)停止。它們繼續(xù)移動(dòng),物體的原始形狀就會(huì)消失。現(xiàn)在讓我們使物體反彈到它的原始形狀。
真實(shí)的物體是固體,在變形時(shí)被壓縮和拉伸。它們能抵抗這種變形。一旦不受干擾,它們也能恢復(fù)到原來(lái)的形狀。
我們沒(méi)有一個(gè)真正的體積,只是一個(gè)描述表面的頂點(diǎn)集合。我們不能用它來(lái)進(jìn)行現(xiàn)實(shí)的物理模擬。但這并不是一個(gè)問(wèn)題。我們真正需要的是看起來(lái)可信的東西。
5.1?彈簧
我們同時(shí)跟蹤每個(gè)頂點(diǎn)的原始位置和變形位置。想象一下,我們?cè)诿總€(gè)頂點(diǎn)的兩個(gè)位置之間附加彈簧。每當(dāng)變形頂點(diǎn)遠(yuǎn)離原始頂點(diǎn)時(shí),彈簧會(huì)把它拉回來(lái)。變形頂點(diǎn)離得越遠(yuǎn),彈簧的拉力就越大.

我們可以直接使用位移矢量作為速度調(diào)整,乘以一個(gè)可配置的彈簧力。這很簡(jiǎn)單,看起來(lái)也很不錯(cuò)。我們?cè)诿看胃马旤c(diǎn)的時(shí)候都會(huì)這樣做。
public float springForce = 20f;
void UpdateVertex (int i) {
Vector3 velocity = vertexVelocities[i];
Vector3 displacement = displacedVertices[i] - originalVertices[i];
velocity -= displacement * springForce * Time.deltaTime;
vertexVelocities[i] = velocity;
displacedVertices[i] += velocity * Time.deltaTime;
}

5.2 衰減
我們的頂點(diǎn)現(xiàn)在可以抵抗變形并跳回原來(lái)的位置。但它們會(huì)過(guò)沖,一直無(wú)休止地跳動(dòng)。發(fā)生這種情況是因?yàn)閺椈稍陧旤c(diǎn)自我修正時(shí)不斷拉動(dòng),增加了它的速度。只有在它向后移動(dòng)太遠(yuǎn)之后才會(huì)減慢速度。
我們可以通過(guò)不斷減緩頂點(diǎn)的速度來(lái)防止這種永恒的振蕩。這種阻尼效應(yīng)可以替代阻力、慣性等等。它是一個(gè)簡(jiǎn)單的因素,隨著時(shí)間的推移,速度會(huì)降低,。
阻尼越高,物體的彈性就越小,反應(yīng)也就越遲鈍。
public float damping = 5f;
void UpdateVertex (int i) {
Vector3 velocity = vertexVelocities[i];
Vector3 displacement = displacedVertices[i] - originalVertices[i];
velocity -= displacement * springForce * Time.deltaTime;
velocity *= 1f - damping * Time.deltaTime;
vertexVelocities[i] = velocity;
displacedVertices[i] += velocity * Time.deltaTime;
}

6. 處理轉(zhuǎn)換
我們的網(wǎng)格變形現(xiàn)在是完全有效的,除了當(dāng)我們變換物體時(shí)。我們所有的計(jì)算都是在局部空間進(jìn)行的。繼續(xù)前進(jìn),移動(dòng)或旋轉(zhuǎn)我們的球體。你會(huì)看到變形力將被錯(cuò)誤地應(yīng)用。
我們必須對(duì)物體的變換進(jìn)行補(bǔ)償。我們通過(guò)將變形力的位置從世界空間轉(zhuǎn)換到本地空間來(lái)做到這一點(diǎn)。
public void AddDeformingForce (Vector3 point, float force) {
point = transform.InverseTransformPoint(point);
for (int i = 0; i < displacedVertices.Length; i++) {
AddForceToVertex(i, point, force);
}
}

6.1 調(diào)整比例
現(xiàn)在,力被施加在正確的地方,但其他的東西仍然是錯(cuò)誤的。將球體均勻地向上或向下縮放。你會(huì)注意到,變形的比例是相同的。這是不正確的。小物體和大物體應(yīng)該受到相同的物理學(xué)影響。
?我們必須對(duì)我們的物體的比例進(jìn)行補(bǔ)償。首先,我們需要知道它的統(tǒng)一尺度。我們可以通過(guò)檢查變換的一個(gè)局部比例軸來(lái)找到它。每次更新都要這樣做,這樣我們就可以在某種程度上處理動(dòng)態(tài)改變比例的對(duì)象。
float uniformScale = 1f;
void Update () {
uniformScale = transform.localScale.x;
…
}
非均勻比例怎么辦?
你可以使用一個(gè)三維矢量,而不是一個(gè)單一的刻度值。然后分別調(diào)整每個(gè)維度。但實(shí)際上,你并不想處理非均勻比例的問(wèn)題。
現(xiàn)在固定AddForceToVertex,將pointToVertex向量按統(tǒng)一比例縮放。這可以確保我們使用正確的距離。
void AddForceToVertex (int i, Vector3 point, float force) {
Vector3 pointToVertex = displacedVertices[i] - point;
pointToVertex *= uniformScale;
float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
float velocity = attenuatedForce * Time.deltaTime;
vertexVelocities[i] += pointToVertex.normalized * velocity;
}
?在UpdateVertex中對(duì)位移做同樣的處理?,F(xiàn)在我們的速度是正確的。
void UpdateVertex (int i) {
Vector3 velocity = vertexVelocities[i];
Vector3 displacement = displacedVertices[i] - originalVertices[i];
displacement *= uniformScale;
velocity -= displacement * springForce * Time.deltaTime;
velocity *= 1f - damping * Time.deltaTime;
vertexVelocities[i] = velocity;
displacedVertices[i] += velocity * Time.deltaTime;
}
然而,對(duì)于一個(gè)沒(méi)有被縮放的物體,我們的速度現(xiàn)在是正確的。由于我們的對(duì)象實(shí)際上是按比例的,我們也必須調(diào)整頂點(diǎn)運(yùn)動(dòng)。這一次我們必須用除法而不是用乘法。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-500235.html
displacedVertices[i] += velocity * (Time.deltaTime / uniformScale);

就這樣,你擁有了它。一個(gè)可以在任何位置、旋轉(zhuǎn)和統(tǒng)一比例下工作的變形網(wǎng)格。請(qǐng)記住,這是一個(gè)簡(jiǎn)單和相對(duì)便宜的視覺(jué)效果。它不是一個(gè)軟體物理模擬。物體的碰撞器并沒(méi)有改變,所以物理引擎并不知道物體的感知形狀。?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-500235.html
到了這里,關(guān)于Unity——網(wǎng)格變形(制作一個(gè)壓力球)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!