聞きかじりめも

主にC++やメディア処理技術などに関して気付いたことを書いていきます.ここが俺のメモ帳だ!

透視投影変換にガンマをかけて嘘パースを実現

はじめに

今回の手法で次のような絵が撮れます.

f:id:Mzawa2:20161023020604p:plain

f:id:Mzawa2:20161023020653p:plain

本記事ではフリーのMMD描画アプリを利用して嘘パースの実験をしていますが,シェーダーレベルでアプリを改造する上,原理のみ紹介するので,CGデザイナよりもむしろエフェクトを開発するプログラマ向けの記事となります.予め御了承下さいませ.

嘘パースについて

皆さん,嘘パース(オーバーパース,漫画パース)というのはご存知でしょうか?

例えば,サンライズアニメでよく見かける,例の武器を画面いっぱいに突き出して構える迫力ある構図サンライズパース)をCGアニメで再現したいとします.しかし,実寸で作られたCGに同じポーズをさせて普通に透視投影変換でレンダリングすると,あんなに大きく武器が映りませんのでちょっと迫力に欠けてしまいます.なぜそんなことが起こるかというと,サンライズパースでは正確な透視投影よりも手前の物体がより大きく映るようにあえて強調した投影に従って描いているからです.このように,通常の透視投影変換ではありえないくらい近い物体が大きく写っているような投影を嘘パースと呼びます.アニメや漫画では迫力があった構図なのにCGにすると物足りない,という現象の原因は嘘パースだったりします.

現在,CGアニメで嘘パースを実現する方法は,その構図専用の歪んだCGモデルを用意する というのが一般的です.でも,それってかなり面倒くさいですよね.だって同じキャラクターなのに超カッコいい構図の度に新しいCGモデルを用意しなきゃいけないんですから.他には,自動で人物CGモデルを局所的に肥大化させる手法(嘘パースプラグイン),画角の異なるカメラを複数用意してシームレスに映像を合成して実現する手法(マンガパース)などが知られています.

ここでは,より原理的な部分に着目して嘘パースを実現する手法を考案しました.それが,透視投影変換にガンマをかける という発想です.ディスプレイガンマのようにZ軸方向にガンマをかけることで,人間の知覚に近い歪み方をとっても簡単に再現できるのではないか?と思って試してみました.これは新しい投影変換を開発する必要がありますので,ちょっと考え方を示していきます.

原理

通常の透視投影

デザイン系の人には遠近法と言った方がわかりやすいかもしれないですが,透視投影とはすべての光線がある唯一の視点(原点)に集中するものとして考案された投影法です.

f:id:Mzawa2:20161023020435p:plain

透視投影をCGで実現するには,レンダリング時に透視投影行列という同次変換行列(下式)をかませます.これによって,透視投影の視錐体がクリッピング空間に圧縮されるわけです.

{\displaystyle
 M_{perspective}=
 \begin{bmatrix}
  \frac{2z_n}{r-l} & 0 & \frac{r+l}{r-l} & 0 \\
  0 & \frac{2z_n}{t-b} & \frac{t+b}{t-b} & 0 \\
  0 & 0 & -\frac{z_f+z_n}{z_f-z_n} & -\frac{2z_fz_n}{z_f-z_n} \\
  0 & 0 & -1 & 0
 \end{bmatrix}
}

透視投影にガンマをかける

透視投影では,物体の見た目の大きさは視点からの距離に反比例するという性質があります.3DCGプログラマにとっては非常に当たり前のことに思えますが,それはなぜでしょう?理由は,透視投影では光線が原点に直線的に集まると仮定しているからです.もちろん,この仮定は物理的には間違ってません.

f:id:Mzawa2:20161023020501p:plain

