日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

在Unity中实现基于粒子的水模拟

發(fā)布時間:2024/1/8 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在Unity中实现基于粒子的水模拟 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

曲面細(xì)分進(jìn)行水模擬(一:物理模擬)


文章目錄

  • 曲面細(xì)分進(jìn)行水模擬(一:物理模擬)
  • 前言
  • 一、曲線模擬的原理介紹
  • 二、代碼計算終點(diǎn)
    • 1.代碼原理介紹
    • 2.第一條射線計算
    • 3.第二條射線計算
    • 4. 分配數(shù)據(jù)準(zhǔn)備
    • 5. 傳遞數(shù)據(jù)
    • 6. 總代碼
  • 總結(jié)


前言

之前花費(fèi)了不少時間編寫了一套通過GPU進(jìn)行粒子系統(tǒng)的模擬,效率很不錯,因此覺得只用來制作霧效太浪費(fèi)了,于是打算再多實(shí)現(xiàn)點(diǎn)什么東西。正好這篇文章的液體成色的原理給了我啟發(fā),于是打算添加一點(diǎn)簡單的水槍效果模擬,沒有很真實(shí)的物理效果,只是使用射線獲取落點(diǎn)然后數(shù)據(jù)進(jìn)行貝塞爾曲線擬合。并非是真正的液體模擬,只是制作一個簡單的水槍效果,但是好處是效率高,能夠在游戲中用的上。(至少在我的項目是用的上的,畢竟只是學(xué)校的小項目)

效果請看視頻

Unity粒子系統(tǒng)液體模擬

由于這次的模擬效果涉及的內(nèi)容過多,因此我打算分為幾篇文章來寫,這是第一篇,使用代碼進(jìn)行一些預(yù)運(yùn)算,準(zhǔn)備數(shù)據(jù)傳遞給材質(zhì)進(jìn)行模擬。


一、曲線模擬的原理介紹

由于Unity并不存在曲線檢測,我們不能真正的進(jìn)行曲線的碰撞檢測(印象中射線檢測算法確實(shí)只有直線的檢測,畢竟是包圍盒切割,曲線的話包圍盒都不好確定了),但是我們可以首先通過公式算出終點(diǎn),然后從起點(diǎn)到終點(diǎn)射一條射線,碰撞到的點(diǎn)就是曲線的終點(diǎn),這樣確實(shí)不是真實(shí)效果,但是對于簡單的模擬還是足夠的。
看圖理解:


光看圖可能會覺得有遮擋和沒遮擋差距很大,但由于只是曲線的終點(diǎn)有一定的差距,只要射線距離不要太大,看起來也不會有很大的問題。

當(dāng)然,一條射線只能確定上方會不會碰撞到物體,水是會下落的,因此我們要確定下面有沒有物體。完整流程如下:

當(dāng)然,實(shí)際效果可比我手畫的好多了,因為是一個貝塞爾曲線,至少曲線的效果還是有的,只是這是射線檢測,不能判斷到曲線過程中會射中的點(diǎn),因此可能會在曲線中依舊會有穿模現(xiàn)象,不過只要射線大小不要太大,穿模是不會經(jīng)常出現(xiàn)的。

二、代碼計算終點(diǎn)

1.代碼原理介紹

根據(jù)原理我們發(fā)現(xiàn)至少要有2條射線,但是實(shí)際上由于水槍的出發(fā)點(diǎn)不可能正好在地面上,因此需要知道地面的高度,因此在最壞的情況下我們需要發(fā)出三條射線,如果沒有遮擋,這就是正常情況,因此為了這個物理模擬,確實(shí)有一點(diǎn)奢侈。

在第一條射線中,目標(biāo)是確定是否有上方遮擋物,因此可以直接根據(jù)計算出來的終點(diǎn)往上射,看看是否有碰撞到的點(diǎn)。不過第一條射線可以舍去,比如當(dāng)初始方向朝下時,不可能向上飛,因此可以直接舍去第一條射線,直接從第二條射線開始計算,此時起始點(diǎn)就是自身的坐標(biāo)。

