在Unity中实现基于粒子的水模拟
曲面細分進行水模擬(一:物理模擬)
文章目錄
- 曲面細分進行水模擬(一:物理模擬)
- 前言
- 一、曲線模擬的原理介紹
- 二、代碼計算終點
- 1.代碼原理介紹
- 2.第一條射線計算
- 3.第二條射線計算
- 4. 分配數據準備
- 5. 傳遞數據
- 6. 總代碼
- 總結
前言
之前花費了不少時間編寫了一套通過GPU進行粒子系統的模擬,效率很不錯,因此覺得只用來制作霧效太浪費了,于是打算再多實現點什么東西。正好這篇文章的液體成色的原理給了我啟發,于是打算添加一點簡單的水槍效果模擬,沒有很真實的物理效果,只是使用射線獲取落點然后數據進行貝塞爾曲線擬合。并非是真正的液體模擬,只是制作一個簡單的水槍效果,但是好處是效率高,能夠在游戲中用的上。(至少在我的項目是用的上的,畢竟只是學校的小項目)
效果請看視頻
Unity粒子系統液體模擬
由于這次的模擬效果涉及的內容過多,因此我打算分為幾篇文章來寫,這是第一篇,使用代碼進行一些預運算,準備數據傳遞給材質進行模擬。
一、曲線模擬的原理介紹
由于Unity并不存在曲線檢測,我們不能真正的進行曲線的碰撞檢測(印象中射線檢測算法確實只有直線的檢測,畢竟是包圍盒切割,曲線的話包圍盒都不好確定了),但是我們可以首先通過公式算出終點,然后從起點到終點射一條射線,碰撞到的點就是曲線的終點,這樣確實不是真實效果,但是對于簡單的模擬還是足夠的。
看圖理解:
光看圖可能會覺得有遮擋和沒遮擋差距很大,但由于只是曲線的終點有一定的差距,只要射線距離不要太大,看起來也不會有很大的問題。
當然,一條射線只能確定上方會不會碰撞到物體,水是會下落的,因此我們要確定下面有沒有物體。完整流程如下:
當然,實際效果可比我手畫的好多了,因為是一個貝塞爾曲線,至少曲線的效果還是有的,只是這是射線檢測,不能判斷到曲線過程中會射中的點,因此可能會在曲線中依舊會有穿模現象,不過只要射線大小不要太大,穿模是不會經常出現的。
二、代碼計算終點
1.代碼原理介紹
根據原理我們發現至少要有2條射線,但是實際上由于水槍的出發點不可能正好在地面上,因此需要知道地面的高度,因此在最壞的情況下我們需要發出三條射線,如果沒有遮擋,這就是正常情況,因此為了這個物理模擬,確實有一點奢侈。
在第一條射線中,目標是確定是否有上方遮擋物,因此可以直接根據計算出來的終點往上射,看看是否有碰撞到的點。不過第一條射線可以舍去,比如當初始方向朝下時,不可能向上飛,因此可以直接舍去第一條射線,直接從第二條射線開始計算,此時起始點就是自身的坐標。
第二條射線就是進行下降的計算,需要注意的時由于我是默認地面是水平的,沒有明顯的起伏,高度一直都是和獲取到的地面高度一致,如果不一致的話由于射線是一條直線,下落曲線就會被拉的很長,曲線效果可能會有一定的影響,同時時間直接按照我的寫法也會有一定的出入,但是時間方面只要在經過一次計算就可以解決,主要問題還是曲線。
第三條射線是為第二條射線的地面位置做準備,為了能夠確定第二條射線的落點,需要預先獲取地面位置,因此需要一條射線直接向下射,獲取到地面的Y軸,根據這個進行落點計算。
2.第一條射線計算
代碼如下(示例):
//用模型的前方作為我們的水槍起始方向,rayDis是射線強度, //upDir 計算出來的就是液體的速度 Vector3 upDir = transform.forward * rayDis; Vector3 upPos = transform.position; Vector3 veTemp = Vector3.zero; float upTime = 0;//向上時執行上拋,否則直接向下確定位置 if (transform.forward.y >= 0) {//本質上下面幾行代碼就是一個自由落體,獲取到最大高度upTime = upDir.y / 9.8f;upDir.y = 0;//確定高度位置upPos.y += 0.5f * 9.8f * upTime * upTime;//確定最后終點upPos += upDir * upTime;//確定距離差veTemp = upPos - transform.position;//第一條線射中目標,檢查是否上方有東西阻擋if (Physics.Raycast(transform.position, veTemp, out raycastHit, veTemp.magnitude, layer)){//第一條射線射中數據傳遞的方法,后面介紹OneRayHit(raycastHit, raycastHit.distance, upTime);Debug.DrawLine(transform.position, raycastHit.point, Color.red);return;} }3.第二條射線計算
第二條射線需要預先準備地面高度,因此首先發射一條射線到地面。
//默認底部高度 float buttonY = transform.position.y - 1;if (Physics.Raycast(transform.position, Vector3.down, out raycastHit, layer)) {buttonY = raycastHit.point.y; }確定了高度后就可以檢查第二個落點了。
float s = upPos.y - buttonY;//就是簡單的解方程,指導s和v計算t的方程 //這里計算只用到了Y值,根據的是Y值差確定時間 float downTime = Mathf.Sqrt(2 * (0.5f * 9.8f * Mathf.Pow(upDir.y / 9.8f, 2) + s) / 9.8f) - Mathf.Abs(upDir.y) / 9.8f; upDir.y = 0; Vector3 downPos = downTime * upDir + transform.position; downPos.y = buttonY;//第二條射線默認無限距離,往盡頭射,因為終點很可能不在同一高度 if (Physics.Raycast(upPos, downPos - upPos, out raycastHit, layer)) {//第二條射線碰撞到物體的情況,要有好的物理模擬效果,//需要再進行一次時間計算,因為還有開方,計算量大,我舍去了TwoRayHit(raycastHit, upPos, veTemp.sqrMagnitude, transform.forward, downTime + upTime);Debug.DrawLine(upPos, raycastHit.point, Color.black);return; }//第二條也沒有中,就在空中結束 //賦值默認碰撞點 raycastHit.point = downPos; raycastHit.normal = Vector3.up; TwoRayHit(raycastHit, upPos, veTemp.sqrMagnitude, transform.forward, downTime + upTime);Debug.DrawLine(upPos, raycastHit.point, Color.white);有了碰撞點后就需要將數據傳遞給材質進行著色了,為了有足夠好的物理模擬效果,我們需要保證值之間能夠獨立,不受其他值影響,因此直接設置材質的變量是一定不行的。
要設置獨立存在的數據最好的位置是什么,自然是模型的頂點數據啊,uv、normal、tangent、color都能作為我們的數據傳遞位置,數據之間也不會相互影響。
首先我們需要確定要傳遞什么數據,首先,起始點和終點是一定需要的,為了有液體射出去的效果,我們需要存儲時間,為了能夠擬合出貝塞爾曲線,需要存儲兩個射線的中間位置,為了保證粒子系統效果的生成,需要有固定不變的定值,因此模型空間不能變。所以數據分配結果如下:
-
首先頂點數據依舊用來存儲隨機數,這個數據是固定不變的, 第一條射線的起點存儲在uv0和uv1(x)中,即begin=(uv0.xy, uv1.x)
-
第一條射線的貝塞爾曲線 中點 存儲在uv1(y)和uv2中,其中center=(uv2.xy, uv1.y)
-
第一條射線的終點[也是第二條射線的起點]在uv3和uv4(x)中,即end=(uv3.xy, uv4.x)
-
第二條射線的貝塞爾曲線中點存儲在uv4(y)和uv5中,其中center=(uv5.xy, uv4.y)
-
第二條射線的終點存儲在tangent中,其中end=(tangent.xyz) 射線的最終點的法線存儲在normal中,就是normal = normal
-
這個點的結束時間存儲在tangent.w中,這個也是刷新的根據時間,確定該粒子是否可用
-
color存儲了這批點的射中類型,x為1時就是第一條射線射中,y為1就是第二條射線射中
-
為了物理模擬,移動時間設在uv6的x中,保證這個模液體移動時間是可變的
4. 分配數據準備
要保證數據能夠正常分配,因此需要代碼生成頂點,由于粒子效果的模擬時會涉及曲面細分,如果一個面的數據差距很大時,很可能導致有一些粒子值差距很大,導致一閃一閃的效果,因此建議使用一次賦值一個三角面。
因此頂點生成算法很簡單,根據設置的頂點數量生成頂點,這些頂點看起來都是一個三角面。
代碼如下
/// <summary> /// 用來初始化這個生成的頂點,為了畫出較好的貝塞爾曲線, /// 且保證每一邊都是貝塞爾曲線,打算將三個點的數據傳入其中 /// 首先頂點數據依舊用來存儲隨機數,這個數據是固定不變的, /// 第一條射線的起點存儲在uv0和uv1(x)中,即begin=(uv0.xy, uv1.x) /// 第一條射線的貝塞爾曲線 中點 存儲在uv1(y)和uv2中,其中center=(uv2.xy, uv1.y) /// 第一條射線的終點[也是第二條射線的起點]在uv3和uv4(x)中,即end=(uv3.xy, uv4.x) /// 第二條射線的貝塞爾曲線 中點 存儲在uv4(y)和uv5中,其中center=(uv5.xy, uv4.y) /// 第二條射線的終點存儲在tangent中,其中end=(tangent.xyz) /// 射線的最終點的法線存儲在normal中,就是normal = normal /// 這個點的結束時間存儲在tangent.w中,這個也是刷新的根據時間 /// color存儲了這批點的射中類型,x為1時就是第一條射線射中,y為1就是第二條射線射中 /// 為了物理模擬,移動時間設在uv6的x中 /// </summary> private void AddMesh() {//表示沒有開始循環circulatePos = 0;particleSize -= particleSize % 3;poss = new Vector3[particleSize];tris = new int[particleSize];tangents = new Vector4[particleSize];normals = new Vector3[particleSize];uv0 = new Vector2[particleSize]; //Texcoord0uv1 = new Vector2[particleSize]; //Texcoord1uv2 = new Vector2[particleSize]; //Texcoord2uv3 = new Vector2[particleSize]; //Texcoord3uv4 = new Vector2[particleSize]; //Texcoord4uv5 = new Vector2[particleSize]; //Texcoord5uv6 = new Vector2[particleSize]; //Texcoord6colors = new Color[particleSize];//三個三個的加for (int i = 0; i < particleSize; i += 3){poss[i] = new Vector3(0, 0, 0);poss[i + 1] = new Vector3(0, 0, 1);poss[i + 2] = new Vector3(1, 0, 0);tris[i] = i;tris[i + 1] = i + 1;tris[i + 2] = i + 2;//設置結束時間為負數,讓Shader知道這個屬性沒有在使用中,//因為只有當前時間在終止時間和終止時間減存活時間之間才會開始運行tangents[i] = new Vector4(0, 0, 0, -100);tangents[i + 1] = new Vector4(0, 0, 0, -100);tangents[i + 2] = new Vector4(0, 0, 0, -100);normals[i] = Vector3.zero;normals[i + 1] = Vector3.zero;normals[i + 2] = Vector3.zero;uv0[i] = Vector2.zero;uv0[i + 1] = Vector2.zero;uv0[i + 2] = Vector2.zero;uv1[i + 2] = Vector2.zero;uv1[i + 2] = Vector2.zero;uv1[i + 2] = Vector2.zero;uv2[i] = Vector2.zero;uv2[i + 1] = Vector2.zero;uv2[i + 2] = Vector2.zero;uv3[i] = Vector2.zero;uv3[i + 1] = Vector2.zero;uv3[i + 2] = Vector2.zero;uv4[i] = Vector2.zero;uv4[i + 1] = Vector2.zero;uv4[i + 2] = Vector2.zero;uv5[i] = Vector2.zero;uv5[i + 1] = Vector2.zero;uv5[i + 2] = Vector2.zero;uv6[i] = Vector2.zero;uv6[i + 1] = Vector2.zero;uv6[i + 2] = Vector2.zero;colors[i] = Color.black;colors[i + 1] = Color.black;colors[i + 2] = Color.black;}mesh = new Mesh();mesh.vertices = poss;mesh.triangles = tris;mesh.tangents = tangents;mesh.uv = uv0;mesh.uv2 = uv1;mesh.uv3 = uv2;mesh.uv4 = uv3;mesh.uv5 = uv4;mesh.uv6 = uv5;mesh.uv7 = uv6;mesh.normals = normals;mesh.colors = colors;meshFilter.mesh = mesh;}5. 傳遞數據
首先是第一個射線射中的數據傳遞方式
/// <summary> /// 第一條射線射中目標 /// </summary> /// <param name="raycastHit">射中點數據</param> /// <param name="dis">理論上的最大距離,也就是本來這條線的長度</param> /// <param name="sqrTrue">中間射中了東西,所以確定實際長度</param> private void OneRayHit(RaycastHit raycastHit, float dis, float moveTime) {SetOneRayPoint(raycastHit, dis, moveTime, circulatePos);SetOneRayPoint(raycastHit, dis, moveTime, circulatePos + 1);SetOneRayPoint(raycastHit, dis, moveTime, circulatePos + 2);meshFilter.sharedMesh.SetTangents(tangents);meshFilter.sharedMesh.SetColors(colors);meshFilter.sharedMesh.SetUVs(0, uv0);meshFilter.sharedMesh.SetUVs(1, uv1);meshFilter.sharedMesh.SetUVs(2, uv2);meshFilter.sharedMesh.SetUVs(3, uv3);meshFilter.sharedMesh.SetUVs(4, uv4);meshFilter.sharedMesh.SetUVs(6, uv6);meshFilter.sharedMesh.SetNormals(normals);circulatePos += 3; }private void SetOneRayPoint(RaycastHit raycastHit, float dis, float moveTime, int index) {//表示這個粒子正在使用中tangents[index].w = Time.time + moveTime + outTime + offsetTime;//第一條射線射中colors[index] = new Color(1, 0, 0, 1);//設置起始位置SetPos(ref uv0[index], transform.position);uv1[index].x = transform.position.z;//設置第一條貝塞爾曲線中點Vector3 dir = transform.forward * dis * 0.5f + transform.position;SetPos(ref uv2[index], dir);uv1[index].y = dir.z;//第一條線的終點SetPos(ref uv3[index], raycastHit.point);uv4[index].x = raycastHit.point.z;//射中的法線normals[index] = raycastHit.normal;//設置粒子移動時間uv6[index].x = moveTime; }第二個點射中,或者壓根就沒射中
/// <summary> /// 第二條射線射中的情況 /// </summary> /// <param name="raycastHit">射線射中點信息</param> /// <param name="upPos">第一條射線的終點</param> /// <param name="firstSqrMax">第一條射線長度的平方</param> /// <param name="fowardDir">第二條射線開始的方向,設為參數是為了考慮看向下方的情況</param> private void TwoRayHit(RaycastHit raycastHit, Vector3 upPos, float dis, Vector3 fowardDir, float moveTime) {SetTwoRayPoint(raycastHit, upPos, dis, fowardDir, moveTime, circulatePos);SetTwoRayPoint(raycastHit, upPos, dis, fowardDir, moveTime, circulatePos + 1);SetTwoRayPoint(raycastHit, upPos, dis, fowardDir, moveTime, circulatePos + 2);circulatePos += 3;mesh.SetColors(colors);mesh.SetUVs(0, uv0);mesh.SetUVs(1, uv1);mesh.SetUVs(2, uv2);mesh.SetUVs(3, uv3);mesh.SetUVs(4, uv4);mesh.SetUVs(5, uv5);mesh.SetUVs(6, uv6);mesh.SetTangents(tangents);mesh.SetNormals(normals); }private void SetTwoRayPoint(RaycastHit raycastHit, Vector3 upPos, float dis, Vector3 fowardDir, float moveTime, int index) {//表示這個粒子正在使用中tangents[index].w = Time.time + moveTime + outTime + offsetTime;colors[index] = new Color(0f, 1f, 0f, 1f);//設置起始位置SetPos(ref uv0[index], transform.position);uv1[index].x = transform.position.z;//設置第一條射線中間位置Vector3 dir = transform.forward * dis * 0.5f + transform.position;SetPos(ref uv2[index], dir);uv1[index].y = dir.z;//設置第一條線的終點SetPos(ref uv3[index], upPos);uv4[index].x = upPos.z;//設置第二條線的中間位置,注意,為了方便,這個點的賦值方式有點特別fowardDir *= (raycastHit.point - upPos).magnitude * 0.5f;fowardDir += upPos;SetPos(ref uv5[index], fowardDir);uv4[index].y = fowardDir.z;//設置第二條線終點SetPos(ref tangents[index], raycastHit.point);//設置射中點的法線normals[index] = raycastHit.normal;//設置粒子移動時間uv6[index].x = moveTime;}6. 總代碼
using UnityEngine;namespace Common.ParticleSystem {/// <summary>/// 水模擬粒子系統控制器,需要時時刷新數據,生成頂點/// </summary>public class ParticleWater : MonoBehaviour{private MeshFilter meshFilter;public Material setMat;/// <summary> /// 循環到的位置 /// </summary>public int circulatePos;/// <summary> /// 粒子輸出花費時間 /// </summary>public float outTime = 0.3f;/// <summary> /// 粒子到達后開始偏移的損耗時間 /// </summary>public float offsetTime = 2f;/// <summary> /// 粒子數量,用來一開始創建 /// </summary>public int particleSize = 300;public float rayDis = 10;public LayerMask layer;#region CurveDate//移動大小曲線public bool isOpenMoveSizeCurve = false;public AnimationCurve moveSizeCurve = AnimationCurve.Linear(0,0,1,1);//偏移大小曲線public bool isOpenOffsetSizeCurve = false;public AnimationCurve offsetSizeCurve = AnimationCurve.Linear(0, 0, 1, 1);//透明曲線,因為透明都在片原著色器使用,因此設置一個就夠了public bool isOpenAlphaCurve = false;public AnimationCurve offsetAlphaCurve = AnimationCurve.Linear(0, 1, 1, 0);//移動透明曲線public AnimationCurve moveAlphaCurve = AnimationCurve.Linear(0, 0, 1, 1);#endregion#region MeshDate //Mesh數據設置位置,因為MeshFilter中的mesh設置沒有效果,只能將Mesh//提出來然后每次設置為后賦值了private Mesh mesh;private Vector3[] poss;private int[] tris;private Vector4[] tangents;private Vector3[] normals;private Vector2[] uv0;private Vector2[] uv1;private Vector2[] uv2;private Vector2[] uv3;private Vector2[] uv4;private Vector2[] uv5;private Vector2[] uv6;private Color[] colors;#endregionprivate void Start(){meshFilter = GetComponent<MeshFilter>();if(meshFilter == null)meshFilter = gameObject.AddComponent<MeshFilter>();else{meshFilter.sharedMesh.Clear();meshFilter.sharedMesh = null;}AddMesh();SetMatValue();}private void OnValidate(){SetMatValue();}private void SetMatValue(){if (setMat == null) return;setMat.SetFloat("_OutTime", outTime);setMat.SetFloat("_OffsetTime", offsetTime);Vector4[] vector4;//設置移動大小vector4 = new Vector4[moveSizeCurve.length];for (int i = 0; i < moveSizeCurve.length; i++){vector4[i] = new Vector4(moveSizeCurve.keys[i].time, moveSizeCurve.keys[i].value,moveSizeCurve.keys[i].inTangent, moveSizeCurve.keys[i].outTangent);}if (isOpenMoveSizeCurve) setMat.EnableKeyword("_MOVE_SIZE");else setMat.DisableKeyword("_MOVE_SIZE");setMat.SetInt("_MoveSizePointCount", moveSizeCurve.length);setMat.SetVectorArray("_MoveSizePointArray", vector4);//設置透明if (isOpenAlphaCurve) setMat.EnableKeyword("_ALPHA");else setMat.DisableKeyword("_ALPHA");//移動透明vector4 = new Vector4[moveAlphaCurve.length];for (int i = 0; i < moveAlphaCurve.length; i++){vector4[i] = new Vector4(moveAlphaCurve.keys[i].time, moveAlphaCurve.keys[i].value,moveAlphaCurve.keys[i].inTangent, moveAlphaCurve.keys[i].outTangent);}setMat.SetInt("_MoveAlphaPointCount", moveAlphaCurve.length);setMat.SetVectorArray("_MoveAlphaPointArray", vector4);//偏移透明vector4 = new Vector4[offsetAlphaCurve.length];for (int i = 0; i < offsetAlphaCurve.length; i++){vector4[i] = new Vector4(offsetAlphaCurve.keys[i].time, offsetAlphaCurve.keys[i].value,offsetAlphaCurve.keys[i].inTangent, offsetAlphaCurve.keys[i].outTangent);}setMat.SetInt("_OffsetAlphaPointCount", offsetAlphaCurve.length);setMat.SetVectorArray("_OffsetAlphaPointArray", vector4);//設置大小vector4 = new Vector4[offsetSizeCurve.length];for (int i = 0; i < offsetSizeCurve.length; i++){vector4[i] = new Vector4(offsetSizeCurve.keys[i].time, offsetSizeCurve.keys[i].value,offsetSizeCurve.keys[i].inTangent, offsetSizeCurve.keys[i].outTangent);}if (isOpenOffsetSizeCurve)setMat.EnableKeyword("_OFFSET_SIZE");else setMat.DisableKeyword("_OFFSET_SIZE");setMat.SetInt("_OffsetSizePointCount", offsetSizeCurve.length);setMat.SetVectorArray("_OffsetSizePointArray", vector4);//設置位置setMat.SetVector("_BeginPos", transform.position);}/// <summary>/// 用來存儲是否射線/// </summary>bool desireRay;public void RayWater(){desireRay = true;}private void FixedUpdate(){//if (!desireRay) return;//else desireRay = false;circulatePos %= particleSize;//檢查粒子是否可以使用,因為所有粒子是順序執行的,不能用就直接退出int i = 0;for (; i<particleSize; i+=3){//可以使用,進行操作if (meshFilter.sharedMesh.tangents[(circulatePos + i)%particleSize].w < Time.time){circulatePos = circulatePos + i;break;}}if (i >= particleSize) return;RaycastHit raycastHit;Vector3 upDir = transform.forward * rayDis;Vector3 upPos = transform.position;Vector3 veTemp = Vector3.zero;float upTime = 0;//向上時執行上拋,否則直接向下確定位置if (transform.forward.y >= 0){upTime = upDir.y / 9.8f;//確定第一條射線數據upDir.y = 0;upPos.y += 0.5f * 9.8f * upTime * upTime;upPos += upDir * upTime;veTemp = upPos - transform.position;//第一條線射中目標,檢查是否上方有東西阻擋if (Physics.Raycast(transform.position, veTemp, out raycastHit, veTemp.magnitude, layer)){OneRayHit(raycastHit, raycastHit.distance, upTime);Debug.DrawLine(transform.position, raycastHit.point, Color.red);return;}}//默認底部高度float buttonY = transform.position.y - 1;if (Physics.Raycast(transform.position, Vector3.down, out raycastHit, layer)){buttonY = raycastHit.point.y;}float s = upPos.y - buttonY;float downTime = Mathf.Sqrt(2 * (0.5f * 9.8f * Mathf.Pow(upDir.y / 9.8f, 2) + s) / 9.8f) - Mathf.Abs(upDir.y) / 9.8f;upDir.y = 0;Vector3 downPos = downTime * upDir + transform.position;downPos.y = buttonY;//第二條射線默認無限距離,往盡頭射if (Physics.Raycast(upPos, downPos - upPos, out raycastHit, layer)){TwoRayHit(raycastHit, upPos, veTemp.magnitude, transform.forward, downTime + upTime);Debug.DrawLine(upPos, raycastHit.point, Color.black);return;}//第二條也沒有中,就在空中結束吧raycastHit.point = downPos;raycastHit.normal = Vector3.up;TwoRayHit(raycastHit, upPos, veTemp.magnitude, transform.forward, downTime + upTime);Debug.DrawLine(upPos, raycastHit.point, Color.white);}/// <summary>/// 第一條射線射中目標/// </summary>/// <param name="raycastHit">射中點數據</param>/// <param name="dis">理論上的最大距離,也就是本來這條線的長度</param>/// <param name="sqrTrue">中間射中了東西,所以確定實際長度</param>private void OneRayHit(RaycastHit raycastHit, float dis, float moveTime){SetOneRayPoint(raycastHit, dis, moveTime, circulatePos);SetOneRayPoint(raycastHit, dis, moveTime, circulatePos + 1);SetOneRayPoint(raycastHit, dis, moveTime, circulatePos + 2);meshFilter.sharedMesh.SetTangents(tangents);meshFilter.sharedMesh.SetColors(colors);meshFilter.sharedMesh.SetUVs(0, uv0);meshFilter.sharedMesh.SetUVs(1, uv1);meshFilter.sharedMesh.SetUVs(2, uv2);meshFilter.sharedMesh.SetUVs(3, uv3);meshFilter.sharedMesh.SetUVs(4, uv4);meshFilter.sharedMesh.SetUVs(6, uv6);meshFilter.sharedMesh.SetNormals(normals);circulatePos += 3;}private void SetOneRayPoint(RaycastHit raycastHit, float dis, float moveTime, int index){//表示這個粒子正在使用中tangents[index].w = Time.time + moveTime + outTime + offsetTime;//第一條射線射中colors[index] = new Color(1, 0, 0, 1);//設置起始位置SetPos(ref uv0[index], transform.position);uv1[index].x = transform.position.z;//設置第一條貝塞爾曲線中點Vector3 dir = transform.forward * dis * 0.5f + transform.position;SetPos(ref uv2[index], dir);uv1[index].y = dir.z;//第一條線的終點SetPos(ref uv3[index], raycastHit.point);uv4[index].x = raycastHit.point.z;//射中的法線normals[index] = raycastHit.normal;//設置粒子移動時間uv6[index].x = moveTime;}/// <summary>/// 第二條射線射中的情況/// </summary>/// <param name="raycastHit">射線射中點信息</param>/// <param name="upPos">第一條射線的終點</param>/// <param name="firstSqrMax">第一條射線長度的平方</param>/// <param name="fowardDir">第二條射線開始的方向,設為參數是為了考慮看向下方的情況</param>private void TwoRayHit(RaycastHit raycastHit, Vector3 upPos, float dis, Vector3 fowardDir, float moveTime){SetTwoRayPoint(raycastHit, upPos, dis, fowardDir, moveTime, circulatePos);SetTwoRayPoint(raycastHit, upPos, dis, fowardDir, moveTime, circulatePos + 1);SetTwoRayPoint(raycastHit, upPos, dis, fowardDir, moveTime, circulatePos + 2);circulatePos += 3;mesh.SetColors(colors);mesh.SetUVs(0, uv0);mesh.SetUVs(1, uv1);mesh.SetUVs(2, uv2);mesh.SetUVs(3, uv3);mesh.SetUVs(4, uv4);mesh.SetUVs(5, uv5);mesh.SetUVs(6, uv6);mesh.SetTangents(tangents);mesh.SetNormals(normals);}private void SetTwoRayPoint(RaycastHit raycastHit, Vector3 upPos, float dis, Vector3 fowardDir, float moveTime, int index){//表示這個粒子正在使用中tangents[index].w = Time.time + moveTime + outTime + offsetTime;colors[index] = new Color(0f, 1f, 0f, 1f);//設置起始位置SetPos(ref uv0[index], transform.position);uv1[index].x = transform.position.z;//設置第一條射線中間位置Vector3 dir = transform.forward * dis * 0.5f + transform.position;SetPos(ref uv2[index], dir);uv1[index].y = dir.z;//設置第一條線的終點SetPos(ref uv3[index], upPos);uv4[index].x = upPos.z;//設置第二條線的中間位置,注意,為了方便,這個點的賦值方式有點特別fowardDir *= (raycastHit.point - upPos).magnitude * 0.5f;fowardDir += upPos;SetPos(ref uv5[index], fowardDir);uv4[index].y = fowardDir.z;//設置第二條線終點SetPos(ref tangents[index], raycastHit.point);//設置射中點的法線normals[index] = raycastHit.normal;//設置粒子移動時間uv6[index].x = moveTime;}/// <summary> /// 將第二個參數的xyz值賦予第一個參數的xyz中,簡化上面的函數 /// </summary>private void SetPos(ref Vector4 vector, Vector3 vector3){vector.x = vector3.x;vector.y = vector3.y;vector.z = vector3.z;}/// <summary> /// 將第二個參數的xyz值賦予第一個參數的xyz中,簡化上面的函數 /// </summary>private void SetPos(ref Vector2 vector, Vector3 vector3){vector.x = vector3.x;vector.y = vector3.y;}/// <summary>/// 用來初始化這個生成的頂點,為了畫出較好的貝塞爾曲線,/// 且保證每一邊都是貝塞爾曲線,打算將三個點的數據傳入其中/// 首先頂點數據依舊用來存儲隨機數,這個數據是固定不變的,/// 第一條射線的起點存儲在uv0和uv1(x)中,即begin=(uv0.xy, uv1.x)/// 第一條射線的貝塞爾曲線 中點 存儲在uv1(y)和uv2中,其中center=(uv2.xy, uv1.y)/// 第一條射線的終點[也是第二條射線的起點]在uv3和uv4(x)中,即end=(uv3.xy, uv4.x)/// 第二條射線的貝塞爾曲線 中點 存儲在uv4(y)和uv5中,其中center=(uv5.xy, uv4.y)/// 第二條射線的終點存儲在tangent中,其中end=(tangent.xyz)/// 射線的最終點的法線存儲在normal中,就是normal = normal/// 這個點的結束時間存儲在tangent.w中,這個也是刷新的根據時間/// color存儲了這批點的射中類型,x為1時就是第一條射線射中,y為1就是第二條射線射中/// 為了物理模擬,移動時間設在uv6的x中/// </summary>private void AddMesh(){//表示沒有開始循環circulatePos = 0;particleSize -= particleSize % 3;poss = new Vector3[particleSize];tris = new int[particleSize];tangents = new Vector4[particleSize];normals = new Vector3[particleSize];uv0 = new Vector2[particleSize]; //Texcoord0uv1 = new Vector2[particleSize]; //Texcoord1uv2 = new Vector2[particleSize]; //Texcoord2uv3 = new Vector2[particleSize]; //Texcoord3uv4 = new Vector2[particleSize]; //Texcoord4uv5 = new Vector2[particleSize]; //Texcoord5uv6 = new Vector2[particleSize]; //Texcoord6colors = new Color[particleSize];//三個三個的加for (int i = 0; i < particleSize; i += 3){poss[i] = new Vector3(0, 0, 0);poss[i + 1] = new Vector3(0, 0, 1);poss[i + 2] = new Vector3(1, 0, 0);tris[i] = i;tris[i + 1] = i + 1;tris[i + 2] = i + 2;//設置結束時間為負數,讓Shader知道這個屬性沒有在使用中,//因為只有當前時間在終止時間和終止時間減存活時間之間才會開始運行tangents[i] = new Vector4(0, 0, 0, -100);tangents[i + 1] = new Vector4(0, 0, 0, -100);tangents[i + 2] = new Vector4(0, 0, 0, -100);normals[i] = Vector3.zero;normals[i + 1] = Vector3.zero;normals[i + 2] = Vector3.zero;uv0[i] = Vector2.zero;uv0[i + 1] = Vector2.zero;uv0[i + 2] = Vector2.zero;uv1[i + 2] = Vector2.zero;uv1[i + 2] = Vector2.zero;uv1[i + 2] = Vector2.zero;uv2[i] = Vector2.zero;uv2[i + 1] = Vector2.zero;uv2[i + 2] = Vector2.zero;uv3[i] = Vector2.zero;uv3[i + 1] = Vector2.zero;uv3[i + 2] = Vector2.zero;uv4[i] = Vector2.zero;uv4[i + 1] = Vector2.zero;uv4[i + 2] = Vector2.zero;uv5[i] = Vector2.zero;uv5[i + 1] = Vector2.zero;uv5[i + 2] = Vector2.zero;uv6[i] = Vector2.zero;uv6[i + 1] = Vector2.zero;uv6[i + 2] = Vector2.zero;colors[i] = Color.black;colors[i + 1] = Color.black;colors[i + 2] = Color.black;}mesh = new Mesh();mesh.vertices = poss;mesh.triangles = tris;mesh.tangents = tangents;mesh.uv = uv0;mesh.uv2 = uv1;mesh.uv3 = uv2;mesh.uv4 = uv3;mesh.uv5 = uv4;mesh.uv6 = uv5;mesh.uv7 = uv6;mesh.normals = normals;mesh.colors = colors;meshFilter.mesh = mesh;}} }傳遞完數據后就到Shader的模擬階段了,估計要過一段時間才能寫后面,最近到大作業的時間了,雖然大學課程很水,但是績點太低還是很難看的,所以更新時間估計要延遲一點了。而且項目壓力好大,找隊友真的是個難題,找到些水貨真的心累,整個項目代碼我寫、框架我搭、著色都我寫,明明都是程序員,寫個代碼有這么難嘛,幸虧我有水平,繃得住,還有閑暇時間寫文章。
總結
以上就是這篇文章的全部內容,這篇文章只是起始部分,在之后的著色部分才是大頭,畢竟要一整套流程搞下來還是內容挺多的,這里只是最簡單的數據傳遞部分而已。
實際上這里的數據傳遞還有可以優化的部分,因為直接使用Set來傳遞頂點數據的話是替換,從效率來看的話是很慢的,畢竟我們實際上只是改變部分值,卻替換了整一個數組對象,因為C#和unity底層有區別,本質上模型存儲的數據應該不是代碼中控制的數組數據,因此在替換時會進行數據檢測、替換等階段,效率很低。
在看unity官方文檔時看到流操作傳遞數據,既然是流,那一定很快啊,但是官方文檔的操作寫的好隨便,網上也沒有找到資料是最令我震驚的,如果有人知道請在下面評論區解答一下,畢竟能優化是最好的。
總結
以上是生活随笔為你收集整理的在Unity中实现基于粒子的水模拟的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rust三人防炸家_《文明重启》三种建房
- 下一篇: 那个被“爱”刺伤的30岁女孩,在这款游戏