第14回 アニメーション

今回はテキスト4-1, 4-2(104ページ~)のアニメーションについての実践を行う。キーフレームを使った補間、カメラワークによる見え方の違いを調べる。
オンラインで受講でPCにProcessingが入っていない場合は、まず Processing をダウンロードしてインストールしてから以下の課題を行う。

課題 1 (キーフレームの線形補間)

ランダムに4つ設定したキーフレームの間を1次式を使って補間した場合の見え方を確認する。
  1. Processingを起動する。
  2. 以下のコードをProcessingのエディタにコピー&ペーストし、「ex14_1」という名前で保存する。
  3. (補間の計算を行っているのは関数「ip」。この関数が受け取る引数 r の値は0~3の間で変化する。それが0~1, 1~2, 2~3の場合に、それぞれ0~1, 1~2, 2~3番目のキーフレームの間で補間した値を計算して返す)
    int count=0;
    // キーフレームでの位置・大きさ・色
    float[] keyX = new float[4];
    float[] keyY = new float[4];
    float[] keyD = new float[4];
    float[] keyCr = new float[4];
    float[] keyCg = new float[4];
    float[] keyCb = new float[4];
    // 各フレームでの位置・大きさ・色
    PVector[] p = new PVector[91];
    float[] d = new float[91];
    color[] c = new color[91];
    
    void setup() {
      size(600, 400);
      // キーフレームでの値をランダムに決める
      for (int i=0; i<4; i++) {
        keyX[i] = random(20, width-20);
        keyY[i] = random(20, height-20);
        keyD[i] = random(20, 80);
        keyCr[i] = random(256);
        keyCg[i] = random(256);
        keyCb[i] = random(256);
      }
      // それ以外のフレームでの値を補間
      for (int i=0; i<=90; i++) {
        float r = i/30.0;
        p[i] = new PVector(ip(keyX, r), ip(keyY, r));
        d[i] = ip(keyD, r);
        c[i] = color(ip(keyCr, r),ip(keyCg, r),ip(keyCb, r));
      }
    }
    
    // 線形補間
    float ip(float[] key, float r) {
      if (r<1) {
        return key[0]*(1-r)+key[1]*r;
      } else if (r<2){
        return key[1]*(1-(r-1))+key[2]*(r-1);
      } else {
        return key[2]*(1-(r-2))+key[3]*(r-2);
      }
    }
    
    void draw() {
      // キーフレームの円(枠)を表示
      background(0);
      noFill();
      for (int i=0; i<4; i++) {
        stroke(keyCr[i], keyCg[i], keyCb[i]);
        ellipse(keyX[i], keyY[i], keyD[i], keyD[i]);
      }
      // 補間した円を表示
      fill(c[count]);
      noStroke();
      ellipse(p[count].x, p[count].y, d[count], d[count]);
      save("課題1/"+nf(count, 2));
      if (count>=90) {
        exit();
      }
      count++;
    }

  4. プログラムを実行する。
  5. (図のように4つの円の枠の間を円がまっすぐ動く。色もその枠の色に合わせてだんだん変わる)
    (4つ目の円のところまで動くと自動的にプログラムが終了する)


  6. 上のメニューから「スケッチ」→「スケッチフォルダーを開く」を選び、出てきたウインドウで「課題1」をダブルクリックする。
    すると、実行結果のそれぞれのフレームと同じ画像00.tif~90.tifの91枚の画像ができていることがわかる。
    (課題6まで進んだら、これらの画像を使って動画を作って提出する。これらの静止画自体は提出しない)
    • 次に進む前にプログラムを上書き保存する。

課題 2 (キーフレームの多項式による補間)

キーフレームのところで急激に動きが変わらないように、3次式で途中のフレームでの値を計算する。
3次関数を \(x=at^3+bt^2+ct+d\) という形で表わすと、\(t\) の値が0, 1, 2, 3のときのキーフレームの値 \(x(0), x(1), x(2), x(3)\) は
\( \begin{eqnarray} x(0)&=&d\cr x(1)&=&a+b+c+d\cr x(2)&=&8a+4b+2c+d\cr x(3)&=&27a+9b+3c+d \end{eqnarray} \)

のようになる。これを行列の形で以下のように書くこともできる。
\( \begin{pmatrix} x(0) \\ x(1) \\ x(2) \\ x(3) \end{pmatrix} = \begin{pmatrix} 0 & 0 & 0 & 1 \\ 1 & 1 & 1 & 1 \\ 8 & 4 & 2 & 1 \\ 27 & 9 & 3 & 1 \end{pmatrix} \begin{pmatrix} a \\ b \\ c \\ d \end{pmatrix} \)

