一,什么是贝塞尔曲线
百度百科诠释:贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线,贝兹曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋,我们在绘图工具上看到的钢笔工具就是来做这种矢量曲线的。贝塞尔曲线是计算机图形学中相当重要的参数曲线,在一些比较成熟的位图软件中也有贝塞尔曲线工具,如PhotoShop等。在Flash4中还没有完整的曲线工具,而在Flash5里面已经提供出贝塞尔曲线工具。
说太多概念也没什么用,直接看公式吧:
1.线性公式:
给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。这条线由下式给出:
一阶曲线就是根据t来的线性插值,还不明白,来看张图你应该就明白了;
(网图)
2.二次方公式:
二次方贝塞尔曲线的路径由给定点P0、P1、P2的函数B(t)公式推导:由(P0,P1),(P1,P2)分别求线性公式所得的结果P0‘ 和 P1‘再带入线性公式,整理所得即为二次公式:
P0,P1所求:
P1,P2所求:
P0,P1,P2二次方公式:
简化所得:
3.三次方公式
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝兹曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。
其公式为:
【推导方式(二次方公式同理):(P0,P1,P2),(P1,P2,P3)分别求二次方公式,所得结果在带入线性公式,简化后即为上式】
4.一般参数公式
N阶贝塞尔曲线可如下推断。给定点P0、P1、…、Pn,其公式为:
(百度百科图)
二,在UNITY中的运用
1.公式转换为代码:
public class BezierCurveTest
{
/// <summary>
/// 线性公式
/// </summary>
Vector3 Bezier(Vector3 p0, Vector3 p1, float t)
{
return (1 - t) * p0 + t * p1;
}
/// <summary>
/// 二次方公式
/// </summary>
Vector3 Bezier(Vector3 p0, Vector3 p1, Vector3 p2, float t)
{
Vector3 p0p1 = (1 - t) * p0 + t * p1;
Vector3 p1p2 = (1 - t) * p1 + t * p2;
Vector3 result = (1 - t) * p0p1 + t * p1p2;
return result;
}
/// <summary>
/// 三次方公式
/// </summary>
Vector3 Bezier(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
Vector3 result;
Vector3 p0p1 = (1 - t) * p0 + t * p1;
Vector3 p1p2 = (1 - t) * p1 + t * p2;
Vector3 p2p3 = (1 - t) * p2 + t * p3;
Vector3 p0p1p2 = (1 - t) * p0p1 + t * p1p2;
Vector3 p1p2p3 = (1 - t) * p1p2 + t * p2p3;
result = (1 - t) * p0p1p2 + t * p1p2p3;
return result;
}
}
2.举个例子
看了半天公式也转换成代码了,那么它到底用的呢?
看个例子:在Unity中绘制一条曲线
搭建场景如下图:
创建一个空物体作为父物体并挂载下面脚本,然后创建四个cube作为曲线控制点,即可在Scene视图下看到上图效果
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class BezierCurveTest : MonoBehaviour
{
// 点的半径
public float radius = 1;
// 曲线取点的密度
public int densityCurve = 1000;
/// <summary>
/// 绘制曲线控制点 -- 此脚本的子物体
/// </summary>
public List<GameObject> ControlPointList = new List<GameObject>();
/// <summary>
/// 当前绘制曲线的所有点
/// </summary>
public List<Vector3> CurvePointList = new List<Vector3>();
/// <summary>
/// 编辑状态下子自动绘制曲线
/// </summary>
private void OnDrawGizmos()
{
// 绘制前重新添加控制点
ControlPointList.Clear();
foreach (Transform item in transform)
{
ControlPointList.Add(item.gameObject);
}
// Select 取每个点的position作为新的元素
List<Vector3> controlPointPos = ControlPointList.Select(point => point.transform.position).ToList();
// 经过三阶运算返回的需要绘制的点
var points = GetDrawingPoints(controlPointPos, densityCurve);
Vector3 startPos = points[0];
CurvePointList.Clear();
CurvePointList.Add(startPos);
for (int i = 1; i < points.Count; i++)
{
if (Vector3.Distance(startPos, points[i]) >= radius)
{
startPos = points[i];
CurvePointList.Add(startPos);
}
}
//绘制曲线
Gizmos.color = Color.blue;
foreach (var item in CurvePointList)
{
Gizmos.DrawSphere(item, radius * 0.5f);
}
//绘制曲线控制点连线
Gizmos.color = Color.red;
for (int i = 0; i < controlPointPos.Count - 1; i++)
{
Gizmos.DrawLine(controlPointPos[i], controlPointPos[i + 1]);
}
}
/// <summary>
/// 获取绘制点
/// </summary>
/// <param name="controlPoints"></param>
/// <param name="segmentsPerCurve"></param>
/// <returns></returns>
public List<Vector3> GetDrawingPoints(List<Vector3> controlPoints, int segmentsPerCurve)
{
List<Vector3> points = new List<Vector3>();
// 下一段的起始点和上段终点是一个,所以是 i+=3
for (int i = 0; i < controlPoints.Count - 3; i += 3)
{
var p0 = controlPoints[i];
var p1 = controlPoints[i + 1];
var p2 = controlPoints[i + 2];
var p3 = controlPoints[i + 3];
for (int j = 0; j <= segmentsPerCurve; j++)
{
var t = j / (float)segmentsPerCurve;
points.Add(CalculateBezierPoint(t, p0, p1, p2, p3));
}
}
return points;
}
// 三阶公式
Vector3 CalculateBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
Vector3 result;
Vector3 p0p1 = (1 - t) * p0 + t * p1;
Vector3 p1p2 = (1 - t) * p1 + t * p2;
Vector3 p2p3 = (1 - t) * p2 + t * p3;
Vector3 p0p1p2 = (1 - t) * p0p1 + t * p1p2;
Vector3 p1p2p3 = (1 - t) * p1p2 + t * p2p3;
result = (1 - t) * p0p1p2 + t * p1p2p3;
return result;
}
}
PS:后续可以根据需求调整点的半径(radius )和曲线取点的密度(densityCurve )进行调整。
三,在实际项目中应用
根据上述示例,很容易想到在指定按照指定路径移动的情况下可以使用(比如:祖玛,保卫萝卜,塔防类)
下面这个Demo制作了一个可视化操作生成曲线点的工具;
实现原理:和二项中一致,我添加了一键保存配置文件扩展,它可以帮助你快速的实现关卡路径的制作,将所得的坐标点保存到本地文件,用的时候在拿出来用就可以了;
场景搭建:
实现效果如下: