贝塞尔曲线和路径¶
贝塞尔曲线是自然几何形状的数学近似。我们使用它们来表示一个尽可能少的信息和高度灵活性的曲线。
与更抽象的数学概念不同,贝塞尔曲线是为工业设计而创建的。它们是图形软件行业中的流行工具。
他们依赖 interpolation 我们在上一篇文章中看到,结合多个步骤来创建平滑的曲线。为了更好地理解贝塞尔曲线是如何工作的,让我们从最简单的形式开始:二次贝塞尔曲线。
二次贝塞尔¶
取三个点,二次贝塞尔工作所需的最小值:
为了在它们之间绘制一条曲线,我们首先使用从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
.
添加控制点¶
在三次贝塞尔曲线的基础上,我们可以改变两个点的工作方式来自由控制曲线的形状。而不是 p0
, p1
, p2
和 p3
,我们将它们存储为:
point0 = p0
:是第一个点,源control0 = p1 - p0
:是相对于第一个控制点的矢量control1 = p3 - p2
:是相对于第二个控制点的矢量point1 = p3
:是第二个点,目的地
这样,我们有两个点和两个控制点,它们是各自点的相对矢量。如果您以前使用过图形或动画软件,这可能看起来很熟悉:
这就是图形软件如何向用户呈现贝塞尔曲线,以及它们在Godot中的工作和外观。
曲线2d,曲线3d,路径和路径2d¶
有两个对象包含曲线: Curve3D 和 Curve2D (分别用于3D和2D)。
它们可以包含多个点,允许更长的路径。也可以将它们设置为节点: Path 和 Path2D (同样适用于3D和2D):
然而,使用它们可能并不十分明显,因此下面是对贝塞尔曲线最常见用例的描述。
评价¶
仅仅评估它们可能是一种选择,但在大多数情况下,它不是很有用。贝塞尔曲线的最大缺点是,如果你以恒定的速度穿过它们, t = 0
到 t = 1
,实际插值将 not 以恒定速度移动。速度也是点之间距离的插值。 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)
然后输出将以恒定速度移动: