Expression Tactics
【3】
点滅、リピートを作る

 エクスプレッションが得意とするモーションのひとつに「繰り返し」があります。今回は「点滅」処理を例にとってエクスプレッションの使いどころ、長所を検証してみようと思います。

 とりあえず手始めに、キーフレーム操作で「レイヤー透明度」を制御し回転灯の点滅を作ってみましょう。


 なんてことはない、0% - 100% - 0%の繰り返しをキーフレームにて実現したものです。今回は6秒分ですが、必要に応じてキーフレームの「コピペ」を繰り返す事になります。

 では、この点滅モーションをエクスプレッションで作ってみます。やっぱり、前回同様、簡単な1行スクリプトです。1つもキーフレームを打つ事なく、点滅のモーションを作る事が可能です。

Math.abs(100-((time+0.5)%1*200));


 絶対値を利用して、1秒間に0-100を1往復するモーションを実現してみました。さらに別タイプで「if分岐」を使ったモーションを作って見ましょう。


if(time%1<0.5){time%0.5*200;}else{100-(time%0.5*200);}

 「もし現在時間の端数が0.5未満だったら増加し、そうでなければ減衰する」と言う「もし」を使ったエクスプレッションです。‥‥私の好みとしては「もし」を使わないほうが好みではありますが、数式以外の要素を使えるのもエクスプレッションの強みですので、紹介してみました。

 どちらのエクスプレッションも1行書き込むだけで、大量のキーフレーム管理とは無縁となります。しかしこの程度で終わらないのが、エクスプレッションの魅力です。アナログシンセサイザーの波形選択のように、色々なモーション〜0%から100%への往復パターンを作ってみましょう。

 ではまず、こんなモーション。サイン関数を利用して、下図のような往復パターンを作ってみました。



 円運動の縦軸のみを取りだした(円形運動から線形運動への変換)「両端ヅメ」のモーションです。サイン関数に円周率をかけて、1秒間区切りのモーションを作り出しています。

 同じサイン関数を使って、下図のようなモーションも作る事ができます。



 アーチ状のユニークなモーションですね。「ポーン、ポーン」とボールがバウンドするようなモーションとなります。

 さらに断続的なモーションを作って見ましょう。中間値を含まないON/OFFのみのモーションです。



 矩形のモーション、例えば、車のウィンカーのようなモーションです。こうして試しに作ってみると、点滅モーションと言っても色々ありますね。実際の作品中のカットにおいては、演出オーダーに合わせて自在に操るスキルが必要となりますので、「たかが点滅」と言っても侮れないのです。

 バリエーションの最後に、もう1つ作って見ましょう。ノコギリ状のモーションです。



 「ピコン‥‥、ピコン‥‥」と言う感じのモーションです。単なるノコギリ状ではなく、ノコギリの山の間に隙間があるのがポイントです。

 最初の「三角」から数えて、以上で5つのモーションを作ってみました。「三角」「サイン」「アーチ」「矩形」「ノコギリ」を全部並べて比較してみましょう。


 これら5つのモーションの特徴は何と言っても「簡単な数式だけ」で作られている事です。例えばノコギリ状のモーションを作ろうとして「ノコギリ波」をWebで検索してみれば、「フーリエ級数展開」だの「フーリエ級数近似式」だの、難しげな単語が山ほど出てきます。しかしながら、少なくともAfter Effectsにおいては、ノコギリ状のカタチを作る為にわざわざ「フーリエ級数展開」にご登場願わなくても、もっと簡単で簡素、短い式で作成可能です。「100-((time+0.5)%1)*200」に見られる通り、「割り算の余り」と「かけ算」「足し算」「引き算」と言った小学校レベルの数式だけで、奇麗なノコギリ波が作れるのです。「プログラムは難解だ」と言う状況とはまるで異なる事がお解りでしょう。

 さて、ここまでとんとん拍子に作れたのは、1秒で1サイクルと言う簡潔なモーションだったから‥‥とも言えます。実際には必ずしも1秒に1サイクルのモーション・0.5秒で明るくなって0.5秒で暗くなるという条件ばかりではありません。

 そこで、まず1秒間を均等に2つに分けたモーションから、不均等・アンシンメトリーなモーションに作り替えてみます。「if分岐」を使ったエクスプレッションを改造して、左右対称の三角から非対称の三角に変えます。

 まずオリジナルの文がこれです。

