Unity3D教你制作Bezier和Spine曲线编辑器二

原创 2017年08月31日 11:17:02

在上篇博客中介绍了直线的制作,下面继续曲线的制作流程,曲线算法很多,比如Bezier(贝赛尔曲线)和B Spine(B样条曲线)等。下面介绍Bezier曲线, 具体来说,我们将创建一个Beziér曲线。

Bezier曲线

Beziér曲线由点序列定义, 它从第一点开始,在最后一点结束,创建一个新的Bezier曲线组件并给它一个点数组, 还要给它一个Reset方法,它用三点进行初始化, 该方法也可用作特殊的Unity方法,该方法在创建或重置组件时由编辑器调用。代码如下所示:

using UnityEngine;

public class BezierCurve : MonoBehaviour {

	public Vector3[] points;

	public void Reset () {
		points = new Vector3[] {
			new Vector3(1f, 0f, 0f),
			new Vector3(2f, 0f, 0f),
			new Vector3(3f, 0f, 0f)
		};
	}
}


继续可视化操作界面的编写,我们需要创建了一个基于LineInspector的曲线可视编辑, 为了减少代码重复,我们将显示点的代码移动到单独的ShowPoint方法,我们可以使用索引调用该方法,我们还将曲线,Handle转换和Handle旋转转换为类变量,因此我们不需要传递给ShowPoint。代码如下所示:

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(BezierCurve))]
public class BezierCurveInspector : Editor {

	private BezierCurve curve;
	private Transform handleTransform;
	private Quaternion handleRotation;

	private void OnSceneGUI () {
		curve = target as BezierCurve;
		handleTransform = curve.transform;
		handleRotation = Tools.pivotRotation == PivotRotation.Local ?
			handleTransform.rotation : Quaternion.identity;

		Vector3 p0 = ShowPoint(0);
		Vector3 p1 = ShowPoint(1);
		Vector3 p2 = ShowPoint(2);

		Handles.color = Color.white;
		Handles.DrawLine(p0, p1);
		Handles.DrawLine(p1, p2);
	}

	private Vector3 ShowPoint (int index) {
		Vector3 point = handleTransform.TransformPoint(curve.points[index]);
		EditorGUI.BeginChangeCheck();
		point = Handles.DoPositionHandle(point, handleRotation);
		if (EditorGUI.EndChangeCheck()) {
			Undo.RecordObject(curve, "Move Point");
			EditorUtility.SetDirty(curve);
			curve.points[index] = handleTransform.InverseTransformPoint(point);
		}
		return point;
	}
}

放到Unity中的效果图如下所示:



在编辑器中显示的效果:



给读者介绍一下:Beziér曲线的思想是它们是参数化的, 如果你给它一个值 - 通常叫做t - 在零和一之间,你会得到一个点在曲线上。 当t从零增加到1时,您从曲线的第一点移动到最后一点。
为了在场景中显示曲线,我们可以通过在曲线上的连续步长之间绘制直线来近似, 假设我们的曲线具有GetPoint方法,我们可以用简单的循环来做到这一点, 我们也不断绘制点之间的直线,但将其颜色变为灰色。代码如下:

private const int lineSteps = 10;
			
	private void OnSceneGUI () {
		curve = target as BezierCurve;
		handleTransform = curve.transform;
		handleRotation = Tools.pivotRotation == PivotRotation.Local ?
			handleTransform.rotation : Quaternion.identity;

		Vector3 p0 = ShowPoint(0);
		Vector3 p1 = ShowPoint(1);
		Vector3 p2 = ShowPoint(2);

		Handles.color = Color.gray;
		Handles.DrawLine(p0, p1);
		Handles.DrawLine(p1, p2);

		Handles.color = Color.white;
		Vector3 lineStart = curve.GetPoint(0f);
		for (int i = 1; i <= lineSteps; i++) {
			Vector3 lineEnd = curve.GetPoint(i / (float)lineSteps);
			Handles.DrawLine(lineStart, lineEnd);
			lineStart = lineEnd;
		}
	}

接下来,现在我们必须将GetPoint方法添加到Bezier曲线中,否则它将不会被编译。 这里我们再次做出一个假设,这次有一个实用的Beziér类可以对任何一个点进行计算, 我们把它们归结为一体,将结果转化为世界空间。

	public Vector3 GetPoint (float t) {
		return transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], t));
	}

所以我们使用所需的方法添加一个静态Bezier类 现在,让我们忽略中间点,简单地在第一个和最后一个点之间进行线性内插。

using UnityEngine;

public static class Bezier {

	public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
		return Vector3.Lerp(p0, p2, t);
	}
}

在Unity中的效果如下所示:





继续介绍,当然,终点之间的线性插值完全忽略了中点, 那么我们如何融入中点呢? 答案是多次插值。 首先,在第一个和中间点之间以及中间和最后一个点之间进行线性内插, 这给了我们两个新的点。 在这两个之间进行线性内插,给出了曲线上的最后一点。代码如下:

	public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
		return Vector3.Lerp(Vector3.Lerp(p0, p1, t), Vector3.Lerp(p1, p2, t), t);
	}