両辺にこの4×4行列の逆行列を左側からかけると
\( \begin{pmatrix} -1/6 & 1/2 & -1/2 & 1/6 \\ 1 & -5/2 & 2 & -1/2 \\ -11/6 & 3 & -3/2 & 1/3 \\ 1 & 0 & 0 & 0 \end{pmatrix} \begin{pmatrix} x(0) \\ x(1) \\ x(2) \\ x(3) \end{pmatrix} = \begin{pmatrix} a \\ b \\ c \\ d \end{pmatrix} \)

つまり \(a, b, c, d\) は以下のように求められる。これらを係数とした3次式で一般の \(t\) での \(x\) の値を計算すれば、滑らかに変化する値になる。
\( \begin{eqnarray} a&=&-\frac{1}{6}x(0)+\frac{1}{2}x(1)-\frac{1}{2}x(2)+\frac{1}{6}x(3)\cr b&=&x(0)-\frac{5}{2}x(1)+2x(2)-\frac{1}{2}x(3)\cr c&=&-\frac{11}{6}x(0)+3x(1)-\frac{3}{2}x(2)+\frac{1}{3}x(3)\cr d&=&x(0) \end{eqnarray} \)
  1. Processingのメニューから「ファイル」→「名前をつけて保存」を選び、「ex14_2」という名前で保存する。
  2. draw関数の中のファイル保存先の部分を「課題1」→「課題2」に変更する。
  3. ip関数を削除し、その部分に以下のコードをコピー&ペーストして実行する。
  4. (これは上記の方法で求めた係数を使った3次関数による補間値を返す関数)
    // 3次関数による補間
    float ip(float[] key, float r) {
      float[][] mt ={
        {-1.0/6, 0.5, -0.5, 1.0/6}, 
        {1, -2.5, 2, -0.5}, 
        {-11.0/6, 3, -1.5, 1.0/3}, 
        {1, 0, 0, 0}
      };
      float a=key[0]*mt[0][0]+key[1]*mt[0][1]+key[2]*mt[0][2]+key[3]*mt[0][3];
      float b=key[0]*mt[1][0]+key[1]*mt[1][1]+key[2]*mt[1][2]+key[3]*mt[1][3];
      float c=key[0]*mt[2][0]+key[1]*mt[2][1]+key[2]*mt[2][2]+key[3]*mt[2][3];
      float d=key[0]*mt[3][0]+key[1]*mt[3][1]+key[2]*mt[3][2]+key[3]*mt[3][3];
      return a*r*r*r+b*r*r+c*r+d;
    }
    円は課題1のときのように2, 3番目の円のところでカクっと曲がらず、滑らかに動く。
    (このプログラムのフォルダには新しく「課題2」フォルダが作成され、その中に91枚の画像ができている)

課題 3 (ズームイン)

カメラを動かさないまま画角 (カメラで写す範囲) だけを変えて、見え方の変化を確認する。
  1. Processingのメニューから「ファイル」→「名前をつけて保存」を選び、「ex14_3」という名前で保存する。
  2. コードをすべて削除し、以下のコードをコピー&ペーストして実行する。
  3. (draw関数の2行目のperspective関数の第1引数で画角を指定している。countは0→90のように変化し、それにつれて画角は95°→5°のように変わる。そのため、初めは広い範囲が写っていて、しだいに中央部分が拡大されて表示されるようになる)
    int count=0;
    // オブジェクトの位置・形・色
    PVector[] bPos = new PVector[20];
    PVector[] bSize = new PVector[20];
    color[] c = new color[20];
    
    void setup() {
      size(600, 400, P3D);
      stroke(255);
      // オブジェクトの位置・形・色をランダムに決める
      for (int i=0; i<bPos.length; i++) {
        bSize[i] = new PVector(0, 0, 0);
        bSize[i].x = random(20, 100);
        bSize[i].y = random(20, 300);
        bSize[i].z = random(20, 100);
        bPos[i] = new PVector(0, 0, 0);
        bPos[i].x = random(width);
        bPos[i].y = height-bSize[i].y/2;
        bPos[i].z = random(-3000, -100);
        c[i] = color(random(256), random(256), random(256));
      }
    }
    
    void draw() {
      background(0);
      perspective(radians(95-count), (float)width/height, 1, 4000); // 画角
      for (int i=0; i<bPos.length; i++) {
        fill(c[i]);
        translate(bPos[i].x, bPos[i].y, bPos[i].z);
        box(bSize[i].x, bSize[i].y, bSize[i].z);
        translate(-bPos[i].x, -bPos[i].y, -bPos[i].z);
      }
      save("課題3/"+nf(count, 2));
      if (count>=90) {
        exit();
      }
      count++;
    }
    ランダムな20個の直方体が最初は小さく、しだいに大きく表示される。
    カメラは最初から最後まで全く動いていないので、オブジェクトは近づいてくるのではなく表示が拡大されるだけ。
    (このプログラムのフォルダには新しく「課題3」フォルダが作成され、その中に91枚の画像ができている)

