cubic_interpolate
最后同步日期:2026-04-15 | Godot 官方原文 — cubic_interpolate
cubic_interpolate
定义
cubic_interpolate() 是一个在两个值之间进行三次插值(也叫三次 Hermite 插值)的数学函数。它能让你从一个值平滑地过渡到另一个值,而且过渡的轨迹是一条优美的曲线,而不是生硬的直线。
打个比方:如果你要把一条绳子从 A 点拉到 B 点,线性插值就像把绳子拉成一条笔直的直线,而三次插值就像让绳子自然垂下来形成一条平滑的 S 形曲线——它会考虑 A 之前和 B 之后的点,让过渡更加丝滑自然。
为什么需要三次插值?
在游戏开发中,动画和运动的"丝滑程度"直接影响玩家的体验。线性插值虽然简单,但 transitions 看起来会很"机械"。三次插值能产生更自然的运动曲线,常见用途包括:
- 相机跟随:让相机移动时先慢后快再慢,而不是匀速移动。
- 关键帧动画:在多个关键帧之间自动生成平滑的中间帧。
- 路径插值:让角色沿着预设的关键点平滑移动,而不是走折线。
函数签名
// Mathf.CubicInterpolate 返回 float
public static float CubicInterpolate(
float from, // 起始值
float to, // 目标值
float pre, // 起始值之前的关键点
float post, // 目标值之后的关键点
float weight // 插值权重(0 到 1 之间)
)func cubic_interpolate(
from: float, # 起始值
to: float, # 目标值
pre: float, # 起始值之前的关键点
post: float, # 目标值之后的关键点
weight: float # 插值权重(0 到 1 之间)
) -> float参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
| from | float | 是 | 起始值,插值的起点。当 weight 为 0 时,返回此值 |
| to | float | 是 | 目标值,插值的终点。当 weight 为 1 时,返回此值 |
| pre | float | 是 | 起始值之前的关键点。它影响曲线在起始处的弯曲方向,让曲线不会突然"折弯" |
| post | float | 是 | 目标值之后的关键点。它影响曲线在结束处的弯曲方向,让曲线平滑"刹车" |
| weight | float | 是 | 插值权重,范围 0 到 1。0 表示完全在 from 处,1 表示完全在 to 处 |
关于 pre 和 post 的理解:想象你在画一条穿过 4 个点的曲线——
pre->from->to->post。函数根据这 4 个点计算出from和to之间的平滑曲线。pre和post就像是"引导点",告诉你曲线应该从哪个方向"进来"、往哪个方向"出去"。
返回值
类型: float
返回在 from 和 to 之间按 weight 权重进行三次插值后的结果。
不同 weight 值的行为:
| weight | 返回值 | 说明 |
|---|---|---|
0.0 | from | 完全处于起始位置 |
0.5 | 中间的曲线值 | 处于曲线中段(不一定是中点) |
1.0 | to | 完全处于目标位置 |
代码示例
基本用法
using Godot;
public partial class CubicInterpExample : Node
{
public override void _Ready()
{
// 四个关键点:pre=0, from=1, to=3, post=2
float pre = 0.0f;
float from = 1.0f;
float to = 3.0f;
float post = 2.0f;
// 不同权重下的插值结果
GD.Print(Mathf.CubicInterpolate(from, to, pre, post, 0.0f)); // 输出: 1(起始值)
GD.Print(Mathf.CubicInterpolate(from, to, pre, post, 0.5f)); // 输出: 约 2(曲线中段)
GD.Print(Mathf.CubicInterpolate(from, to, pre, post, 1.0f)); // 输出: 3(目标值)
}
}func _ready():
# 四个关键点:pre=0, from=1, to=3, post=2
var pre := 0.0
var from := 1.0
var to := 3.0
var post := 2.0
# 不同权重下的插值结果
print(cubic_interpolate(from, to, pre, post, 0.0)) # 输出: 1.0(起始值)
print(cubic_interpolate(from, to, pre, post, 0.5)) # 输出: 约 2.0(曲线中段)
print(cubic_interpolate(from, to, pre, post, 1.0)) # 输出: 3.0(目标值)实际场景:角色沿关键点平滑移动
using Godot;
public partial class SmoothMover : Node3D
{
// 四个路径关键点的 Y 坐标
[Export] public float ExPreY = 0.0f;
[Export] public float ExFromY = 2.0f;
[Export] public float ExToY = 5.0f;
[Export] public float ExPostY = 3.0f;
// 插值速度
[Export] public float ExSpeed = 0.5f;
private float _progress = 0.0f;
public override void _Process(double delta)
{
// 缓慢推进插值权重
_progress += (float)delta * ExSpeed;
if (_progress > 1.0f)
_progress = 0.0f; // 循环演示
// 用三次插值计算当前 Y 坐标
float y = Mathf.CubicInterpolate(
ExFromY, ExToY, ExPreY, ExPostY, _progress
);
Position = new Vector3(Position.X, y, Position.Z);
}
}extends Node3D
# 四个路径关键点的 Y 坐标
@export var ex_pre_y: float = 0.0
@export var ex_from_y: float = 2.0
@export var ex_to_y: float = 5.0
@export var ex_post_y: float = 3.0
# 插值速度
@export var ex_speed: float = 0.5
var _progress: float = 0.0
func _process(delta: float):
# 缓慢推进插值权重
_progress += delta * ex_speed
if _progress > 1.0:
_progress = 0.0 # 循环演示
# 用三次插值计算当前 Y 坐标
var y := cubic_interpolate(
ex_from_y, ex_to_y, ex_pre_y, ex_post_y, _progress
)
position = Vector3(position.x, y, position.z)实际场景:在多个关键帧之间平滑过渡
using Godot;
public partial class KeyframeAnimator : Node
{
// 一组关键帧值(比如角色的缩放大小)
private readonly float[] _keyframes = { 1.0f, 2.5f, 0.8f, 3.0f, 1.5f };
// 动画总时长
[Export] public float ExDuration = 4.0f;
private float _elapsed = 0.0f;
public override void _Process(double delta)
{
_elapsed += (float)delta;
if (_elapsed > ExDuration)
_elapsed -= ExDuration;
// 计算当前处于哪两个关键帧之间
float segmentLength = ExDuration / (_keyframes.Length - 1);
int segmentIndex = (int)Mathf.Min(
Mathf.Floor(_elapsed / segmentLength),
_keyframes.Length - 2
);
// 计算当前段的局部权重(0~1)
float localWeight = (_elapsed - segmentIndex * segmentLength) / segmentLength;
// 获取前后四个关键点
float pre = _keyframes[Mathf.Max(segmentIndex - 1, 0)];
float from = _keyframes[segmentIndex];
float to = _keyframes[segmentIndex + 1];
float post = _keyframes[Mathf.Min(segmentIndex + 2, _keyframes.Length - 1)];
// 三次插值
float value = Mathf.CubicInterpolate(from, to, pre, post, localWeight);
GD.Print($"当前值: {value:F2}");
}
}extends Node
# 一组关键帧值(比如角色的缩放大小)
var _keyframes: Array[float] = [1.0, 2.5, 0.8, 3.0, 1.5]
# 动画总时长
@export var ex_duration: float = 4.0
var _elapsed: float = 0.0
func _process(delta: float):
_elapsed += delta
if _elapsed > ex_duration:
_elapsed -= ex_duration
# 计算当前处于哪两个关键帧之间
var segment_length := ex_duration / (_keyframes.size() - 1)
var segment_index := int(minf(
floorf(_elapsed / segment_length),
_keyframes.size() - 2
))
# 计算当前段的局部权重(0~1)
var local_weight := (_elapsed - segment_index * segment_length) / segment_length
# 获取前后四个关键点
var pre := _keyframes[maxi(segment_index - 1, 0)]
var from := _keyframes[segment_index]
var to := _keyframes[segment_index + 1]
var post := _keyframes[mini(segment_index + 2, _keyframes.size() - 1)]
# 三次插值
var value := cubic_interpolate(from, to, pre, post, local_weight)
print("当前值: %.2f" % value)注意事项
weight 可以超出 0~1 范围:虽然通常在 0 到 1 之间使用,但
weight的值可以超出这个范围。超出时,曲线会继续延伸到from和to之外,可能出现"过冲"(overshoot)现象。pre 和 post 的选择很重要:如果
pre和from相同、post和to相同,三次插值会退化为线性插值。要让曲线更平滑,pre和post应该反映曲线在端点处的自然延伸方向。与
lerp()的区别:lerp()(线性插值)只能在两点之间走直线,而cubic_interpolate()可以在四个点之间走出平滑曲线。如果你的动画需要"缓入缓出"的效果,三次插值是好选择;如果只需要简单的匀速过渡,lerp()就够用了。与
cubic_interpolate_in_time()的区别:cubic_interpolate_in_time()多了一个to_t参数,可以控制插值的时间分布(类似变速效果),而本函数假设两个关键点之间的时间间隔是均匀的。对于角度值请使用专用函数:如果你的值是角度(比如 0~360 度),应该使用
cubic_interpolate_angle()而不是本函数,因为角度在 0 和 360 之间是"首尾相连"的,直接用本函数会导致在跨越 0/360 边界时出现不自然的跳变。
