Unity3D 参数曲线 实现曲线上的匀速运动

环境:Unity2021.1.14 语言:C#

总起

本文源代码可以在https://github.com/anguangzhihen/TestOdinInspector中的TestCurve场景中找到。

Bezier曲线和Catmull-Rom曲线是工程中常见的曲线实现方式,他们本身原理十分简单,只是个多项式方程组,拿到公式带入就能实现。

Bezier曲线和Catmull-Rom曲线之间可以相互转换,所以本篇内容只针对Bezier曲线进行说明,Catmull-Rom本质上是一样的。

最近在工作中需要使用Bezier曲线来控制镜头运动,实现后在跟美术反复沟通后发现,他们想要时间曲线直接控制镜头的运动速度,但是现在Bezier的实现中传入参数t在曲线上运动是有快有慢的,这会造成美术在控制时间曲线时非常不直观。

因此保证传入参数使得能在曲线上匀速运动是非常重要的。

后面查了很多资料后发现了一种工业界常用的方案能解决该问题——参数曲线。

Bezier曲线

一般开发中最常用的Bezier曲线为三次方公式:

 具体实现也非常简单:

public static Vector3 GetBezierPoint(Vector3[] points, float t)
{
	Vector3 p0 = points[0];
	Vector3 p1 = points[1];
	Vector3 p2 = points[2];
	Vector3 p3 = points[3];

	float rest = (1f - t);
	Vector3 newPos = Vector3.zero;
	newPos += p0 * rest * rest * rest;
	newPos += p1 * t * 3f * rest * rest;
	newPos += p2 * 3f * t * t * rest;
	newPos += p3 * t * t * t;
	return newPos;
}

实现效果:

Unity3D 参数曲线 实现曲线上的匀速运动_第1张图片

 

可以看到采样小球并不是均匀分布的。

而我们的目标就是获得一个函数t=f(s),使得传入的s保证是匀速运动的,s具体来说应该是曲线长度的百分比,最终目标就变成了获取弧长。

参数曲线

包括Bezier曲线的很多曲线都能表示为以下形式:

 使用Bezier曲线公式进行推导就能获得具体的A、B、C、D:

public static ParametricCurve CreateByBezier(Vector3[] points)
{
	ParametricCurve curve = new ParametricCurve();
	curve.points = points;
	curve.A = -1 * points[0] + 3 * points[1] - 3 * points[2] + points[3];
	curve.B = 3 * points[0] - 6 * points[1] + 3 * points[2];
	curve.C = -3 * points[0] + 3 * points[1];
	curve.D = points[0];
	return curve;
}

然后对该公式求导获取绝对值后,求0到t的积分就能获得从0到t处的弧长:

 积分的求解非常复杂,《数值分析》中对这块内容有详细介绍,我只是简单的学习了一下就不班门弄斧了。不过具体应用起来还是很方便的,我们这边采用Gauss-Legendre求积公式:

public float GetArcLength(float t)
{
	var halfT = t / 2f;

	float sum = 0f;
	foreach (var wx in gaussWX)
	{
		var w = wx[0];
		var x = wx[1];
        // 权值w乘以公式带入特定值x
		sum += w * GetPointDer(halfT * x + halfT).magnitude;
	}
	sum *= halfT;
	return sum;
}

获得了该公式后,我们实际已知的是s,而需要求t,这个时候使用牛顿迭代法迭代3、4次就可以快速的获得精确的t:

 其中t0是初始迭代值,t1是下一个迭代值,这边我们t0就直接选用s了:

public float S2T(float s)
{
	const int NEWTON_SEGMENT = 4;

	s = Mathf.Clamp01(s);
	float t = s;
	// 牛顿迭代法
	for (int i = 0; i < NEWTON_SEGMENT; i++)
	{
		t = t - (T2S(t) - s) / T2SDer(t);
	}
	return t;
}

结果:

Unity3D 参数曲线 实现曲线上的匀速运动_第2张图片

 

参考

《微积分的本质》【官方双语/合集】微积分的本质 - 系列合集_哔哩哔哩_bilibili

《数值分析》第2版 Timothy Sauer

如何得到贝塞尔曲线的曲线长度和 t 的近似关系? - 知乎

你可能感兴趣的:(客户端工具,游戏程序,unity,参数曲线,贝塞尔曲线,曲线)