效果如下:

这种曲线被称为二次Beziér曲线,因为涉及多项式数学。
线性曲线可以写为B(t) = (1 - t) P0 + t P1
B(t) = (1 - t) ((1 - t) P0 + t P1) + t ((1 - t) P1 + t P2)更深一步。 这只是线性曲线,P0和P1被两条新的线性曲线取代。 它也可以被重写为更紧凑的形式B(t) = (1 - t)2 P0 + 2 (1 - tt P1 + t2 P2
所以我们可以使用二次公式而不是三次调用Vector3.Lerp。

对应的代码如下:

	public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
		t = Mathf.Clamp01(t);
		float oneMinusT = 1f - t;
		return
			oneMinusT * oneMinusT * p0 +
			2f * oneMinusT * t * p1 +
			t * t * p2;
	}


另外,我们的二次Beziér曲线的一阶导数是B'(t) = 2 (1 - t) (P1 - P0) + 2 t (P2 - P1)。 我们来补充一下:

	public static Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
		return
			2f * (1f - t) * (p1 - p0) +
			2f * t * (p2 - p1);
	}


继续解释,该函数产生与曲线相切的线,可以将其解释为沿着曲线移动的速度。 所以现在我们可以添加一个GetVelocity方法到Bezier曲线。因为它产生一个速度矢量而不是一个点,它不应该受到曲线的位置的影响,所以我们在变换后减去它。
	public Vector3 GetVelocity (float t) {
		return transform.TransformPoint(Bezier.GetFirstDerivative(points[0], points[1], points[2], t)) -
			transform.position;
	}

现在我们可以在Bezier曲线的Inspector的OnSceneGUI方法中可视化曲线的速度。

		Vector3 lineStart = curve.GetPoint(0f);
		Handles.color = Color.green;
		Handles.DrawLine(lineStart, lineStart + curve.GetVelocity(0f));
		for (int i = 1; i <= lineSteps; i++) {
			Vector3 lineEnd = curve.GetPoint(i / (float)lineSteps);
			Handles.color = Color.white;
			Handles.DrawLine(lineStart, lineEnd);
			Handles.color = Color.green;
			Handles.DrawLine(lineEnd, lineEnd + curve.GetVelocity(i / (float)lineSteps));
			lineStart = lineEnd;
		}



我们可以清楚地看到速度沿着曲线如何变化,但那些长长的线条使得视图混乱, 而不是显示速度,我们可以表现出运动的方向。

		Vector3 lineStart = curve.GetPoint(0f);
		Handles.color = Color.green;
		Handles.DrawLine(lineStart, lineStart + curve.GetDirection(0f));
		for (int i = 1; i <= lineSteps; i++) {
			Vector3 lineEnd = curve.GetPoint(i / (float)lineSteps);
			Handles.color = Color.white;
			Handles.DrawLine(lineStart, lineEnd);
			Handles.color = Color.green;
			Handles.DrawLine(lineEnd, lineEnd + curve.GetDirection(i / (float)lineSteps));
			lineStart = lineEnd;
		}

这需要我们将GetDirection添加到Bezier曲线,这简单地归一化速度。

	public Vector3 GetDirection (float t) {
		return GetVelocity(t).normalized;
	}

我们再来一步,为Bezier添加新的方法,以获得立方曲线! 它的工作原理就像二次版本,除了它需要第四点,其公式进一步深入,导致六个线性插值的组合。 其合并函数变为B(t) = (1 - t)3 P0 + 3 (1 - t)2 t P1 + 3 (1 - t) t2 P2 + t3 P3,其具有其一阶导数B'(t) = 3 (1 - t)2 (P1 - P0) + 6 (1 - tt (P2 - P1) + 3 t2 (P3 - P2)

	public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
		t = Mathf.Clamp01(t);
		float oneMinusT = 1f - t;
		return
			oneMinusT * oneMinusT * oneMinusT * p0 +
			3f * oneMinusT * oneMinusT * t * p1 +
			3f * oneMinusT * t * t * p2 +
			t * t * t * p3;
	}
	
	public static Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
		t = Mathf.Clamp01(t);
		float oneMinusT = 1f - t;
		return
			3f * oneMinusT * oneMinusT * (p1 - p0) +
			6f * oneMinusT * t * (p2 - p1) +
			3f * t * t * (p3 - p2);
	}

因此,我们可以通过考虑另外一点来将Bezier曲线从二次方升级为立方, 确保将第四个点手动添加到其阵列中,或者通过重置该组件。代码如下:

	public Vector3 GetPoint (float t) {
		return transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], points[3], t));
	}
	
	public Vector3 GetVelocity (float t) {
		return transform.TransformPoint(
			Bezier.GetFirstDerivative(points[0], points[1], points[2], points[3], t)) - transform.position;
	}
	
	public void Reset () {
		points = new Vector3[] {
			new Vector3(1f, 0f, 0f),
			new Vector3(2f, 0f, 0f),
			new Vector3(3f, 0f, 0f),
			new Vector3(4f, 0f, 0f)
		};
	}

