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

举报
海拥 发表于 2021/11/27 16:35:44 2021/11/27
【摘要】 🌊 作者主页:海拥🌊 简介:🏆CSDN全栈领域优质创作者、🥇HDZ核心组成员、🥈蝉联C站周榜前十🌊 粉丝福利:粉丝群 每周送六本书,不定期送各种小礼品@TOC贝塞尔曲线是自然几何形状的数学近似。我们使用它们来表示一条曲线,该曲线具有尽可能少的信息并具有很高的灵活性。与更抽象的数学概念不同,贝塞尔曲线是为工业设计而创建的。它们是图形软件行业中流行的工具。它们依赖于插值(我在上一篇文...

🌊 作者主页:海拥
🌊 简介:🏆CSDN全栈领域优质创作者、🥇HDZ核心组成员、🥈蝉联C站周榜前十
🌊 粉丝福利:粉丝群 每周送六本书,不定期送各种小礼品

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

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

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

二次贝塞尔曲线

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

../../_images/bezier_quadratic_points.png

为了在它们之间绘制一条曲线,我们首先使用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

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

在这里插入图片描述
(图片来源:维基百科)

三次贝塞尔曲线

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

../../_images/bezier_cubic_points.png

我们首先使用的功能与四个参数取四点作为输入, 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:是第二点,目的地

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

../../_images/bezier_cubic_handles.png

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

Curve2D,Curve3D,路径和Path2D

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

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

../../_images/bezier_path_2d.png

但是,使用它们可能并不十分明显,因此以下是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以恒定速度增加,圆的速度(以每秒像素为单位)也会变化。这使得贝塞尔曲线难以在开箱即用的情况下使用。

画画

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

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

../../_images/bezier_point_amount.png

此外,如果两个控制点都是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)

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

在这里插入图片描述

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200