〰️
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 のspeed
とinfluence
を取得 - (今回は検証を 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 付近に張り付けば一致している=再現できている
- 既存の “result” レイヤーの Position に
検証に使ったスクリプト(全文:ExtendScript)
使い方
- 比較したいレイヤーを選択し、Position の X(ADBE Position_0)にベジェでキーフレームを打つ
- 同一コンポに “result” というレイヤーを用意(Null などで OK)
- スクリプトを実行
- グラフエディターで 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 として同じ要領で再現できます。
参考リンク
- AE Scripting Docs: Property
https://ae-scripting.docsforadobe.dev/property/property/ - AE Scripting Docs: KeyframeEase
https://ae-scripting.docsforadobe.dev/other/keyframeease/ - AE のイージング情報エクスポート
https://github.com/funatsufumiya/ofxAEEasingLoader/blob/main/tools/exportSelectedEasing.jsx - Adobe Help: キーフレーム補間
https://helpx.adobe.com/jp/after-effects/using/keyframe-interpolation.html - Adobe Help: 速度グラフ(speed/influence の考え方)
https://helpx.adobe.com/after-effects/using/speed.html
Discussion