Direct3D ファントムダストのオブジェクトスペースノーマルマップ

また今更な古い話なので興味ない方は無視してください。(前回)

当時 CG WORLD の 2004 年 9 月号を見た人から、キャラに object space (OS) の
ノーマルマップを使っているのはなぜかと良く聞かれることがありました。
それを思い出したのでちょっと書いてみます。

●理由

Phantom Dust は、シーン全部にほぼノーマルマップまたは同等の重いシェーダーを
適用しています。

Xbox は Shader Model 1.x 世代なので、Pixel Shader は 9~12bit の低精度な
固定小数だし 8 + 4 stage という命令数制限もありました。
この制限の中に必要な機能を詰め込むにはかなり工夫が必要です。

逆に頂点シェーダー側は浮動小数演算だし命令スロット数も比較的余裕はあります。
でも頂点演算はタイトです。シェーダーが 1命令 (0.5 cycle) 増えただけで描画速度に
大きく響きます。

PixelShader
・命令数制限、演算精度が厳しいので、必要な機能を入れるだけでぎりぎり。

VertexShader
・描画速度にかなり影響が生じる。機能的には十分だが速度的には余裕がない。

キャラクタ用シェーダーの頂点は、スキニング処理が入るためただでさえ負荷が高く、
頂点データも index と weight の分だけ多くなります。

 ・頂点のデータ量を減らしたい
 ・演算量を減らして高速化したい。

よって最適化のために、キャラクタ用シェーダーではオブジェクトスペースのノーマル
マップを使っています。
速度だけでなくメモリを大量に消費することもノーマルマップでは大問題でした。

●計算

オブジェクトスペースで作られたノーマルマップは、シェイプの変形に柔軟に追従する
ことが出来ません。
そのため、法線をテクスチャに焼き込んだ形状が保存されない Maya 等のツール上では、
オブジェクトスペースのノーマルマップではアニメーションすることが不可能となります。

しかしながら焼き込んだ形状が保存されていて、かつ変形したジオメトリとの差分が求まるならば、
オブジェクトスペースのノーマルマップから変形後の法線を導き出すことが出来ます。

ボーンによるアニメーションでも全く同じことです。基準となるバインドポーズを持っていて、
ツールから出力されるメッシュはこの形状を保ったスタティックなものです。

スキニングの演算自体が、骨の移動に従った基準形状からの各頂点の変化量を求めていることに
なります。このマトリクスがそのまま使えます。

Blend された Local to World matrix をそのまま使うだけ

Phantom Dust では光源ベクトルを逆変換しているので、頂点で求めた Local To World
Matrix の逆行列 (3×3) を光源に掛けます。
これが各頂点毎の Local 空間 (OS) なので、サンプリングされたオブジェクトスペースの
ノーマルマップを使ったライティングを行えます。
基本姿勢の OS を Os0 とすると、光源を World → Os → Os0 と変換していることに
なります。

頂点に Tangent Space の Matrix を埋め込まなくて済むので、こちらの方がデータ量として
かなりお得です。Tanget Space への変換分だけ演算負荷が減るし、頂点サイズの縮小も
そのまま高速化に繋がります。

ただしオブジェクトスペースでノーマルマップを作った場合は、以後絶対に基本となる
ポーズを変えてはいけません。Export 時に、ツール上でうっかり触ってずれてしまった、
とか許されなくなるわけです。

●ムービーレンダリング

プロジェクト終盤で困ったのがムービーのレンダリングです。
ゲーム内のキャラデータを使ってムービーシーンを作る必要があったのですが、次の理由に
よりそれが困難でした。

 (1) 法線マップのレンダリング方法
 (2) アニメーション変形

(1) 当時の Maya (4.5 あたり) には法線マップ用のレンダリング機能がなかったので、
そのままではレンダリングすることが出来ません。シェーディングノードを複雑に組み合わせて
Matrix 演算を行うことも出来ますが、いろいろ無理があります。

(2) 上でも書いたとおり、オブジェクトスペースのノーマルマップはツール上だと
元の形状からの変化量が求まらないのでアニメーション変形に対応することができません。

結局ムービーレンダリングのために専用のプラグインを作成しました。
プロジェクト終盤ではすでに RADEON 9700 が出ていたので、RADEON + ShaderModel 2.0
相当の OpenGL 機能を使っています。
OpenGL + ARB_vertex_program + ARB_fragment_program を使い、
Maya ビューポートへのリアルタイムプレビュー +
Maya ハードウエアレンダーバッファへのレンダリングを行っています。

確か条件分けは 3通り。

1. TS Normal map ではそのまま何もせずレンダリング可能。アニメーションも OK。

2. OS Normal map でも変形しない物ならそのままレンダリングします。

3. OS かつアニメーションを行う場合、プラグインに対して事前に基本形状を登録する
 必要あり。

3. では登録したメッシュを内部で記録して、Object Space のレンダリング時に参照しています。
これは Transform node (Matrix) ではなくメッシュ自体を丸ごと保存しています。
そのため実機と違い、ボーンアニメーションだけでなくシェイプアニメーションにも対応
出来るようになっています。

これが可能なのは OS だけど各頂点に Tangent Space 用の Matrix を埋め込んでいるから。
変形後の頂点から Tangent Space の Matrix が求まるので、そこから逆変換すれば
OS のノーマルマップから得た法線を TS に持って行くことが出来ます。
つまり頂点が持っている Tangent Space 変換 Matrix は 2つです。

Matrix1:  Ts → Os0
Matrix2:  Ts → Os

Os0 は基本形状の Object Space 、Os はアニメーション後の Object Space とすると
サンプリングした法線は Matrix1 で逆変換後 Matrix2 に適用すればよいことになります。

●背景

背景は Tangent Space (TS) です。地面や壁などバンプマップとしてタイリングするし
同じノーマルマップテクスチャを何度も使い回すからです。
貼られる面も任意となります。

●その後

あくまで昔の話です。キャラデータに OS のノーマルマップを使用したのはこのときだけ。
変形があるとデータの扱いが難しいし、その後のシェーダーでは PixelShader もかなり
高機能化されたので、ここまで考える必要がなくなりました。

Shader Model 2.0~3.0 の描画パイプラインの設計では、全く区別のない両対応をしたものの
結局キャラにも Tangent Space を使っています。

ただしツール内で生成された Tangent Space には互換性が無いので、最終的な
レンダリングプログラムと条件を一致させなければなりません。
結局自前のツールでデータを生成することになります。

Shader Model 4.0 以降は TS のみの対応としました。
シェーダーの基本機能として当たり前に使えるけど、その比重はさらに下がっています。
すでに Direct3D 11 の時代、Shader Model 5.0 ならなおさらでしょう。

関連エントリ
Direct3D ファントムダストと破壊の穴
3D一般 ノーマルマップの互換性問題(2) UVに潜む罠