if(time%1<0.5){time%0.5*200;}else{100-(time%0.5*200);}

 時間を0.5で区切っている、つまり1(秒)を半分に分割しているゆえに、左右対称の三角が形成されているので、このバランスを崩し、非対称へと作り替えます。エクスプレッションの文を書き始める前に、「非対称三角形の要素」を整理して考えてみましょう。

 頂点の位置が0〜1の間で動くと、三角形の山の形も変動します。と言う事は、三角形頂点の時間位置をずらせるように改造すれば、三角形の形を制御できそうです。頂点の時間位置を「peak」と名付けた変数で扱い、エクスプレッションに組み込んでみましょう。peakの値は、ひとまず0.5に設定しておきます。

peak=0.5;
if(time%1<peak){time%1*(100/peak);}else{(100-time%1*100)/((1-peak)/1);}

 なぜこのような式になるかは、よく考えてみてくださいネ。斜め読みせずに、文を頭から読んで見てください。難しい要素は1つもありませんので、物怖じせずに読み進めば理解できます。

 では試しに、peakの値を0.1にしてみましょう。

peak=0.1;
if(time%1<peak){time%1*(100/peak);}else{(100-time%1*100)/((1-peak)/1);}


 ちゃんと思った通りのカタチになっています。さらに今度は、peakの値を0.8にしてみましょう。

peak=0.8;
if(time%1<peak){time%1*(100/peak);}else{(100-time%1*100)/((1-peak)/1);}


 大成功ですね。peakに設定する値の変更で、自分の好きな三角形を作る事が可能となりました。何度も繰り返しますが、このエクスプレッションの肝は「簡単な数式と文だけで作られている」事です。高等数学をわざわざ持ち出さなくても、自分の思い通りに作れるのだという事を認識してください。また何行にも渡る難解なプログラム文でもありません。たった2行です。

 さて、ここまでできると、もうちょっとカスタムしてみようと欲が出てきます。山の「角(つの)」の形はpeakで制御可能となりました。しかしどんな状況でも必ず「1秒サイクル」とは限りません。山の「すそ野」の広さ、すなわちサイクルの長さも制御できるように改造しましょう。「length」と名付けた変数で制御します。

peak=0.5;
length=1
;
if(time%length<peak){time%length*(100/peak);}
else{(100-time%length*(100/length))/((length-peak)/length);}

 lengthと言う変数を新たに設置し組み込みました。読み辛いのでelseの前に一度改行を入れてあります。さて、結果は?


 特に問題は無さそうですね。peakとlengthの値を色々変えてみましょう。


 どの場合においても、良さそうですね。‥‥と油断していると、次のような目に遭います。


 実はpeakの値を0にすると、「100を0で割る」事になり、エラーが発生します。「100をゼロ等分する」‥‥ゼロは無‥‥の訳だから、何かを「無」で割る事はできるんか???‥‥と言う事でエラーが出る訳です。これを避ける為の簡単な方法が、やっぱりif分岐です。安易だけど簡単ですね。以下のようになります。

peak=0;
length=1;
if(peak==0){peakX=1;}else{peakX=100/peak;}
if(time%length<peak){time%length*peakX;}
else{(100-time%length*(100/length))/((length-peak)/length);}

 peakにゼロが指定された場合は、何もしない‥‥つまり「1をかける」=「等倍」にするようにif分岐を組みます。パッチを当てたような対処方ですが、とりあえずうまくいきました。下図のように右下がりのノコギリ状モーションが出来ました。


 もうこれで充分だ‥‥と思いますが、考えてみれば「山の高さは変えられないのかな?」と気がつきます。0から100の間で往復するだけではなく、20から80だったり、30から40だったり、三角形の背の高さは場面によって異なる事が多いでしょう。明滅の範囲を調節できるようにしたくなるのが、自然の成り行きです。‥‥では、そうしましょう。

 最大値をMax、最小値をMinと名付けて、エクプレッションに組み込みます。

peak=0.5;//最明部の時間〜ユーザ設定はこの行から
length=1;//全体の長さ
Max=100;//最大値
Min=0;//最小値〜ユーザ設定はこの行まで

