【问题标题】:Interpolating values between interval, interpolation as per Bezier curve区间内插值,按照贝塞尔曲线插值
【发布时间】:2011-05-04 12:21:02
【问题描述】:

为了实现 2D 动画,我正在寻找两个关键帧之间的插值值,其变化速度由贝塞尔曲线定义。问题是贝塞尔曲线以参数形式表示,而要求是能够评估特定时间的值。

更详细地说,假设 10 和 40 的值将在 4 秒内进行插值,其值不会不断变化,而是由表示为 0,0 0.2,0.3 0.5,0.5 1,1 的贝塞尔曲线定义。 现在,如果我以每秒 24 帧的速度绘图,我需要评估每一帧的值。我怎样才能做到这一点 ?我查看了 De Casteljau 算法,并认为将曲线分成 24*4 块持续 4 秒可以解决我的问题,但这听起来是错误的,因为时间是沿着“x”轴而不是沿着曲线。

进一步简化 如果我在平面上绘制曲线,x 轴代表时间,y 轴代表我正在寻找的值。我真正需要的是能够找出对应于“x”的“y”。然后我可以将 x 分成 24 个部分,并知道每一帧的值

【问题讨论】:

  • 这有帮助吗:gamedev.net/topic/… ?
  • 我看过这篇文章和很多其他文章,每个人似乎都有自己的解决方案。我认为这个问题应该有一个标准的解决方案,因为它是如此普遍的问题。
  • 我检查了我用于 CG 课程的书中的几章,有很多关于样条曲线的信息,但不幸的是没有提到确定曲线长度的标准方法。
  • 贝塞尔曲线可能不是您在这种情况下应该使用的。任意贝塞尔曲线不是函数,因为它可以在自身上循环——为给定的 x 提供多个 y 值。你想要的是一个函数,就 x 和 y(没有 t)而言。

标签: math animation core-animation bezier


【解决方案1】:

我遇到了同样的问题:那里的每个动画包似乎都使用贝塞尔曲线来控制一段时间内的值,但是没有关于如何将贝塞尔曲线实现为 y(x) 函数的信息。所以这就是我想出的。

二维空间中的标准三次贝塞尔曲线可以定义为四个点P0=(x0, y0) .. P3=(x3, y3).
P0 和 P3 是曲线的终点,而 P1 和 P2 是手柄影响其形状。使用参数 t ϵ [0, 1],然后可以使用方程确定沿曲线的任何给定点的 x 和 y 坐标
A) x = (1-t)3x0 + 3t(1-t)2x1 + 3t2(1-t)x2 + t3x3
B) y = (1-t)3y0 + 3t(1-t)2y1 + 3t2(1-t)y2 + t3y3 .

我们想要的是一个函数 y(x),给定一个 x 坐标,它将返回曲线的相应 y 坐标。为此,曲线必须从左到右单调移动,这样它就不会在不同的 y 位置上多次占据相同的 x 坐标。确保这一点的最简单方法是限制输入点,使 x03x1, x2 ε [x0, x3]。换句话说,P0 必须在 P3 的左侧,两个句柄之间。

为了计算给定 x 的 y,我们必须首先从 x 确定 t。从 t 得到 y 就是将 t 应用于方程 B 的简单问题。

我看到了两种方法来确定给定 y 的 t。

首先,您可以尝试对 t 进行二分搜索。从下限 0 和上限 1 开始,通过方程 A 计算 t 的这些值的 x。继续平分区间,直到获得合理接近的近似值。虽然这应该可以正常工作,但它既不会特别快也不会非常精确(至少不能同时使用两者)。

第二种方法是实际求解方程 A 的 t。这有点难以实现,因为方程是三次的。另一方面,计算变得非常快速并产生精确的结果。

方程 A 可以改写为
(-x0+3x1-3x2+x3)t3 + (3x0-6x1+3x2)t2 + (-3x0+3x1)t + (x0-x) = 0.
插入 x0..x3 的实际值,我们得到 at3 形式的三次方程+ bt2 + c*t + d = 0 我们知道在 [0, 1] 内只有一个解。我们现在可以使用this Stack Overflow answer 中发布的算法来求解这个方程。

以下是一个演示这种方法的小 C# 类。将其转换为您选择的语言应该足够简单。

using System;

public class Point {
    public Point(double x, double y) {
        X = x;
        Y = y;
    }
    public double X { get; private set; }
    public double Y { get; private set; }
}

public class BezierCurve {
    public BezierCurve(Point p0, Point p1, Point p2, Point p3) {
        P0 = p0;
        P1 = p1;
        P2 = p2;
        P3 = p3;
    }

    public Point P0 { get; private set; }
    public Point P1 { get; private set; }
    public Point P2 { get; private set; }
    public Point P3 { get; private set; }

