Unity實現(xiàn)噴漆效果
本文實例為大家分享了Unity實現(xiàn)噴漆效果展示的具體代碼,供大家參考,具體內容如下
噴漆功能
**應用場景:**如墻上的標語貼花,汽車上的噴漆等。
選擇方案:
1、當然實現(xiàn)方法各式各異,最最最簡單,也是最“不堪入目”的方法是直接給一個面片,然后獲取噴漆位置,加上一個要噴漆表面法線方向的偏移,作為最終面片放置位置,當然,不要忘了設置面片的方向。這種方法雖然說簡單,但是效果并不理想,會出經?,F(xiàn)與其他物體穿插的情況,如果游戲中曲面太多,那么這個方案基本沒法看。
2、對于個別特殊的需求來講,比如說人物身上的紋身,完全可以用一個shader里實現(xiàn),此方法僅限于一個貼花對應一個物體,如果是一對多的情況,請看后邊這兩種。
3、有一種簡易的方法是用Projector,這種方法實現(xiàn)較為簡單,不多說。
4、接下來說一種動態(tài)生成網格方案,也較為常用,接下來就詳細說說這種方案。
實現(xiàn)思路:
噴漆的網格是根據場景中所噴位置的物體的網格動態(tài)生成的,噴漆的時候,獲取規(guī)定范圍內的物體,再用一個立方體(也可以用球體)去截取這些物體的Mesh,從而構造新的網格,將噴漆渲染在這個Mesh就OK了。
代碼實現(xiàn):
首先,我們需要一個獲取規(guī)定范圍內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構建了新的三角形(這里注意頂點的空間變換),然后分別用立方體的每一個面去做裁剪,轉換成數學算法,其實是判面與面的關系,具體實現(xiàn):
/// <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循環(huán)幾十K次,不出意外PC端也會卡頓,所以異步是一個較好的選擇。復雜的裁剪計算交給其他線程,計算好主線程直接拿數據做渲染;
3.效果問題
由于新生成的噴漆Mesh是由原有物體的mesh裁剪所得的,而這兩個Mesh位置是重疊在一起的,兩個完全重疊的面,如果其他因變量也相同的情況下,讓計算機渲染,計算機也不知道該先渲染哪個,這樣就出現(xiàn)z-fighting的問題。所以加一個Push()方法,將新Mesh的頂點沿當前頂點的法線方向擠出一點距離,這樣就實現(xiàn)了一個噴漆功能。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
C#使用Automation實現(xiàn)控制自動撥打接聽電話
這篇文章主要為大家詳細介紹了C#如何使用Automation實現(xiàn)控制自動撥打接聽電話,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-02-02C#中Dictionary與List的用法區(qū)別以及聯(lián)系詳解
List和Dictionary想必是我們平常用到最多的C#容器了,他們使用起來都很簡單,這篇文章主要給大家介紹了關于C#中Dictionary與List的用法區(qū)別以及聯(lián)系的相關資料,需要的朋友可以參考下2023-11-11