曲面细分进行水模拟(一:物理模拟)


文章目录

  • 曲面细分进行水模拟(一:物理模拟)
  • 前言
  • 一、曲线模拟的原理介绍
  • 二、代码计算终点
    • 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中实现基于粒子的水模拟相关推荐

  1. 在Unity中实现基于粒子的水模拟(二:开始着色)

    在Unity中实现基于粒子的水模拟(二:开始着色) 文章目录 在Unity中实现基于粒子的水模拟(二:开始着色) 前言 一.生成顶点 二.偏移模拟 1.接收细分着色器输出的顶点 2.根据数据调用对应的 ...

  2. 在Unity中实现基于粒子的水模拟(三:混合屏幕)

    在Unity中实现基于粒子的水模拟(三:混合屏幕) 文章目录 在Unity中实现基于粒子的水模拟(三:混合屏幕) 前言 一.着色算法介绍 1.折射 2.反射 二.准备纹理 1.获取纹理 2.模糊纹理 ...

  3. 在Unity中创建基于Node节点的编辑器 (二) 窗口序列化

    孙广东  2018.5.13 csdn 的产品 , 真垃圾, 不想吐槽了, 文章保存就丢!     没办法  .    怎么不满意, 还是得继续用, 哎~~~ 第二部分 在Unity中序列化基于节点的 ...

  4. 在Unity中创建基于Node节点的编辑器 (一)

    孙广东   2018.5.13 Unity  AssetStore中关于Node节点 编辑器相关的插件可是数不胜数, 状态机,行为树,Shader 可视化等等. Unity自己也有 Animator的 ...

  5. 学习在Unity中创建一个动作RPG游戏

    游戏开发变得简单.使用Unity学习C#并创建您自己的动作角色扮演游戏! 你会学到什么 学习C#,一种现代通用的编程语言. 了解Unity中2D发展的能力. 发展强大的和可移植的解决问题的技能. 了解 ...

  6. Unity中的灯光和渲染

    一:Unity中的灯光 --Directional Light:模拟太阳光.它与位置无关,是平行光,可以调整旋转角度模拟昼夜 --Spot Light:模拟车灯.手电筒的光.舞台灯光 --Point ...

  7. Unreal Engine 4 基于网格的水面模拟实现

    http://blog.csdn.net/shangguanwaner/article/details/51862644 Unreal Engine 4 水面模拟实现 一般游戏里水面的模拟都是实用动态 ...

  8. 粒子群课设_GitHub - LIYAJUN2018/tscss: 基于粒子群算法的中职自动排课系统

    kvf-admin kvf-admin是一套快速开发框架.脚手架.后台管理系统.权限系统,上手简单,拿来即用.为广大开发者去除大部分重复繁锁的代码工作,让开发者拥有更多的时间陪恋人.家人和朋友. 后端 ...

  9. (十九)unity shader之——————基于物理的渲染技术(PBS):中篇(Unity 5中的Standard Shader的实现和使用)

    一.unity 5中的standard shader 在unity5中新创建一个模型或是新创建一个材质时,默认使用的着色器都是一个名为standard 的着色器.这个standard shader使用 ...

最新文章

  1. java mcrypt encrypt_PHP mcrypt_encrypt加密,使用java解密
  2. Inside the C++ Object Model | Outline
  3. 工程图样中粗实线的用途_图纸天天画,粗实线和细实线的线宽比例是多少?2:1还是3:1?...
  4. oracle查询语句大全(oracle 基本命令大全一)
  5. 福大软工1816 · 第二次作业 - 个人项目
  6. 编程建立一通讯簿C语言,C语言编程问题用C语言编个学生通讯录管理系统,功能有:①创建通讯 爱问知识人...
  7. Activity与AppCompatActivity全屏实现方法
  8. linux日志搜索关键词_linux中的实用技巧和快捷键总结
  9. CitrixVDI新版动手实验手册
  10. 嵌入式linux 内核移植篇
  11. 中通2008通信概预算编制系统简介
  12. 愚人节里的巧合与必然:BAT等亮出的AI招牌故事
  13. leetcode 罗马数字与整数的转换算法
  14. 阿里云视频点播Demo
  15. C 彩色艺术化二维码样式设计(仅说思路)
  16. 30分钟理解关键链--《突破项目的瓶颈--关键链 》读书笔记
  17. 物联网-物联前端安全加密技术简介
  18. 有益信息分享方式转换到微博前的群发告知短信
  19. 【交换篇】03. 配置接口 ❀ C3750-E ❀ CISCO 交换机
  20. java窗体实现随机点名软件,求一个教师随机点名程序

热门文章

  1. 逻辑思维题及答案解析
  2. 干货分享 | B站SLO由失败转成功,B站SRE做对了什么?
  3. 大彩串口屏之LUA使用1
  4. Android实现获取未接来电和未读短信数量的方法
  5. 新手必看学习JAVA的N个理由,看阿…
  6. HDU 3236 Gift Hunting (程序猿的哄女朋友方式)
  7. Excel 两列合并为一列中间加空格
  8. 《小鸡快跑》的成功故事
  9. C语言之逆序输出一个四位数
  10. python爬虫入门爬取lpl选手价值排行榜