対して嘘パースでは,視点の物体が通常の透視投影よりも極端に大きくなるという性質があります.透視投影では直線 {y=kz} に従って投影面 {z=z_n} での見た目の大きさを決定していましたが,私はここでガンマ変換をかけることにしました.つまり,人が奥行きのあるシーンを観察したときに,スティーブンスの冪乘則に従って奥行知覚が実際の奥行きよりも強調される と仮定しました(これが事実かどうかは私は知りません).この時の投影は次のようになります.

f:id:Mzawa2:20161023020518p:plain

このようにすると,同じ大きさの物体でも遠くにあれば投影面ではより小さく映り,逆に近い物体はより大きく映ることになります.具体的には,同じ物体の見た目の大きさは{z^\gamma}の逆数に比例します.

{ \displaystyle
 h_n = \frac{z_n^\gamma h}{z^\gamma} \propto \frac{1}{z^\gamma}
}

ガンマ透視投影変換

この考えに基づいて投影行列を再構成すると,次のようになります.ここではガンマ透視投影変換と呼ぶことにします.{\gamma = 1}とすると元の透視投影行列と一致するはずです.プログラム的には,モデルビュー行列をかけた後に一旦Z軸を編集する必要があるのでちょっと面倒ですが,バーテックスシェーダーで難なく実装できるでしょう.

{ \displaystyle
 \begin{bmatrix}
  x \\
  y \\
  z \\
  1
 \end{bmatrix}=
 \begin{bmatrix}
  \frac{2z_n^\gamma}{r-l} & 0 & -\frac{r+l}{r-l} & 0 \\
  0 & \frac{2z_n^\gamma}{t-b} & -\frac{t+b}{t-b} & 0 \\
  0 & 0 & \frac{z_f^\gamma+z_n^\gamma}{z_f^\gamma-z_n^\gamma} & -\frac{2z_f^\gamma z_n^\gamma}{z_f^\gamma-z_n^\gamma} \\
  0 & 0 & 1 & 0
 \end{bmatrix}
  \begin{bmatrix}
   X \\
   Y \\
   (-Z)^\gamma \\
   1
  \end{bmatrix}
}

実験

今回はMMD on WebGLソースコードを改変して,初音ミクの標準モデル(あにまさ式)で試してみました.この場を借りて作者様に感謝します.

結果

まずは通常の透視投影変換でレンダリングした結果がこちらです.

f:id:Mzawa2:20161023020532p:plain

これとほぼ同じ構図,ポーズで {\gamma = 1.3, 1.7} でガンマ透視投影変換でレンダリングした結果が次のようになります.

f:id:Mzawa2:20161023020544p:plain

f:id:Mzawa2:20161023020604p:plain

狙い通り,前方に突き出した手がかなり強調される嘘パースが実現できました.

今回の目的とは異なりますが,逆に {\gamma = 0.6} とガンマを1より小さくした場合どうなるかというと...

f:id:Mzawa2:20161023020615p:plain

こうなります.奥の方の手が透視投影よりも大きく映ります.偶然かもしれませんが,平行投影に似ています.

注意点

上の結果はかなりうまくいってるようですが,実はガンマ透視投影では構図をうまく調節しないとすぐにひどい結果になります.

f:id:Mzawa2:20161023020628p:plain

このように,画面の外側は激しく歪みやすいです.しかし改善策はあります.ガンマ透視投影行列の光軸中心(行列の3列目の上2個)をずらしてやることで,ある程度問題を回避できるようです.

f:id:Mzawa2:20161023020638p:plain

f:id:Mzawa2:20161023020653p:plain

上2つはほぼ同じ方向からほぼ同じポーズをレンダリングしたもので,上は光軸中心を中央に固定しており,下では光軸中心を顔付近に持ってきています.このように,光軸を中央からずらして調節することで,ほぼ違和感のない嘘パースが様々な構図で実現できるようです.

経験則ですが,初音ミクのような人体モデルの場合はガンマによって頭部が歪むと強く違和感を覚えるので,頭部だけは歪まないように構図を調節すると良いでしょう.