Unity實(shí)現(xiàn)噴漆效果
本文實(shí)例為大家分享了Unity實(shí)現(xiàn)噴漆效果展示的具體代碼,供大家參考,具體內(nèi)容如下
噴漆功能
**應(yīng)用場(chǎng)景:**如墻上的標(biāo)語(yǔ)貼花,汽車上的噴漆等。
選擇方案:
1、當(dāng)然實(shí)現(xiàn)方法各式各異,最最最簡(jiǎn)單,也是最“不堪入目”的方法是直接給一個(gè)面片,然后獲取噴漆位置,加上一個(gè)要噴漆表面法線方向的偏移,作為最終面片放置位置,當(dāng)然,不要忘了設(shè)置面片的方向。這種方法雖然說(shuō)簡(jiǎn)單,但是效果并不理想,會(huì)出經(jīng)常現(xiàn)與其他物體穿插的情況,如果游戲中曲面太多,那么這個(gè)方案基本沒(méi)法看。
2、對(duì)于個(gè)別特殊的需求來(lái)講,比如說(shuō)人物身上的紋身,完全可以用一個(gè)shader里實(shí)現(xiàn),此方法僅限于一個(gè)貼花對(duì)應(yīng)一個(gè)物體,如果是一對(duì)多的情況,請(qǐng)看后邊這兩種。
3、有一種簡(jiǎn)易的方法是用Projector,這種方法實(shí)現(xiàn)較為簡(jiǎn)單,不多說(shuō)。
4、接下來(lái)說(shuō)一種動(dòng)態(tài)生成網(wǎng)格方案,也較為常用,接下來(lái)就詳細(xì)說(shuō)說(shuō)這種方案。
實(shí)現(xiàn)思路:
噴漆的網(wǎng)格是根據(jù)場(chǎng)景中所噴位置的物體的網(wǎng)格動(dòng)態(tài)生成的,噴漆的時(shí)候,獲取規(guī)定范圍內(nèi)的物體,再用一個(gè)立方體(也可以用球體)去截取這些物體的Mesh,從而構(gòu)造新的網(wǎng)格,將噴漆渲染在這個(gè)Mesh就OK了。
代碼實(shí)現(xiàn):
首先,我們需要一個(gè)獲取規(guī)定范圍內(nèi)MeshRenderer的函數(shù):
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去做裁剪,裁剪函數(shù):
public void BuildDecal(GameObject affectedObject, bool isLast)
{
Mesh affectedMesh = affectedObject.GetComponent<MeshFilter>().sharedMesh;
if (affectedMesh == null) return;
//這里預(yù)存了已獲取物體的vertices和triangles,減少了不必要的GC
Vector3[] vertices = GetVertexList(affectedObject);
int[] triangles = GetTriangleList(affectedObject);
//目標(biāo)頂點(diǎn)轉(zhuǎn)換到當(dāng)前物體的模型空間
Matrix4x4 matrix = this.transform.worldToLocalMatrix*affectedObject.transform.localToWorldMatrix;
//將主要計(jì)算移入異步
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構(gòu)建了新的三角形(這里注意頂點(diǎn)的空間變換),然后分別用立方體的每一個(gè)面去做裁剪,轉(zhuǎn)換成數(shù)學(xué)算法,其實(shí)是判面與面的關(guān)系,具體實(shí)現(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]); //不在裁剪面正面,說(shuō)明有相交
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,到這里已經(jīng)為新的Mesh準(zhǔn)備好了所有的數(shù)據(jù),接下來(lái)將計(jì)算好的數(shù)據(jù)移步到主線程做渲染:
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;
}
});
}
這樣,一個(gè)噴漆功能就做好了,有幾點(diǎn)需要注意是的是:
1.GC的控制
示例:Vector3[] vertices = mesh.vertices;
注意這里不是簡(jiǎn)單的內(nèi)存引用,而是會(huì)申請(qǐng)新的內(nèi)存,所以這樣的臨時(shí)變量會(huì)造成GC,當(dāng)物體的頂點(diǎn)上十幾K,甚至幾十K的時(shí)候,這樣的GC是吃不消的!為了盡量避免這樣的情況,可以做一次預(yù)存處理,對(duì)沒(méi)有檢測(cè)過(guò)物體的頂點(diǎn)和三角形數(shù)據(jù)進(jìn)行保存,下次用的時(shí)候直接取,從而取代mesh.vertices;
2.計(jì)算量的問(wèn)題
還是出于性能的考慮,當(dāng)與之裁剪的Mesh頂點(diǎn)數(shù)太多,在主線程for循環(huán)幾十K次,不出意外PC端也會(huì)卡頓,所以異步是一個(gè)較好的選擇。復(fù)雜的裁剪計(jì)算交給其他線程,計(jì)算好主線程直接拿數(shù)據(jù)做渲染;
3.效果問(wèn)題
由于新生成的噴漆Mesh是由原有物體的mesh裁剪所得的,而這兩個(gè)Mesh位置是重疊在一起的,兩個(gè)完全重疊的面,如果其他因變量也相同的情況下,讓計(jì)算機(jī)渲染,計(jì)算機(jī)也不知道該先渲染哪個(gè),這樣就出現(xiàn)z-fighting的問(wèn)題。所以加一個(gè)Push()方法,將新Mesh的頂點(diǎn)沿當(dāng)前頂點(diǎn)的法線方向擠出一點(diǎn)距離,這樣就實(shí)現(xiàn)了一個(gè)噴漆功能。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Unity?UGUI的StandaloneInputModule標(biāo)準(zhǔn)輸入模塊組件使用示例
這篇文章主要為大家介紹了Unity?UGUI的StandaloneInputModule標(biāo)準(zhǔn)輸入模塊組件使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
C#中閉包的實(shí)現(xiàn)和注意事項(xiàng)詳解
閉包并不是某一個(gè)語(yǔ)言中特有的概念,在主流的編程語(yǔ)言中都有這個(gè)特性,閉包可以讓一個(gè)內(nèi)部方法可以訪問(wèn)它所在外部方法中的變量,并可以對(duì)變量的值進(jìn)行修改,即使在外部方法的生命周期已經(jīng)結(jié)束后,本文給大家介紹了C#中閉包的實(shí)現(xiàn)和注意事項(xiàng),需要的朋友可以參考下2025-01-01
C#集成DeepSeek模型實(shí)現(xiàn)AI私有化的流程步驟(本地部署與API調(diào)用教程)
本文主要介紹了C#集成DeepSeek模型實(shí)現(xiàn)AI私有化的方法,包括搭建基礎(chǔ)環(huán)境,如安裝Ollama和下載DeepSeek?R1模型,客戶端?ChatBox?AI?接入?DeepSeek?的步驟,以及?C#?調(diào)用?DeepSeek?API?的示例代碼,并總結(jié)了其在實(shí)際項(xiàng)目中的應(yīng)用價(jià)值,需要的朋友可以參考下2025-03-03
Unity中的RegisterPlugins實(shí)用案例深入解析
這篇文章主要為大家介紹了Unity中的RegisterPlugins實(shí)用案例深入解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
C# 獲取進(jìn)程退出代碼的實(shí)現(xiàn)示例
這篇文章主要介紹了C# 獲取進(jìn)程退出代碼的實(shí)現(xiàn)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02
C#實(shí)現(xiàn)漂亮的數(shù)字時(shí)鐘效果
這篇文章主要介紹了C#實(shí)現(xiàn)漂亮的數(shù)字時(shí)鐘效果,涉及時(shí)間函數(shù)的應(yīng)用及繪圖的方法,需要的朋友可以參考下2014-10-10
在C#中調(diào)用Python代碼的兩種實(shí)現(xiàn)方式
這篇文章主要介紹了在C#中調(diào)用Python代碼的兩種實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
C# 大數(shù)據(jù)導(dǎo)出word的假死報(bào)錯(cuò)的處理方法
C# 大數(shù)據(jù)導(dǎo)出word的假死報(bào)錯(cuò)的處理方法,需要的朋友可以參考一下2013-03-03
c#模擬js escape方法的簡(jiǎn)單實(shí)例
這篇文章主要介紹了c#模擬js escape方法的簡(jiǎn)單實(shí)例,有需要的朋友可以參考一下2013-11-11