    public double? GetY(double x) {
        // Determine t
        double t;
        if (x == P0.X) {
            // Handle corner cases explicitly to prevent rounding errors
            t = 0;
        } else if (x == P3.X) {
            t = 1;
        } else {
            // Calculate t
            double a = -P0.X + 3 * P1.X - 3 * P2.X + P3.X;
            double b = 3 * P0.X - 6 * P1.X + 3 * P2.X;
            double c = -3 * P0.X + 3 * P1.X;
            double d = P0.X - x;
            double? tTemp = SolveCubic(a, b, c, d);
            if (tTemp == null) return null;
            t = tTemp.Value;
        }

        // Calculate y from t
        return Cubed(1 - t) * P0.Y
            + 3 * t * Squared(1 - t) * P1.Y
            + 3 * Squared(t) * (1 - t) * P2.Y
            + Cubed(t) * P3.Y;
    }

    // Solves the equation ax³+bx²+cx+d = 0 for x ϵ ℝ
    // and returns the first result in [0, 1] or null.
    private static double? SolveCubic(double a, double b, double c, double d) {
        if (a == 0) return SolveQuadratic(b, c, d);
        if (d == 0) return 0;

        b /= a;
        c /= a;
        d /= a;
        double q = (3.0 * c - Squared(b)) / 9.0;
        double r = (-27.0 * d + b * (9.0 * c - 2.0 * Squared(b))) / 54.0;
        double disc = Cubed(q) + Squared(r);
        double term1 = b / 3.0;

        if (disc > 0) {
            double s = r + Math.Sqrt(disc);
            s = (s < 0) ? -CubicRoot(-s) : CubicRoot(s);
            double t = r - Math.Sqrt(disc);
            t = (t < 0) ? -CubicRoot(-t) : CubicRoot(t);

            double result = -term1 + s + t;
            if (result >= 0 && result <= 1) return result;
        } else if (disc == 0) {
            double r13 = (r < 0) ? -CubicRoot(-r) : CubicRoot(r);

            double result = -term1 + 2.0 * r13;
            if (result >= 0 && result <= 1) return result;

            result = -(r13 + term1);
            if (result >= 0 && result <= 1) return result;
        } else {
            q = -q;
            double dum1 = q * q * q;
            dum1 = Math.Acos(r / Math.Sqrt(dum1));
            double r13 = 2.0 * Math.Sqrt(q);

            double result = -term1 + r13 * Math.Cos(dum1 / 3.0);
            if (result >= 0 && result <= 1) return result;

            result = -term1 + r13 * Math.Cos((dum1 + 2.0 * Math.PI) / 3.0);
            if (result >= 0 && result <= 1) return result;

            result = -term1 + r13 * Math.Cos((dum1 + 4.0 * Math.PI) / 3.0);
            if (result >= 0 && result <= 1) return result;
        }

        return null;
    }

    // Solves the equation ax² + bx + c = 0 for x ϵ ℝ
    // and returns the first result in [0, 1] or null.
    private static double? SolveQuadratic(double a, double b, double c) {
        double result = (-b + Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);
        if (result >= 0 && result <= 1) return result;

        result = (-b - Math.Sqrt(Squared(b) - 4 * a * c)) / (2 * a);
        if (result >= 0 && result <= 1) return result;

        return null;
    }

    private static double Squared(double f) { return f * f; }

    private static double Cubed(double f) { return f * f * f; }

    private static double CubicRoot(double f) { return Math.Pow(f, 1.0 / 3.0); }
}

【讨论】:

    【解决方案2】:

    你有几个选择:

    假设您的曲线函数 F(t) 接受一个范围为 0 到 1 的参数 t,其中 F(0) 是曲线的起点,F(1) 是曲线的终点。

    您可以通过以每单位时间的恒定变化递增 t 来为沿曲线的运动设置动画。 所以 t 由函数 T(time) = Constant*time 定义

    例如,如果您的帧是 1/24 秒,并且您希望以每秒 0.1 个单位 t 的速率沿曲线移动,那么您将每帧增加 0.1 (t/s) * 1/24(秒/帧)。

    这里的一个缺点是您每单位时间的实际速度或行驶距离不会是恒定的。这取决于您的控制点的位置。

    如果您想沿曲线均匀地缩放速度,您可以修改每单位时间 t 的恒定变化。但是,如果您希望速度发生显着变化,您会发现很难控制曲线的形状。如果您希望一个端点处的速度更大,则必须将控制点移得更远,从而将曲线的形状拉向该点。如果这是一个问题,您可以考虑对 t 使用非常数函数。有多种方法需要权衡取舍,我们需要了解有关您的问题的更多详细信息以提出解决方案。例如,过去我允许用户定义每个关键帧的速度,并使用查找表将时间转换为参数 t,这样关键帧速度之间的速度就会线性变化(这很复杂)。

    另一个常见的问题:如果您通过连接多条 Bezier 曲线来制作动画,并且希望在曲线之间移动时速度是连续的,那么您需要约束您的控制点,使其与相邻曲线对称。 Catmull-Rom 样条线是一种常用的方法。

    【讨论】:

      【解决方案3】:

      我有answered a similar question here。基本上,如果您事先知道控制点,那么您可以将 f(t) 函数转换为 y(x) 函数。为了不必全部手动完成,您可以使用 Wolfram Alpha 等服务来帮助您进行数学运算。

      【讨论】:

        猜你喜欢
        • 2018-03-10
        • 2011-03-10
        • 2011-12-06
        • 1970-01-01
        • 1970-01-01
        • 2013-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多