〰️

AE のキーフレーム補間をスクリプトで実測・再現してみた

に公開

TL;DR

  • After Effects(AE)のキーフレームイージングは、in/out の type・speed・influence を使ってCubic Bézierに近い形で表現できそう。
  • その仮説を確かめるため、AE から in/out の speed/influence を取得し、Cubic Bézierで再計算した自作補間カーブを AE 純正の補間カーブと同じ時間軸上にベイクして比較するスクリプトを書いた。
  • 結果、かなり一致(差分 ≒ 0 付近)することを確認。

    赤(X軸): 純正カーブと自作カーブ
    緑(Y軸): 差分(自作 − 純正)

背景と狙い

AE のグラフエディターでは Speed(速度) と Influence(影響度) でイージングを作れますが、内部の厳密な数学モデルは公式に式としては公開されていません。
そこで、AE から実際のキーフレームの in/out イージング情報を取得し、自作の Cubic Bézier 補間で再現した結果とAE 純正の補間を同一フレームで比較して、挙動を検証しました。

検証

検証の考え方(実装要点)

  • 取得
    • keyOutTemporalEase(k)keyInTemporalEase(k+1) で out/in の speedinfluence を取得
    • (今回は検証を BEZIER 前提にしたため key*InterpolationType() は参照のみ)
  • 再現(Cubic Bézier)
    • 時間軸のパラメータ化:
      p0x=0, p3x=1, p1x=outInfluence/100, p2x=1-inInfluence/100
      → X(t) を ニュートン法で反転して t_bez を求める
    • 値軸の制御点:
      p0y=v0, p3y=v1, p1y=v0 + (outSpeed * dt) * (outInfluence/100), p2y=v1 - (inSpeed * dt) * (inInfluence/100)
    • y = cubic_bezier(p0y, p1y, p2y, p3y, t_bez) を計算
  • 可視化
    • 既存の “result” レイヤーの Position に
      X = 自作補間の値, Y = (自作 − 純正) をフルフレームでベイク
    • グラフエディターで 赤(X=自作) と 緑(Y=差分) を表示
    • 緑が 0 付近に張り付けば一致している=再現できている

検証に使ったスクリプト(全文:ExtendScript)

使い方

  1. 比較したいレイヤーを選択し、Position の X(ADBE Position_0)にベジェでキーフレームを打つ
  2. 同一コンポに “result” というレイヤーを用意(Null などで OK)
  3. スクリプトを実行
  4. グラフエディターで X: 自作カーブ(赤)/Y: 差分(緑) を確認
(function() {
    app.beginUndoGroup("AE Native Easing Full Reproduce");

    var comp = app.project.activeItem;
    if (!(comp instanceof CompItem)) {
        alert("コンポジションを開いてください。");
        return;
    }

    var srcLayer = comp.selectedLayers[0];
    if (!srcLayer) {
        alert("対象のレイヤーを1つ選択してください。");
        return;
    }

    // Position の X チャンネル(ADBE Position_0)を対象
    var prop = srcLayer.property("ADBE Transform Group").property("ADBE Position_0");
    var keyCount = prop.numKeys;
    if (keyCount < 2) {
        alert("Position プロパティにキーフレームを2つ以上設定してください。");
        return;
    }

    var dstLayer = comp.layer("result");
    if (!dstLayer) {
        alert('「result」レイヤーが存在しません。あらかじめ作成してください。');
        return;
    }
    var dstProp = dstLayer.property("ADBE Transform Group").property("ADBE Position");

    function interpolate(k0, k1, t) {
        var dt = k1.t - k0.t;
        if (dt <= 0.0) return k0.v;
        var localT = (t - k0.t) / dt;

        // 時間軸(0..1)のベジェ制御点:influence を横方向に割り当て
        var p0x = 0.0, p3x = 1.0;
        var p1x = k0.influence / 100.0;
        var p2x = 1.0 - k1.influence / 100.0;

        // 値軸のベジェ制御点:speed × influence × dt を縦方向に反映
        var p0y = k0.v, p3y = k1.v;
        var p1y = k0.v + (k0.speed * dt) * (k0.influence / 100.0);
        var p2y = k1.v - (k1.speed * dt) * (k1.influence / 100.0);

        // X(t)=localT を満たす t をニュートン法で解き、時間の歪みを求める
        var guess = localT;
        for (var i = 0; i < 5; ++i) {
            var bez_x = cubic_bezier(p0x, p1x, p2x, p3x, guess);
            var bez_dx = 3 * (1 - guess) * (1 - guess) * (p1x - p0x)
                       + 6 * (1 - guess) * guess * (p2x - p1x)
                       + 3 * guess * guess * (p3x - p2x);
            if (bez_dx === 0.0) break;
            guess -= (bez_x - localT) / bez_dx;
            if (guess < 0) guess = 0;
            if (guess > 1) guess = 1;
        }
        var t_bez = guess;

        return cubic_bezier(p0y, p1y, p2y, p3y, t_bez);
    }

    function cubic_bezier(p0, p1, p2, p3, t) {
        var u = 1.0 - t;
        return u*u*u*p0 + 3*u*u*t*p1 + 3*u*t*t*p2 + t*t*t*p3;
    }

    var frameDur = 1 / comp.frameRate;

    for (var k = 1; k < keyCount; k++) {
        var t0 = prop.keyTime(k),  t1 = prop.keyTime(k + 1);
        var v0 = prop.keyValue(k), v1 = prop.keyValue(k + 1);

        // 今回は BEZIER 前提で検証(type は未使用)
        // var outType = prop.keyOutInterpolationType(k);
        // var inType  = prop.keyInInterpolationType(k + 1);

        var outEase = prop.keyOutTemporalEase(k)[0];
        var inEase  = prop.keyInTemporalEase(k + 1)[0];

        for (var t = t0; t <= t1 + 1e-8; t += frameDur) {
            var v = interpolate(
                { t: t0, v: v0, speed: outEase.speed, influence: outEase.influence },
                { t: t1, v: v1, speed: inEase.speed,  influence: inEase.influence  },
                t
            );
            var nativeValue = prop.valueAtTime(t, true);

            // result レイヤーの Position にベイク:
            // X = 自作(再現)値、Y = 差分(自作 - 純正)
            dstProp.setValueAtTime(t, [v, v - nativeValue]);
        }
    }

    // 見やすさのため、補間を HOLD に
    for (var i = 1; i <= dstProp.numKeys; i++) {
        dstProp.setInterpolationTypeAtKey(i, KeyframeInterpolationType.HOLD, KeyframeInterpolationType.HOLD);
    }

    app.endUndoGroup();
})();

結果(再掲)

  • 赤(X軸): AEのカーブとフルフレームベイクした再現カーブ
  • 緑(Y軸): 差分(自作 − 純正)。0 付近に張り付く=再現精度が高い証拠
  • 画面右端まで概ね 0 に収束しており、AE 純正の補間にかなり近いことが分かります

所感・注意点

  • Easy Ease 系を含む一般的なベジェイージングは、上記の扱いで十分に再現できました。
  • Linear / Hold / Spatial 補間は本検証の対象外(Temporal / BEZIER 前提)。必要に応じて条件分岐で扱ってください。
  • サブフレーム精度や 内部の丸めの違いにより、ごく小さな差が出る可能性はあります。
  • 位置以外のプロパティ(回転・スケール・エフェクト値等)でも、1D として同じ要領で再現できます。

参考リンク

Discussion