第二條射線就是進(jìn)行下降的計算,需要注意的時由于我是默認(rèn)地面是水平的,沒有明顯的起伏,高度一直都是和獲取到的地面高度一致,如果不一致的話由于射線是一條直線,下落曲線就會被拉的很長,曲線效果可能會有一定的影響,同時時間直接按照我的寫法也會有一定的出入,但是時間方面只要在經(jīng)過一次計算就可以解決,主要問題還是曲線。

第三條射線是為第二條射線的地面位置做準(zhǔn)備,為了能夠確定第二條射線的落點(diǎn),需要預(yù)先獲取地面位置,因此需要一條射線直接向下射,獲取到地面的Y軸,根據(jù)這個進(jìn)行落點(diǎn)計算。

2.第一條射線計算

代碼如下(示例):

//用模型的前方作為我們的水槍起始方向,rayDis是射線強(qiáng)度, //upDir 計算出來的就是液體的速度 Vector3 upDir = transform.forward * rayDis; Vector3 upPos = transform.position; Vector3 veTemp = Vector3.zero; float upTime = 0;//向上時執(zhí)行上拋,否則直接向下確定位置 if (transform.forward.y >= 0) {//本質(zhì)上下面幾行代碼就是一個自由落體,獲取到最大高度upTime = upDir.y / 9.8f;upDir.y = 0;//確定高度位置upPos.y += 0.5f * 9.8f * upTime * upTime;//確定最后終點(diǎn)upPos += upDir * upTime;//確定距離差veTemp = upPos - transform.position;//第一條線射中目標(biāo),檢查是否上方有東西阻擋if (Physics.Raycast(transform.position, veTemp, out raycastHit, veTemp.magnitude, layer)){//第一條射線射中數(shù)據(jù)傳遞的方法,后面介紹OneRayHit(raycastHit, raycastHit.distance, upTime);Debug.DrawLine(transform.position, raycastHit.point, Color.red);return;} }

3.第二條射線計算

第二條射線需要預(yù)先準(zhǔn)備地面高度,因此首先發(fā)射一條射線到地面。

//默認(rèn)底部高度 float buttonY = transform.position.y - 1;if (Physics.Raycast(transform.position, Vector3.down, out raycastHit, layer)) {buttonY = raycastHit.point.y; }

確定了高度后就可以檢查第二個落點(diǎn)了。

float s = upPos.y - buttonY;//就是簡單的解方程,指導(dǎo)s和v計算t的方程 //這里計算只用到了Y值,根據(jù)的是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;//第二條射線默認(rèn)無限距離,往盡頭射,因為終點(diǎn)很可能不在同一高度 if (Physics.Raycast(upPos, downPos - upPos, out raycastHit, layer)) {//第二條射線碰撞到物體的情況,要有好的物理模擬效果,//需要再進(jìn)行一次時間計算,因為還有開方,計算量大,我舍去了TwoRayHit(raycastHit, upPos, veTemp.sqrMagnitude, transform.forward, downTime + upTime);Debug.DrawLine(upPos, raycastHit.point, Color.black);return; }//第二條也沒有中,就在空中結(jié)束 //賦值默認(rèn)碰撞點(diǎn) raycastHit.point = downPos; raycastHit.normal = Vector3.up; TwoRayHit(raycastHit, upPos, veTemp.sqrMagnitude, transform.forward, downTime + upTime);Debug.DrawLine(upPos, raycastHit.point, Color.white);

有了碰撞點(diǎn)后就需要將數(shù)據(jù)傳遞給材質(zhì)進(jìn)行著色了,為了有足夠好的物理模擬效果,我們需要保證值之間能夠獨(dú)立,不受其他值影響,因此直接設(shè)置材質(zhì)的變量是一定不行的。
要設(shè)置獨(dú)立存在的數(shù)據(jù)最好的位置是什么,自然是模型的頂點(diǎn)數(shù)據(jù)啊,uv、normal、tangent、color都能作為我們的數(shù)據(jù)傳遞位置,數(shù)據(jù)之間也不會相互影響。

