关于Unity3D动态生成连续性网格几何体总结【第四部分】(曲线平滑篇)
前言
前面几个部分已经完全讲解了动态网格的生成与UV、贴图等等的生成。本篇主要来解决之间留下来的瑕疵问题。
一、平滑算法
在讨论具体算法之前,我们来看看,为啥两条路径之间会出现网格重叠的问题,其实答案很简单,那么就是两条线段之间首与尾部的斜率无法保证一直,只要我们使首尾斜率保证一致的情况下,就能修复这个问题。
对比所有曲线方式以后,决定选择二次贝塞尔曲线作为两两路径之间的斜率进行斜率平滑。这里有三阶贝塞尔曲线的实例,不熟悉的同学可以去试试,玩玩看。
那么知道如何解决这个问题后,我们要做的就是具体计算每一条二次贝塞尔曲线的贝塞尔点。
但是对于我们这种手残党,肯定不可能每次都用双手加目测去调整贝塞尔曲线点。因此这里我们采用自动进行贝塞尔曲线的平滑方式来做。具体做法是,首先选择第一条子路径的尾部的斜率作为下一条子路径的首部的斜率,然后依次类推,子路径之间的斜率相互衔接,保证所有子路径衔接出的斜率保持一致。然后对于之前的细分节点的位置信息,也可以通过贝塞尔曲线进行采样获取。
自动计算贝塞尔曲线的代码:
/// <summary>
/// 自动计算贝塞尔曲线
/// </summary>
public static List<Vector3> AutoCaculateBezierCurve(List<Vector3> pathList, bool isNeedCure = true, float checkAnagle = 15, float fixedAngle = 10, Vector3 lastGradient = default(Vector3), Vector3 nextGradient = default(Vector3))
{
List<Vector3> beizerList = new List<Vector3>(pathList.Count - 1);
for (int beizerIndex = 0; beizerIndex < pathList.Count - 1; beizerIndex++)
{
beizerList.Add((pathList[beizerIndex + 1] + pathList[beizerIndex]) / 2);
}
if (isNeedCure && pathList.Count > 1)
{
AutoCurveByNormal(pathList, beizerList, checkAnagle, fixedAngle);
}
return beizerList;
}
/// <summary>
/// 平滑路径
/// </summary>
/// <param name="pathList">路径点</param>
/// <param name="beizerList">贝塞尔曲线</param>
/// <param name="checkAnagle">检测曲线角度阈值</param>
/// <param name="fixedAngle">修复曲线角度阈值</param>
private static void AutoCurveByNormal(List<Vector3> pathList, List<Vector3> beizerList, float checkAnagle, float fixedAngle)
{
for (int i = 0; i < pathList.Count - 2; i++)
{
//这里是把曲线转化成二维平面,如果要使用三维曲线平滑,只需要加上y值就可以了
//上一个子路径的起始点
Vector3 preStartNode = new Vector3(pathList[i].x, 0, pathList[i].z);
//上一个子路径的终止点
Vector3 preEndNode = new Vector3(pathList[i + 1].x, 0, pathList[i + 1].z);
//上一个子路径的贝塞尔点
Vector3 preBeizerNode = new Vector3(beizerList[i].x, 0, beizerList[i].z);
float preLength = (preEndNode - preStartNode).magnitude;
//后一个子路径的起始点
Vector3 nextStartNode = new Vector3(pathList[i + 1].x, 0, pathList[i + 1].z);
//后一个子路径的终止点
Vector3 nextEndNode = new Vector3(pathList[i + 2].x, 0, pathList[i + 2].z); ;
//后一个子路径的贝塞尔曲线点
Vector3 nextBeizerNode = new Vector3(beizerList[i + 1].x, 0, beizerList[i + 1].z);
float nextLength = (nextEndNode - nextStartNode).magnitude;
//获取上一个路径的尾部路径斜率
Vector3 preGradient = BeizerUilityTools.BezierCurveGradient(preStartNode, preEndNode, preBeizerNode, 1);
//获取后一个路径的尾部路径斜率
Vector3 nextGradient = BeizerUilityTools.BezierCurveGradient(nextStartNode, nextEndNode, nextBeizerNode, 0);
//首尾斜率的平面法线
Vector3 normal = Vector3.Cross(preGradient, nextGradient).normalized;
float angle = Vector3.Angle(preGradient, nextGradient);
//检测两斜率之间的角度差是否满足目标阈值
if (angle > checkAnagle)
{
//需要修复的角度差值
float lerp = angle - fixedAngle;
//按照比例进行曲线斜率调整
float scale = preLength / (nextLength + preLength);
if (i == 0)
{
//调整后的上一个路径尾部斜率
preGradient = Quaternion.AngleAxis(lerp * scale, normal) * preGradient;
//通过斜率值来获取上一个路径的贝塞尔曲线点的位置
beizerList[i] = BeizerUilityTools.GetBezierCurePiontByGradient(preGradient, preStartNode, preEndNode, 1);
beizerList[i] = new Vector3(beizerList[i].x, (pathList[i].y + pathList[i + 1].y) / 2, beizerList[i].z);
//调整后的后一个路径尾部斜率
nextGradient = Quaternion.AngleAxis(-lerp * (1 - scale), normal) * nextGradient;
//通过斜率值来获取上一个路径的贝塞尔曲线点的位置
beizerList[i + 1] = BeizerUilityTools.GetBezierCurePiontByGradient(nextGradient, nextStartNode, nextEndNode, 0);
beizerList[i + 1] = new Vector3(beizerList[i + 1].x, (pathList[i + 1].y + pathList[i + 2].y) / 2, beizerList[i + 1].z);
}
else
{
//如果是整体路径的开始点,就直接使用尾部斜率
nextGradient = Quaternion.AngleAxis(-lerp, normal) * nextGradient;
beizerList[i + 1] = BeizerUilityTools.GetBezierCurePiontByGradient(nextGradient, nextStartNode, nextEndNode, 0);
beizerList[i + 1] = new Vector3(beizerList[i + 1].x, (pathList[i + 1].y + pathList[i + 2].y) / 2, beizerList[i + 1].z);
}
}
}
}
通过贝塞尔曲线进行子路径内部的节点细分操作代码:
//通过beizer曲线进行节点细分
for (int pathIndex = 0; pathIndex < pathCount; pathIndex++)
{
Vector3 curNode = pathList[pathIndex];//子路径起始节点
Vector3 nextNode = pathList[pathIndex + 1];//子路径终止节点
Vector3 bezierNode = beizerList[pathIndex];//子路径的贝塞尔点
for (int segmentIndex = pathIndex == 0 ? 0 : 1; segmentIndex < segment; segmentIndex++)
{
//获取子路径贝塞尔曲线的等分比例t
float t = segmentIndex * 1.0f / (segment - 1);
//通过曲线的等分比例获取对应的位置点
Vector3 segmentPoint = BeizerUilityTools.BezierCurve(curNode, nextNode, bezierNode, t);
//获取当期节点的斜率值
Vector3 gradient = BeizerUilityTools.BezierCurveGradient(curNode, nextNode, bezierNode, t);
for (int verticesIndex = 0; verticesIndex < templteVerticesCount; verticesIndex++)
{
//顶点值的位置
Vector3 localPos = templteVertice[verticesIndex];
//通过细分节点的位置与斜率,在计算当前顶点的世界坐标
Vector3 worldPos = localToWorld(localPos, segmentPoint, gradient);
tempVerticeCount++;
}
}
}
二、平滑计算公式
关于二次贝塞尔曲线的资源,直接度娘或者谷歌一下,就是一大把。废话不多说,直接上代码:
public class BeizerUilityTools
{
/// <summary>
/// 转化斜率值
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
public static float VectorConversionToSlope(Vector3 dir)
{
return dir.z / dir.x;
}
/// <summary>
/// 获取两点之间的贝塞尔曲线点
/// </summary>
/// <param name="startGradient"></param>
/// <param name="endGradient"></param>
/// <param name="startPoint"></param>
/// <param name="endPoint"></param>
/// <param name="middlePoint"></param>
/// <returns></returns>
public static Vector3 CaculateBeizerNodeByTwoLine(float startGradient, float endGradient, Vector3 startPoint, Vector3 endPoint, Vector3 middlePoint)
{
if (startGradient == endGradient)
{
Debug.LogError("斜率相等,无法计算贝赛点");
return middlePoint;
}
float bezierX = (startGradient * startPoint.x - endGradient * endPoint.x - startPoint.z + endPoint.z) / (startGradient - endGradient);
float beizerY = startGradient * (bezierX - startPoint.x) + startPoint.z;
return new Vector3(bezierX, 0, beizerY);
}
/// <summary>
/// 获取点之间的距离
/// </summary>
/// <param name="totalVector3"></param>
/// <returns></returns>
public static float GetTotalLength(Vector3[] totalVector3)
{
float totalLength = 0;
for (int i = 1; i < totalVector3.Length; i++)
{
totalLength += (totalVector3[i] - totalVector3[i - 1]).magnitude;
}
return totalLength;
}
/// <summary>
/// 获取点之间的距离
/// </summary>
/// <param name="totalVector3"></param>
/// <returns></returns>
public static float GetSqrTotalLength(Vector3[] totalVector3)
{
float totalLength = 0;
for (int i = 1; i < totalVector3.Length; i++)
{
totalLength += CaculateSqrDistance(totalVector3[i], totalVector3[i - 1]);
}
return totalLength;
}
public static float CaculateSqrDistance(Vector3 start, Vector3 end)
{
float valueX = (start.x - end.x);
float valutY = (start.y - end.y);
float valutZ = (start.z - end.z);
return valueX * valueX + valutY * valutY + valutZ * valutZ;
}
/// <summary>
/// 获取别塞尔曲线的采样点
/// </summary>
/// <param name="startNode"></param>
/// <param name="endNode"></param>
/// <param name="bezierNode"></param>
/// <param name="pointCount"></param>
/// <returns></returns>
public static float BezierLength(Vector3 startNode, Vector3 endNode, Vector3 bezierNode, int pointCount = 30)
{
if (pointCount < 2)
{
return 0;
}
float length = 0.0f;
Vector3 lastPoint = BezierCurve(startNode, endNode, bezierNode, 0.0f / (float)pointCount);
for (int i = 1; i <= pointCount; i++)
{
Vector3 point = BezierCurve(startNode, endNode, bezierNode, (float)i / (float)pointCount);
length += Vector3.Distance(point, lastPoint);
lastPoint = point;
}
return length;
}
/// <summary>
/// 二次贝塞尔
/// </summary>
public static Vector3 BezierCurve(Vector3 startNode, Vector3 endNode, Vector3 bezierNode, float t)
{
return (1 - t) * (1 - t) * startNode + 2 * t * (1 - t) * bezierNode + t * t * endNode;
}
/// <summary>
/// 二次贝塞尔斜率
/// </summary>
/// <param name="startNode"></param>
/// <param name="endNode"></param>
/// <param name="bezierNode"></param>
/// <param name="t"></param>
/// <returns></returns>
public static Vector3 BezierCurveGradient(Vector3 startNode, Vector3 endNode, Vector3 bezierNode, float t)
{
return (2 * startNode - 4 * bezierNode + 2 * endNode) * t + 2 * bezierNode - 2 * startNode;
}
/// <summary>
/// 通过斜率得到贝赛尔曲线的位置
/// </summary>
/// <param name="startNode"></param>
/// <param name="endNode"></param>
/// <param name="t"></param>
/// <returns></returns>
public static Vector3 GetBezierCurePiontByGradient(Vector3 gradient, Vector3 startNode, Vector3 endNode, float t)
{
return (gradient + (1 - t) * 2 * startNode - 2 * endNode * t) / (2 * (1 - 2 * t));
}
/// <summary>
/// 获取贝塞尔曲线交点的t值
/// </summary>
/// <param name="startNodeValue"></param>
/// <param name="endNodeValue"></param>
/// <param name="bezierNodeValue"></param>
/// <param name="value"></param>
/// <returns></returns>
public static float GetBizerCurveTByValue(float startNodeValue, float endNodeValue, float bezierNodeValue, float value)
{
float t = 0;
if ((startNodeValue + endNodeValue - 2 * bezierNodeValue) == 0)
{
t = (value - endNodeValue) / (2 * (bezierNodeValue - startNodeValue));
}
else
{
float t1 = (2 * (startNodeValue - bezierNodeValue) + Mathf.Sqrt((2 * bezierNodeValue - 2 * startNodeValue) * (2 * bezierNodeValue - 2 * startNodeValue) - 4 * (startNodeValue + endNodeValue - 2 * bezierNodeValue) * (startNodeValue - value))) / (2 * (startNodeValue + endNodeValue - 2 * bezierNodeValue));
float t2 = (2 * (startNodeValue - bezierNodeValue) - Mathf.Sqrt((2 * bezierNodeValue - 2 * startNodeValue) * (2 * bezierNodeValue - 2 * startNodeValue) - 4 * (startNodeValue + endNodeValue - 2 * bezierNodeValue) * (startNodeValue - value))) / (2 * (startNodeValue + endNodeValue - 2 * bezierNodeValue));
t = (t1 < 0 || t1 > 1) ? t2 : t1;
}
return t;
}
}
总结
以上就是今天要讲的内容,本文仅仅简单连续生成动态模型的平滑方式与算法,修复上部分留下来的一些瑕疵,后面将讲讲如何高性能生成动态网格,敬请期待!
Demo展示地址:
https://download.csdn.net/download/u011194970/20350094
传送门
上一篇 :关于Unity3D动态生成连续性网格几何体总结【第三部分】(贴图篇)