課題 4 (ドリーイン)

画角を変えないままカメラを前方に動かし、見え方の変化を確認する。
  1. Processingのメニューから「ファイル」→「名前をつけて保存」を選び、「ex14_4」という名前で保存する。
  2. コードを以下のように書き換える。
  3. 26行目の後に以下のコードを追加 (コピー&ペースト) してプログラムを実行する。
  4.   PVector dc = new PVector(width/2.0, height/2.0, height/2.0/tan(PI/6)); // デフォルトのカメラ位置
      camera(dc.x, dc.y, dc.z-count*20, dc.x, dc.y, -5000, 0, 1, 0); // カメラ位置、注視位置、画面上方向
    (camera関数はカメラの位置、注視位置、画面の上方向を決めるベクトルを設定する関数。ここではカメラ位置のz成分 (画面の前後方向。手前側が+方向) だけを変化させる)
    (カメラ自体が前進するので、課題3とは異なりオブジェクトが自分の左右をすり抜けるように動く)
    (このプログラムのフォルダには新しく「課題4」フォルダが作成され、その中に91枚の画像ができている)

課題 5 (トラック)

カメラを一定の方向を向けたまま横に動かしたときの見え方を確認する。
  1. Processingのメニューから「ファイル」→「名前をつけて保存」を選び、「ex14_5」という名前で保存する。
  2. コードを以下のように書き換えてプログラムを実行する。
  3. (カメラ、注視点の両方のx座標を「デフォルト位置から900右にずれた位置」→「900左にずれた位置」に移動させる。つまり前を向いたまま右から左に移動させる)
    (カメラは右から左に移動するので、オブジェクトは左から右に移動していくように見える)
    (このプログラムのフォルダには新しく「課題5」フォルダが作成され、その中に91枚の画像ができている)

課題 6 (パン)

カメラを固定したまま横に向きを変えたときの見え方を確認する。
  1. Processingのメニューから「ファイル」→「名前をつけて保存」を選び、「ex14_6」という名前で保存する。
  2. コードを以下のように書き換えてプログラムを実行する。
  3. (注視点はカメラの前方から0.45ラジアン(≒26°)右から0.45ラジアン左の位置に移動する。カメラ自身は動かない)
    (カメラは右から左に向きを変えるので、オブジェクトは左から右に移動していくように見える)
    (このプログラムのフォルダには新しく「課題6」フォルダが作成され、その中に91枚の画像ができている)


  4. 図のようにしてex14_6の「課題1」フォルダにある画像から「動画1.mov」(または「動画1.mp4」)を作成する。
    1. 「ツール」→「ムービーメーカー」でツールを起動する
    2. (一つ目の)「選択」で動画のもとになる画像があるフォルダを選ぶ
    3. 「動画を作成」で動画を保存する (このときの設定方法はProcessingのバージョンによって異なる)
    (動画の保存先はどこでもよい)

    演習室・Processing 3.5の場合
    (「圧縮」を「Animation」に設定する)


    自宅・Processing 4の場合
    (「圧縮」を「MPEG-4」に設定する)



  5. 同様にして「ex14_6」の中の「課題2」~「課題6」フォルダの画像から「動画2.mov」~「動画6.mov」(または「動画2.mp4」~「動画6.mp4」)を作る。
  6. (Processing 3.5でmov形式の動画を作成した場合は) 中間ファイル「動画1.mov.tmp」~「動画6.mov.tmp」を削除する。
  7. 6つの動画をまとめて選択して右クリックし、「送る」→「圧縮(zip形式)フォルダー」でzip化する。
  8. (300~500kB程度になるはず)

提出


戻る

inserted by FC2 system