1. 程式人生 > 程式設計 >Unity實現噴漆效果

Unity實現噴漆效果

本文例項為大家分享了Unity實現噴漆效果展示的具體程式碼,供大家參考,具體內容如下

噴漆功能

**應用場景:**如牆上的標語貼花,汽車上的噴漆等。

選擇方案:

1、當然實現方法各式各異,最最最簡單,也是最“不堪入目”的方法是直接給一個面片,然後獲取噴漆位置,加上一個要噴漆表面法線方向的偏移,作為最終面片放置位置,當然,不要忘了設定面片的方向。這種方法雖然說簡單,但是效果並不理想,會出經常現與其他物體穿插的情況,如果遊戲中曲面太多,那麼這個方案基本沒法看。
2、對於個別特殊的需求來講,比如說人物身上的紋身,完全可以用一個shader裡實現,此方法僅限於一個貼花對應一個物體,如果是一對多的情況,請看後邊這兩種。

3、有一種簡易的方法是用Projector,這種方法實現較為簡單,不多說。
4、接下來說一種動態生成網格方案,也較為常用,接下來就詳細說說這種方案。

實現思路:

噴漆的網格是根據場景中所噴位置的物體的網格動態生成的,噴漆的時候,獲取規定範圍內的物體,再用一個立方體(也可以用球體)去擷取這些物體的Mesh,從而構造新的網格,將噴漆渲染在這個Mesh就OK了。

程式碼實現:

首先,我們需要一個獲取規定範圍內MeshRenderer的函式:

public GameObject[] GetAffectedObjects(Bounds bounds,LayerMask affectedLayers)
{
 MeshRenderer[] renderers = FindObjectsOfType<MeshRenderer>();
 List<GameObject> objects = new List<GameObject>();
 foreach (Renderer r in renderers)
 {
 if (!r.enabled) continue;
 if ((1 << r.gameObject.layer & affectedLayers.value) == 0) continue;
 if (r.GetComponent<Decal>() != null) continue;

 if (bounds.Intersects(r.bounds))
 {
  objects.Add(r.gameObject);
 }
 }
 return objects.ToArray();
}

然後拿到這些GameObject去做裁剪,裁剪函式:

public void BuildDecal(GameObject affectedObject,bool isLast)
{
 Mesh affectedMesh = affectedObject.GetComponent<MeshFilter>().sharedMesh;
 if (affectedMesh == null) return;

 //這裡預存了已獲取物體的vertices和triangles,減少了不必要的GC
 Vector3[] vertices = GetVertexList(affectedObject);
 int[] triangles = GetTriangleList(affectedObject);

 //目標頂點轉換到當前物體的模型空間
 Matrix4x4 matrix = this.transform.worldToLocalMatrix*affectedObject.transform.localToWorldMatrix;
 //將主要計算移入非同步
 Loom.RunAsync(() =>
 {
 for (int i = 0; i < triangles.Length; i += 3)
 {
  int i1 = triangles[i];
  int i2 = triangles[i + 1];
  int i3 = triangles[i + 2];

  Vector3 v1 = matrix.MultiplyPoint(vertices[i1]);
  Vector3 v2 = matrix.MultiplyPoint(vertices[i2]);
  Vector3 v3 = matrix.MultiplyPoint(vertices[i3]);

  Vector3 side1 = v2 - v1;
  Vector3 side2 = v3 - v1;
  Vector3 normal = Vector3.Cross(side1,side2).normalized;

  if (Vector3.Angle(-Vector3.forward,normal) >= maxAngle) continue;

  DecalPolygon poly = new DecalPolygon(v1,v2,v3);

  //用立方體裁剪
  poly = DecalPolygon.ClipPolygon(poly,right);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly,left);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly,top);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly,bottom);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly,front);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly,back);
  if (poly == null) continue;

  AddPolygon(poly,normal);
 }

 if (isLast)
 {
  RenderDecal();
 }
 });
}

DecalPolygon構建了新的三角形(這裡注意頂點的空間變換),然後分別用立方體的每一個面去做裁剪,轉換成數學演算法,其實是判面與面的關係,具體實現:

/// <summary>
/// 兩面相交裁剪
/// </summary>
public static DecalPolygon ClipPolygon(DecalPolygon polygon,Plane plane)
{
 //相交為True
 bool[] positive = new bool[9];
 int positiveCount = 0;

 for (int i = 0; i < polygon.vertices.Count; i++)
 {
 positive[i] = !plane.GetSide(polygon.vertices[i]); //不在裁剪面正面,說明有相交
 if (positive[i]) positiveCount++;
 }

 if (positiveCount == 0)
 return null; //全都在裁剪面正面面,不相交
 if (positiveCount == polygon.vertices.Count) return polygon; //全都在裁剪面反面,完全相交

 DecalPolygon tempPolygon = new DecalPolygon();

 for (int i = 0; i < polygon.vertices.Count; i++)
 {
 int next = i + 1;
 next %= polygon.vertices.Count;

 if (positive[i])
 {
  tempPolygon.vertices.Add(polygon.vertices[i]);
 }

 if (positive[i] != positive[next])
 {
  Vector3 v1 = polygon.vertices[next];
  Vector3 v2 = polygon.vertices[i];

  Vector3 v = LineCast(plane,v1,v2);
  tempPolygon.vertices.Add(v);
 }
 }

 return tempPolygon;
}

OK,到這裡已經為新的Mesh準備好了所有的資料,接下來將計算好的資料移步到主執行緒做渲染:

public void RenderDecal()
{
 //主執行緒渲染
  Loom.QueueOnMainThread(() =>
 {
 if (sprite == null || Renderer == null||filter==null)
    {
      return;
    }
    //生成uv資訊
    GenerateTexCoords(0,sprite);
    //距離偏移
 Push(pushDistance);

 Mesh mesh = CreateMesh();
 if (mesh != null) {
  mesh.name = "DecalMesh";
  filter.mesh = mesh;
  Renderer.material = material;
  Renderer.enabled = true;
 }
 });
}

這樣,一個噴漆功能就做好了,有幾點需要注意是的是:

1.GC的控制

示例:Vector3[] vertices = mesh.vertices;
注意這裡不是簡單的記憶體引用,而是會申請新的記憶體,所以這樣的臨時變數會造成GC,當物體的頂點上十幾K,甚至幾十K的時候,這樣的GC是吃不消的!為了儘量避免這樣的情況,可以做一次預存處理,對沒有檢測過物體的頂點和三角形資料進行儲存,下次用的時候直接取,從而取代mesh.vertices;

2.計算量的問題

還是出於效能的考慮,當與之裁剪的Mesh頂點數太多,在主執行緒for迴圈幾十K次,不出意外PC端也會卡頓,所以非同步是一個較好的選擇。複雜的裁剪計算交給其他執行緒,計算好主執行緒直接拿資料做渲染;

3.效果問題

由於新生成的噴漆Mesh是由原有物體的mesh裁剪所得的,而這兩個Mesh位置是重疊在一起的,兩個完全重疊的面,如果其他因變數也相同的情況下,讓計算機渲染,計算機也不知道該先渲染哪個,這樣就出現z-fighting的問題。所以加一個Push()方法,將新Mesh的頂點沿當前頂點的法線方向擠出一點距離,這樣就實現了一個噴漆功能。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。