首先我們需要確定要傳遞什么數(shù)據(jù),首先,起始點(diǎn)和終點(diǎn)是一定需要的,為了有液體射出去的效果,我們需要存儲時間,為了能夠擬合出貝塞爾曲線,需要存儲兩個射線的中間位置,為了保證粒子系統(tǒng)效果的生成,需要有固定不變的定值,因此模型空間不能變。所以數(shù)據(jù)分配結(jié)果如下:

  • 首先頂點(diǎn)數(shù)據(jù)依舊用來存儲隨機(jī)數(shù),這個數(shù)據(jù)是固定不變的, 第一條射線的起點(diǎn)存儲在uv0和uv1(x)中,即begin=(uv0.xy, uv1.x)

  • 第一條射線的貝塞爾曲線 中點(diǎn) 存儲在uv1(y)和uv2中,其中center=(uv2.xy, uv1.y)

  • 第一條射線的終點(diǎn)[也是第二條射線的起點(diǎn)]在uv3和uv4(x)中,即end=(uv3.xy, uv4.x)

  • 第二條射線的貝塞爾曲線中點(diǎn)存儲在uv4(y)和uv5中,其中center=(uv5.xy, uv4.y)

  • 第二條射線的終點(diǎn)存儲在tangent中,其中end=(tangent.xyz) 射線的最終點(diǎn)的法線存儲在normal中,就是normal = normal

  • 這個點(diǎn)的結(jié)束時間存儲在tangent.w中,這個也是刷新的根據(jù)時間,確定該粒子是否可用

  • color存儲了這批點(diǎn)的射中類型,x為1時就是第一條射線射中,y為1就是第二條射線射中

  • 為了物理模擬,移動時間設(shè)在uv6的x中,保證這個模液體移動時間是可變的

4. 分配數(shù)據(jù)準(zhǔn)備

要保證數(shù)據(jù)能夠正常分配,因此需要代碼生成頂點(diǎn),由于粒子效果的模擬時會涉及曲面細(xì)分,如果一個面的數(shù)據(jù)差距很大時,很可能導(dǎo)致有一些粒子值差距很大,導(dǎo)致一閃一閃的效果,因此建議使用一次賦值一個三角面。

因此頂點(diǎn)生成算法很簡單,根據(jù)設(shè)置的頂點(diǎn)數(shù)量生成頂點(diǎn),這些頂點(diǎn)看起來都是一個三角面。

代碼如下

/// <summary> /// 用來初始化這個生成的頂點(diǎn),為了畫出較好的貝塞爾曲線, /// 且保證每一邊都是貝塞爾曲線,打算將三個點(diǎn)的數(shù)據(jù)傳入其中 /// 首先頂點(diǎn)數(shù)據(jù)依舊用來存儲隨機(jī)數(shù),這個數(shù)據(jù)是固定不變的, /// 第一條射線的起點(diǎn)存儲在uv0和uv1(x)中,即begin=(uv0.xy, uv1.x) /// 第一條射線的貝塞爾曲線 中點(diǎn) 存儲在uv1(y)和uv2中,其中center=(uv2.xy, uv1.y) /// 第一條射線的終點(diǎn)[也是第二條射線的起點(diǎn)]在uv3和uv4(x)中,即end=(uv3.xy, uv4.x) /// 第二條射線的貝塞爾曲線 中點(diǎn) 存儲在uv4(y)和uv5中,其中center=(uv5.xy, uv4.y) /// 第二條射線的終點(diǎn)存儲在tangent中,其中end=(tangent.xyz) /// 射線的最終點(diǎn)的法線存儲在normal中,就是normal = normal /// 這個點(diǎn)的結(jié)束時間存儲在tangent.w中,這個也是刷新的根據(jù)時間 /// color存儲了這批點(diǎn)的射中類型,x為1時就是第一條射線射中,y為1就是第二條射線射中 /// 為了物理模擬,移動時間設(shè)在uv6的x中 /// </summary> private void AddMesh() {//表示沒有開始循環(huán)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;//設(shè)置結(jié)束時間為負(fù)數(shù),讓Shader知道這個屬性沒有在使用中,//因為只有當(dāng)前時間在終止時間和終止時間減存活時間之間才會開始運(yùn)行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. 傳遞數(shù)據(jù)