现在,我们再增加一个点也就是四个点:

		Vector3 p0 = ShowPoint(0);
		Vector3 p1 = ShowPoint(1);
		Vector3 p2 = ShowPoint(2);
		Vector3 p3 = ShowPoint(3);
		
		Handles.color = Color.gray;
		Handles.DrawLine(p0, p1);
		Handles.DrawLine(p2, p3);



给读者解释一下:现在很明显,我们用直线段绘制曲线,我们可以增加提高视觉品质的步骤,我们还可以使用迭代方法来精确到像素级。但我们也可以使用Unity的句柄,用DrawBezier方法,它负责为我们绘制漂亮的立方Bezier曲线。我们也可以用它们自己的方法来显示方向,并按比例缩小它们占用的空间。

private const float directionScale = 0.5f;
	
	private void OnSceneGUI () {
		curve = target as BezierCurve;
		handleTransform = curve.transform;
		handleRotation = Tools.pivotRotation == PivotRotation.Local ?
			handleTransform.rotation : Quaternion.identity;
		
		Vector3 p0 = ShowPoint(0);
		Vector3 p1 = ShowPoint(1);
		Vector3 p2 = ShowPoint(2);
		Vector3 p3 = ShowPoint(3);
		
		Handles.color = Color.gray;
		Handles.DrawLine(p0, p1);
		Handles.DrawLine(p2, p3);
		
		ShowDirections();
		Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);
	}

	private void ShowDirections () {
		Handles.color = Color.green;
		Vector3 point = curve.GetPoint(0f);
		Handles.DrawLine(point, point + curve.GetDirection(0f) * directionScale);
		for (int i = 1; i <= lineSteps; i++) {
			point = curve.GetPoint(i / (float)lineSteps);
			Handles.DrawLine(point, point + curve.GetDirection(i / (float)lineSteps) * directionScale);
		}
	}


最终效果图如下所示:




代码下载地址:链接:http://pan.baidu.com/s/1gfrJVrl  密码:ft1o 中的编号02的包



版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

提高数据库性能,需要注意的某些事项

●查询速度慢的原因很多,常见如下几种: 1、没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2、I/O吞吐量小,形成了瓶颈效应。 3、没有创建计算列导致查询不优化。 ...

网络游戏之快照插值物理模拟

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等...

Lua开发环境搭建(一)--使用VS2012编译Lua

去官网下载lua的最新版本:www.lua.org Lua由三部分组成,即  * Lua链接库  * Lua解释器  * Lua编译器 因此,对于Lua源代码的编译主要就是编译Lua链接库、Lua...

游戏核心之固定流水线

笔者简介:姜雪伟,网名海洋,资深程序,CSDN社区专家,CSDN特邀编辑,畅销书作者,已出版书籍《手把手教你架构3D游戏引擎》、《Unity3D实战核心技术详解》等书籍。 笔者在1月4号...
  • jxw167
  • jxw167
  • 2017-01-03 10:20
  • 2586

如何学习一款游戏引擎

市面上的引擎应用的最广的无非是Cocos2D引擎和Unity3D引擎以及虚幻4引擎,当然还有许多开源的引擎,面对这么多的引擎,读者该如何学习?笔者就以自己工作十多年的经验给读者分享一下,因为人与人是不...
  • jxw167
  • jxw167
  • 2016-12-19 10:20
  • 3247

Unity3D教你制作Bezier和Spine曲线编辑器四

在上篇博客中介绍了关于曲线编辑器的制作,下面开始讲如何去编辑,这就需要在曲线上加控制点虽然我们的样条是连续的,但它在曲线段之间会急剧的变化,这些突然变化也导致了点的方向和速度变化,因为两个曲线之间的共...

Unity3D教你制作Bezier和Spine曲线编辑器一

以前做端游编辑器时,制作过曲线编辑器,现在使用Unity3D编程,Unity3D为我们提供了很多组件,但是在项目开发中,我们可能只需要某个小功能,但是要把整个组件加到项目中,一旦需求变换,修改起来比较...
  • jxw167
  • jxw167
  • 2017-08-30 16:41
  • 2274

Unity中的曲线绘制

Unity中的曲线绘制(一)——直线 直线   让我们从创建一条简单的直线开始。两点成线,我们需要一个起始点p0,以及终点p2;   现在我们可以创建一个游戏对象...

Unity3D教你制作Bezier和Spine曲线编辑器三

继续接着介绍曲线编辑器的制作,上篇博客介绍了关于Bezier曲线的制作,接下来给读者介绍Spine B样条曲线之作。 如果要创建复杂的曲线,我们需要连接多个曲线,这样的构造称为样条。让我们通过复制Be...

Unity3D教你制作Bezier和Spine曲线编辑器总结

上篇博客给读者介绍了关于曲线编辑器控制点的制作,最后给读者介绍如何使用曲线编辑器。 我们已经用样条做了一段时间了,但是还没有告诉读者如何使用它们。可以用样条做许多事情,例如,移动一个物体的路径,我们来...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)