Ratio=(Max-Min)/100;
if(peak==0){peakX=1;}else{peakX=100/peak;}
if(time%length<peak){time%length*peakX*Ratio+Min;}
else{(100-time%length*(100/length))/((length-peak)/length)*Ratio+Min;}

 peak、lengthに加えて、Max、Minと言う2つの設定値を設置しました。文中の「//」に続く日本語はメモ書きです。エクスプレッションに用いられるJavaScriptは行内の//以降の文字を命令文として無視する仕組みを持っているので、「//」で区切った後に「忘れないようにメモを書いておく」ような使い方ができるのです。

 さて、結果は以下の通りになりました。


 問題は発生していないようなので、色々な値を入れてみます。


 かなり自由自在に三角形を制御できるようになりましたね。もうこれで充分でしょう。

 ‥‥と終わるのはまだ早いですし、少々軽率です。明滅のオーダーが必ず三角形や矩形であるとは限りません。例えば、下図のようなエンベロープを要求する演出オーダーだってあり得ます。

 アナログシンセサイザーで言うところの「ADSR」、つまりアタック・ディケイ・サスティン・リリースの全てを制御できなければ点滅を制御できるとは言えません。現在の仕様では「AとDのみ」、つまりアタックとディケイのみの単純な制御しか出来ません。

 では「ADSR」を全てエクスプレッションで制御する‥‥?

 ‥‥出来ます。確かに出来ますが、エクスプレッションの優位性を阻害する結果、つまり「エクスプレッションの作成が大変で取り扱いも難しくなり、結局、手作業でキーフレームをコピペしたほうが手間も少なく管理も楽」と言う結果が待っているように思えます。「家計を軽減しようとして太陽光発電を導入したら、その維持費で家計が火の車になった」と言うような、本末転倒そのものです。

 エクスプレッションを使う事は、「キーフレーム以外の制御方法」をも得る事‥‥とも言えますが、これはつまり「複数の手段を得る」事であって、「何でもかんでもエクスプレッションでやる」と言う事ではありません。「複数を使い分ける」事が重要なのです。

 振りかえって見れば、After Effectsは「ADSR」を簡単に制御できる、何とも素晴らしい「キーフレーム」と言う機能を持っています。これを使わない手はありません。つまり、「ADSR」のコントロールはキーフレームでおこない、リピートする部分をエクスプレッションでおこなえば、かなり効率的に作業が進みそうです。

 では、やってみましょう。

 

 上図のように、非対称台形を形成するキーフレームを打ちます。実はこれで作業の90%が終わったようなもので、後は簡単なエクスプレッションを1行追加します。

loopOutDuration("cycle",1);


*キャプチャ画像のエクスプレッション文が上の例文と少々異なりますが、内容は一緒です。


 これでおしまい。グラフを見てみましょう。

 ループ命令でクローンされた台形のエンベロープが点線で示されています。‥‥なんだか気が抜けるほど簡単な方法で点滅モーションが実現してしまいました。

 「なんで前の例文も、全てこの方法で解説しなかったのか?」と言われそうですが、私がここで書きたいのは、「いくつもの方法」です。エクスプレッションだけでも点滅は作れるし、キーフレームと協調して作る事も出来る‥‥と言う事を解説したかったのです。つまり、1周期分のキーフレームをループで埋める方法「だけ」を解説したくなかったのです。

 しかしまだ、この方法には欠点があります。「オフセット」がある、つまり「周期の先頭部分が、必ずしもレイヤーインポイントと一致しない場合」には、loopOutでは対処できなくなります。


 途中からキーフレームが打たれている場合、その前の部分をループによるクローンで埋めてくれません。インポイントをずらしたり、プリコンポーズしたりして対処は可能ですが、ここは1つ、前後をクローンで埋めるタイプのループを作ってみます。

keyLoop(1,6,false);