首先是第一個射線射中的數(shù)據(jù)傳遞方式

/// <summary> /// 第一條射線射中目標(biāo) /// </summary> /// <param name="raycastHit">射中點(diǎn)數(shù)據(jù)</param> /// <param name="dis">理論上的最大距離,也就是本來這條線的長度</param> /// <param name="sqrTrue">中間射中了東西,所以確定實(shí)際長度</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);//設(shè)置起始位置SetPos(ref uv0[index], transform.position);uv1[index].x = transform.position.z;//設(shè)置第一條貝塞爾曲線中點(diǎn)Vector3 dir = transform.forward * dis * 0.5f + transform.position;SetPos(ref uv2[index], dir);uv1[index].y = dir.z;//第一條線的終點(diǎn)SetPos(ref uv3[index], raycastHit.point);uv4[index].x = raycastHit.point.z;//射中的法線normals[index] = raycastHit.normal;//設(shè)置粒子移動時間uv6[index].x = moveTime; }

第二個點(diǎn)射中,或者壓根就沒射中

/// <summary> /// 第二條射線射中的情況 /// </summary> /// <param name="raycastHit">射線射中點(diǎn)信息</param> /// <param name="upPos">第一條射線的終點(diǎn)</param> /// <param name="firstSqrMax">第一條射線長度的平方</param> /// <param name="fowardDir">第二條射線開始的方向,設(shè)為參數(shù)是為了考慮看向下方的情況</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);//設(shè)置起始位置SetPos(ref uv0[index], transform.position);uv1[index].x = transform.position.z;//設(shè)置第一條射線中間位置Vector3 dir = transform.forward * dis * 0.5f + transform.position;SetPos(ref uv2[index], dir);uv1[index].y = dir.z;//設(shè)置第一條線的終點(diǎn)SetPos(ref uv3[index], upPos);uv4[index].x = upPos.z;//設(shè)置第二條線的中間位置,注意,為了方便,這個點(diǎn)的賦值方式有點(diǎn)特別fowardDir *= (raycastHit.point - upPos).magnitude * 0.5f;fowardDir += upPos;SetPos(ref uv5[index], fowardDir);uv4[index].y = fowardDir.z;//設(shè)置第二條線終點(diǎn)SetPos(ref tangents[index], raycastHit.point);//設(shè)置射中點(diǎn)的法線normals[index] = raycastHit.normal;//設(shè)置粒子移動時間uv6[index].x = moveTime;}

6. 總代碼

