游戏开发中的贝塞尔曲线,曲线和路径

游戏开发中的贝塞尔曲线,曲线和路径


贝塞尔曲线是自然几何形状的数学近似。我们使用它们来表示一条曲线,该曲线具有尽可能少的信息并具有很高的灵活性。

与更抽象的数学概念不同,贝塞尔曲线是为工业设计而创建的。它们是图形软件行业中流行的工具。

它们依赖于插值(我在上一篇文章中提过),结合了多个步骤以创建平滑曲线。为了更好地了解贝塞尔曲线的工作原理,让我们从其最简单的形式开始:二次贝塞尔曲线。

二次贝塞尔曲线

取三点,这是二次贝塞尔曲线起作用的最低要求:

游戏开发中的贝塞尔曲线,曲线和路径

为了在它们之间绘制一条曲线,我们首先使用0到1范围内的值,在由三个点组成的两个线段的每个顶点的两个顶点上逐步进行插值。这使我们在改变线段值时沿着线段移动两个点的t从0到1。

func _quadratic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, t: float):
    var q0 = p0.linear_interpolate(p1, t)
    var q1 = p1.linear_interpolate(p2, t)

然后q0,我们进行插值并q1获得r沿曲线移动的单个点。

var r = q0.linear_interpolate(q1, t)
return r

这种类型的曲线称为二次贝塞尔曲线。

游戏开发中的贝塞尔曲线,曲线和路径
(图片来源:*)

三次贝塞尔曲线

在前面的示例的基础上,我们可以通过在四个点之间进行插值来获得更多控制。

游戏开发中的贝塞尔曲线,曲线和路径

我们首先使用的功能与四个参数取四点作为输入, p0,p1,p2和p3:

func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):

我们对每个点应用线性插值以将其减少到三个:

var q0 = p0.linear_interpolate(p1, t)
var q1 = p1.linear_interpolate(p2, t)
var q2 = p2.linear_interpolate(p3, t)

然后,我们将三点简化为两点:

var r0 = q0.linear_interpolate(q1, t)
var r1 = q1.linear_interpolate(q2, t)

并给一个:

var s = r0.linear_interpolate(r1, t)
return s

这是全部功能:

func _cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float):
    var q0 = p0.linear_interpolate(p1, t)
    var q1 = p1.linear_interpolate(p2, t)
    var q2 = p2.linear_interpolate(p3, t)

    var r0 = q0.linear_interpolate(q1, t)
    var r1 = q1.linear_interpolate(q2, t)

    var s = r0.linear_interpolate(r1, t)
    return s

结果将是在所有四个点之间插值的平滑曲线:

游戏开发中的贝塞尔曲线,曲线和路径

(图片来源:*)

注意

三次贝塞尔曲线插值在3D中的效果相同,只是使用Vector3 代替Vector2。

添加控制点

以立方贝塞尔曲线为基础,我们可以更改两个点的工作方式以*控制曲线的形状。而是具有p0p1p2p3,我们将它们存储为:

  • point0 = p0:是第一点,来源
  • control0 = p1 - p0:相对于第一个控制点的向量
  • control1 = p3 - p2:是相对于第二个控制点的向量
  • point1 = p3:是第二点,目的地

这样,我们有两个点和两个控制点,它们是相对于各个点的相对向量。如果您以前使用过图形或动画软件,则可能看起来很熟悉:

游戏开发中的贝塞尔曲线,曲线和路径

这就是图形软件如何向用户显示Bezier曲线,以及它们在Godot中的工作方式和外观。

Curve2D,Curve3D,路径和Path2D

有两个包含曲线的对象:Curve3D和Curve2D(分别用于3D和2D)。

它们可以包含多个点,从而可以使用更长的路径。也可以将它们设置为节点:Path和Path2D(也分别用于3D和2D):

游戏开发中的贝塞尔曲线,曲线和路径

但是,使用它们可能并不十分明显,因此以下是Bezier曲线最常见用例的描述。

评估

仅评估它们可能是一种选择,但是在大多数情况下,它不是很有用。 贝塞尔曲线的最大缺点是,如果以恒定速度从t = 0到t = 1遍历它们,则实际插补将不会以恒定速度移动。 速度也是点p0,p1,p2和p3之间距离的插值,并且没有数学上简单的方法来以恒定速度遍历曲线。

让我们用下面的伪代码做一个简单的例子:

var t = 0.0

func _process(delta):
    t += delta
    position = _cubic_bezier(p0, p1, p2, p3, t)

游戏开发中的贝塞尔曲线,曲线和路径

如您所见,即使t以恒定速度增加,圆的速度(以每秒像素为单位)也会变化。这使得贝塞尔曲线难以在开箱即用的情况下使用。

画画

绘制贝塞尔曲线(或基于曲线的对象)是一种非常常见的用例,但这也不容易。在几乎任何情况下,贝塞尔曲线都需要转换为某种线段。但是,这通常很困难,而又不创建大量的代码。

原因是曲线的某些部分(特别是拐角)可能需要大量的点,而其他部分可能不需要:

游戏开发中的贝塞尔曲线,曲线和路径

此外,如果两个控制点都是0, 0(请记住它们是相对矢量),则贝塞尔曲线将只是一条直线(因此绘制大量的点将是浪费的)。

在绘制贝塞尔曲线之前,需要进行细分。这通常通过递归或分而治之的功能来完成,该功能可以分割曲线,直到曲率量小于某个阈值为止。

的曲线类通过提供这种 Curve2D.tessellate()函数(其接收可选stages的递归和角度tolerance参数)。这样,基于曲线绘制对象就更容易了。

遍历

曲线的最后一个常见用例是遍历它们。由于前面提到的有关恒速的内容,这也很困难。

为了使此操作更容易,需要将曲线烘焙到等距的点。这样,它们可以通过常规插值进行近似(可以通过三次选项进一步改进)。为此,只需将Curve.interpolate_baked()方法与Curve2D.get_baked_length()一起使用 。第一次调用它们中的任何一个都会在内部烘焙曲线。

然后,可以使用以下伪代码完成恒速遍历:

var t = 0.0

func _process(delta):
    t += delta
    position = curve.interpolate_baked(t * curve.get_baked_length(), true)

然后,输出将以恒定速度移动:

游戏开发中的贝塞尔曲线,曲线和路径

上一篇:第四周一步步搭建神经网络以及应用(编程作业)


下一篇:01. linear regression正文