function keyLoop(v1,v2,v3)//H.Ezura//2007.04.15
//パラメータ1:ループ開始点キーフレームindex番号
//パラメータ2:ループ終了点キーフレームindex番号
//パラメータ3:ループ範囲内にループ終了点キーフレームを含むか否かの真偽値
{
f0=timeToFrames(t = time + thisComp.displayStartTime,
fps = 1.0 / thisComp.frameDuration, isDuration = false);
if(v3){addF=1}else{addF=0};
f1=timeToFrames(key(v1).time, fps, isDuration);
f2=timeToFrames(key(v2).time, fps, isDuration)+addF;
loopFrames=f2-f1;
offset=f1%(loopFrames);
return valueAtTime(framesToTime((f0-offset+loopFrames)%loopFrames+f1, fps));
//loopFramesを1回足しているのはマイナス値発生防止の為。
}


 この「function(){}」と言う書き方はエクスプレッションの醍醐味の1つで、「ユーザ自身が作ったルーチン(処理装置)」を使う際の書き方です。「keyLoop」と言う名前は作成者である私が(勝手に)付けた名前ですが、その名をエクスプレッション文中で呼び出す事で、自作の「処理装置」を使う事ができます。実際に使うと下図のように、前後をループによるクローンで埋めた状態が出来上がります。


*図のグラフは「ガビガビ」にジャギっていますが、実際はちゃんと滑らかに動きます。


 「キーフレーム(1)からキーフレーム(6)をループ範囲とし、終了キーフレーム自身のフレームデュレーションを含まずにループ」と言う内容です。functionのパラメータ、つまり処理装置の設定値は、設定方法も含め、全て自分で作る事ができますから、自分好みの処理装置を作る事が可能となります。「どこかの誰かが作ってくれたプラグインやアニメーションプリセットを探しまわったり、誰かが作ってくれるのを待つ」事なく、自分でグリグリ作る事が可能な訳です。

 いきなり「function」などが出てきて面食らった人もいるかも知れませんが、その中身はいつものように「算数」レベルの「足し算」「引き算」「かけ算」「割り算」が主で、After Effects独自の「timeToFrames()」などがちょこっと出てくるだけですから、落ち着いて読み進めば恐るるに足りません。ちなみに「timeToFrames()」は、「時間をフレーム数に変換する」という内容のもので、私の「keyLoop()」においては、ループ範囲を一旦フレーム数で把握し直してから処理をしています。‥‥こうしたやり方も全て自分のやりやすいように作れるのが、functionの強みです。

 キーフレームによるエンベロープ作成と、ループによるクローン作成で、点滅が思いのままとなりました。下図のような複雑な形のエンベロープもリピート可能ですから、パトライトや航空障害灯、携帯電話の着信など、ちまたで目にする点滅モーションならば、いとも簡単に作り出せます。


 ‥‥と言う事で、めでたし、めでたし。私の「keyLoop」は透明度だけでなく、位置座標にも、フィルタのパラメータにも、タイムリマップにも使えるよう作ってあるので、色々な場面で活用可能です。私はギターを弾きますが、「keyLoop」は言うなればAfter Effects上の「自作のエフェクター」のような感じですね。

 エクスプレッションに興味が湧いてきたら、functionを使って、自分だけの「処理装置」を作ってみては如何でしょうか。


 数式やプログラムと聞くと、「理数系」「高度な知識」と言った印象を受けるかも知れませんが、何の事は無い、動きのアイデアを「たまたま」身近な算術や簡単な「英文もどき」で表現するだけの話です。他人に地図を描いて説明したり、旅の予算を割り勘で計算するくらいの能力があれば、既にエクスプレッションを作る潜在能力がある証なのです。

 とは言っても、functionについては、さすがに初見では理解し辛いと思いますので、後のコラムにてもう少し細かく解説します。別に難しい構造ではないですから、物怖じする必要はありません。

 「自作の処理装置」をエクスプレッションでちゃちゃっと作れるようになると、モーションをAfter Effectsのプリセットに合わす事なく自身の発想と都合で制御する事が可能になるので、表現の幅を「誰かのプリセット」に合わせて制限するような事が少なくなります。

 私はこうしたユーザファンクションをいくつも作り溜めており、ffx(アニメーションプリセットファイル)により瞬時に呼び出せるよう、環境を整えています。ニュアンスにつぐニュアンスを表現する際に、独自のファンクション=処理装置は、大変心強い武器となってくれるのです。(もちろん、作業速度重視の仕事にも役立ちますが‥‥)


注意1)この解説ページに掲載してあるGIFアニメ画像の再生時間軸は厳正ではありません。GIFアニメはファイルフォーマット自体が厳正な時間軸を保証していないので、再生する環境により再生速度が変わります。

注意2)ここで紹介したスクリプトを実際に使用する際は、使用者の責任において使用してください。


 【2】<■>【4】

INDEX


IntegerQ INDEX
h.ezura / afx / ezqnet