using UnityEngine;namespace Common.ParticleSystem {/// <summary>/// 水模擬粒子系統(tǒng)控制器,需要時時刷新數(shù)據(jù),生成頂點(diǎn)/// </summary>public class ParticleWater : MonoBehaviour{private MeshFilter meshFilter;public Material setMat;/// <summary> /// 循環(huán)到的位置 /// </summary>public int circulatePos;/// <summary> /// 粒子輸出花費(fèi)時間 /// </summary>public float outTime = 0.3f;/// <summary> /// 粒子到達(dá)后開始偏移的損耗時間 /// </summary>public float offsetTime = 2f;/// <summary> /// 粒子數(shù)量,用來一開始創(chuàng)建 /// </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);//透明曲線,因為透明都在片原著色器使用,因此設(shè)置一個就夠了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數(shù)據(jù)設(shè)置位置,因為MeshFilter中的mesh設(shè)置沒有效果,只能將Mesh//提出來然后每次設(shè)置為后賦值了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;//設(shè)置移動大小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);//設(shè)置透明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);//設(shè)置大小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);//設(shè)置位置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;//檢查粒子是否可以使用,因為所有粒子是順序執(zhí)行的,不能用就直接退出int i = 0;for (; i<particleSize; i+=3){//可以使用,進(jìn)行操作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;//向上時執(zhí)行上拋,否則直接向下確定位置if (transform.forward.y >= 0){upTime = upDir.y / 9.8f;//確定第一條射線數(shù)據(jù)upDir.y = 0;upPos.y += 0.5f * 9.8f * upTime * upTime;upPos += upDir * upTime;veTemp = upPos - transform.position;//第一條線射中目標(biāo),檢查是否上方有東西阻擋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;}}//默認(rèn)底部高度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;//第二條射線默認(rèn)無限距離,往盡頭射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;}//第二條也沒有中,就在空中結(jié)束吧raycastHit.point = downPos;raycastHit.normal = Vector3.up;TwoRayHit(raycastHit, upPos, veTemp.magnitude, transform.forward, downTime + upTime);Debug.DrawLine(upPos, raycastHit.point, Color.white);}/// <summary>/// 第一條射線射中目標(biāo)/// </summary>/// <param name="raycastHit">射中點(diǎn)數(shù)據(jù)</param>/// <param name="dis">理論上的最大距離,也就是本來這條線的長度</param>/// <param name="sqrTrue">中間射中了東西,所以確定實(shí)際長度</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);//設(shè)置起始位置SetPos(ref uv0[index], transform.position);uv1[index].x = transform.position.z;//設(shè)置第一條貝塞爾曲線中點(diǎn)Vector3 dir = transform.forward * dis * 0.5f + transform.position;SetPos(ref uv2[index], dir);uv1[index].y = dir.z;//第一條線的終點(diǎn)SetPos(ref uv3[index], raycastHit.point);uv4[index].x = raycastHit.point.z;//射中的法線normals[index] = raycastHit.normal;//設(shè)置粒子移動時間uv6[index].x = moveTime;}/// <summary>/// 第二條射線射中的情況/// </summary>/// <param name="raycastHit">射線射中點(diǎn)信息</param>/// <param name="upPos">第一條射線的終點(diǎn)</param>/// <param name="firstSqrMax">第一條射線長度的平方</param>/// <param name="fowardDir">第二條射線開始的方向,設(shè)為參數(shù)是為了考慮看向下方的情況</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);//設(shè)置起始位置SetPos(ref uv0[index], transform.position);uv1[index].x = transform.position.z;//設(shè)置第一條射線中間位置Vector3 dir = transform.forward * dis * 0.5f + transform.position;SetPos(ref uv2[index], dir);uv1[index].y = dir.z;//設(shè)置第一條線的終點(diǎn)SetPos(ref uv3[index], upPos);uv4[index].x = upPos.z;//設(shè)置第二條線的中間位置,注意,為了方便,這個點(diǎn)的賦值方式有點(diǎn)特別fowardDir *= (raycastHit.point - upPos).magnitude * 0.5f;fowardDir += upPos;SetPos(ref uv5[index], fowardDir);uv4[index].y = fowardDir.z;//設(shè)置第二條線終點(diǎn)SetPos(ref tangents[index], raycastHit.point);//設(shè)置射中點(diǎn)的法線normals[index] = raycastHit.normal;//設(shè)置粒子移動時間uv6[index].x = moveTime;}/// <summary> /// 將第二個參數(shù)的xyz值賦予第一個參數(shù)的xyz中,簡化上面的函數(shù) /// </summary>private void SetPos(ref Vector4 vector, Vector3 vector3){vector.x = vector3.x;vector.y = vector3.y;vector.z = vector3.z;}/// <summary> /// 將第二個參數(shù)的xyz值賦予第一個參數(shù)的xyz中,簡化上面的函數(shù) /// </summary>private void SetPos(ref Vector2 vector, Vector3 vector3){vector.x = vector3.x;vector.y = vector3.y;}/// <summary>/// 用來初始化這個生成的頂點(diǎn),為了畫出較好的貝塞爾曲線,/// 且保證每一邊都是貝塞爾曲線,打算將三個點(diǎn)的數(shù)據(jù)傳入其中/// 首先頂點(diǎn)數(shù)據(jù)依舊用來存儲隨機(jī)數(shù),這個數(shù)據(jù)是固定不變的,/// 第一條射線的起點(diǎn)存儲在uv0和uv1(x)中,即begin=(uv0.xy, uv1.x)/// 第一條射線的貝塞爾曲線 中點(diǎn) 存儲在uv1(y)和uv2中,其中center=(uv2.xy, uv1.y)/// 第一條射線的終點(diǎn)[也是第二條射線的起點(diǎn)]在uv3和uv4(x)中,即end=(uv3.xy, uv4.x)/// 第二條射線的貝塞爾曲線 中點(diǎn) 存儲在uv4(y)和uv5中,其中center=(uv5.xy, uv4.y)/// 第二條射線的終點(diǎn)存儲在tangent中,其中end=(tangent.xyz)/// 射線的最終點(diǎn)的法線存儲在normal中,就是normal = normal/// 這個點(diǎn)的結(jié)束時間存儲在tangent.w中,這個也是刷新的根據(jù)時間/// color存儲了這批點(diǎn)的射中類型,x為1時就是第一條射線射中,y為1就是第二條射線射中/// 為了物理模擬,移動時間設(shè)在uv6的x中/// </summary>private void AddMesh(){//表示沒有開始循環(huán)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;//設(shè)置結(jié)束時間為負(fù)數(shù),讓Shader知道這個屬性沒有在使用中,//因為只有當(dāng)前時間在終止時間和終止時間減存活時間之間才會開始運(yùn)行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;}} }

傳遞完數(shù)據(jù)后就到Shader的模擬階段了,估計要過一段時間才能寫后面,最近到大作業(yè)的時間了,雖然大學(xué)課程很水,但是績點(diǎn)太低還是很難看的,所以更新時間估計要延遲一點(diǎn)了。而且項目壓力好大,找隊友真的是個難題,找到些水貨真的心累,整個項目代碼我寫、框架我搭、著色都我寫,明明都是程序員,寫個代碼有這么難嘛,幸虧我有水平,繃得住,還有閑暇時間寫文章。

總結(jié)

以上就是這篇文章的全部內(nèi)容,這篇文章只是起始部分,在之后的著色部分才是大頭,畢竟要一整套流程搞下來還是內(nèi)容挺多的,這里只是最簡單的數(shù)據(jù)傳遞部分而已。
實(shí)際上這里的數(shù)據(jù)傳遞還有可以優(yōu)化的部分,因為直接使用Set來傳遞頂點(diǎn)數(shù)據(jù)的話是替換,從效率來看的話是很慢的,畢竟我們實(shí)際上只是改變部分值,卻替換了整一個數(shù)組對象,因為C#和unity底層有區(qū)別,本質(zhì)上模型存儲的數(shù)據(jù)應(yīng)該不是代碼中控制的數(shù)組數(shù)據(jù),因此在替換時會進(jìn)行數(shù)據(jù)檢測、替換等階段,效率很低。
在看unity官方文檔時看到流操作傳遞數(shù)據(jù),既然是流,那一定很快啊,但是官方文檔的操作寫的好隨便,網(wǎng)上也沒有找到資料是最令我震驚的,如果有人知道請在下面評論區(qū)解答一下,畢竟能優(yōu)化是最好的。

總結(jié)

以上是生活随笔為你收集整理的在Unity中实现基于粒子的水模拟的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。