CustomRenderTargetその2

ここで3Dの計算の詳細(ビューやプロジェクション変換行列の中身の計算とか)について説明するのは何か違うような気がするのでそういうのは省略するとして。って説明できるほど詳しくもない。数学苦手だし。
そうは言ってもDirect3Dはそのような行列を駆使して頂点変換し、その流れでロジックを構成するようになっているので、中身はさておき計算の概要は知っておかないと何もできない。
今回はそういう話。

頂点変換の流れ

前回のコードでは完全に省略したが、3Dを扱う場合はワールド変換→ビュー変換→プロジェクション変換→ビューポート変換、という感じで座標を変換して描画する。古来よりポリゴンを描画する3Dの計算はそのようになっていて、それなりに意味があり有用な手法であるのでDirect3D及びGPUはその手法を踏襲している。OpenGLでも同様。

ワールド変換

OpenGLだとモデル変換?ともあれ、通常は頂点バッファ内の頂点は回転原点を(0,0,0)にした-1〜1のローカル座標系で定義されるので、それをそのまま描画しに行くと例えば建物と人が同じサイズになったりする。あと向きも変えられない。位置も。
なので、キャラの姿勢と位置と大きさにあわせてローカル座標系の全頂点をワールド内の全オブジェクト統一座標系に変換してやる必要がある。これをワールド変換と言う。キャラそれぞれ用に回転・スケーリング・移動の成分を持った4x4行列を持たせる。

ビュー変換

全オブジェクト統一座標系であるワールド座標に全頂点を変換したとして、それをどのように画面に表現するかはワールド内に配置するカメラによる。カメラの位置からカメラの向きにあるオブジェクトがカメラに映る。
3D計算におけるカメラは4x4行列で表現され、これを頂点に乗算することで、カメラの位置が(0,0,0)、カメラの正面がz方向+になるような座標系に変換されるようにする。ワールド原点からカメラを移動・回転させる行列の逆行列となり、ようするにカメラを移動させる代わりに全オブジェクトの全頂点を逆に移動させてしまおうという発想である。
Matrix#look_atにカメラの位置と向きを渡せばそのような行列が手に入る。これを頂点シェーダで全頂点に対して乗算する処理をビュー変換と言う。ビュー変換後は全頂点が移動・回転するが、スケールはワールド座標系の時と同じである。カメラにはサイズの情報が無いから。

プロジェクション変換

射影変換とも言う。とりあえず画面にワールドの物体がどのぐらいのサイズで描画されるのかはここで決まる。Matrix#projectionで行列が手に入り、渡す引数は画面サイズとカメラからの距離となる。カメラはレンズが付いているイメージがあるが、3D描画の世界でのカメラは数学的な点Pであり、サイズを持たないし、矩形でもない。
プロジェクション変換で言う画面サイズはCustomRenderTargetの描画バッファをワールド内のスケールで表現したもので、縦横の比率が食い違うと潰れた画像になる。カメラからの距離とワールド内の画面サイズにより描画できる視界の範囲が変わる。
カメラからワールド内の描画面を通した視界は視錐台と呼ばれる立体の台形になるが、プロジェクション変換では描画面上でのxy座標が-1〜+1になるようにスケーリングされる。遠いところは台形が大きい分、視界内でも-1〜+1より大きな値になる。代わりに頂点ベクトルの4つ目の要素であるwに台形の拡大率が入り、GPUによりxyzがwで割られる。
例えば前回のサンプルの頂点シェーダに以下のコードを追加してwを2にしてやると

    output.vPosition.w = 2;

各頂点の座標が半分になり、このような結果になる。

このあたりの話の詳細はまるぺけさんのその70 完全ホワイトボックスなパースペクティブ射影変換行列とかその55 そもそも「w」って何なのか?とかを参考にするといいかもしれないが、まあ、詳しく知らなくても何とかなるんじゃないかな。

この仕組みの汎用性

最小限の処理を記述したサンプルはここまで書いたワールド・ビュー・プロジェクション変換を全部すっ飛ばして頂点バッファの値をそのまま頂点シェーダから出力した。本来であればカメラから見たワールド内の頂点情報が算出されて出てくるはず、ということである。言い換えればそのへんを通常の3Dのように計算するか、全くしないか、ぜんぜん違う計算をするか、は、ユーザが自由に決められる。一応、3Dが想定されているのでHLSLには3Dに便利な関数がたくさん定義されているし、GPUが自動的にxyzをwで割るあたりは3D用としか思えないのだが、それでも、例えば2D描画にも使えるし、描画じゃない何かにも使える。

CustomRenderTargetの用途

まあ、とりあえず3Dをすることはできる。でも3Dの世界は広く、すごい勢いで新しい技術が開発されていってるし、そっち系を真面目にやるならいちいち自前で実装するんじゃなくそれこそUnityみたいなのに乗っかったほうがよい。っていうか同レベルの機能・表現を個人が作れるとはとても思えない。
じゃあCustomRenderTargetって何するものなの?って話になるが、このぐらいの低レベルの仕組みを用意しておけば、例えば2Dメッシュ分割だとか、2Dスキンメッシュだとか、ベクタグラフィックだとか、そんな3Dじゃない何かを作って遊べるんじゃないかな〜と言う感じである。頂点を直接扱ってシェーダが書ければ何でもできる。あとは3Dを利用した各種表現の実験とか。影の作り方とか。
もちろん簡単に3Dができるようなフレームワークを誰かが作ってくれるなら大歓迎だけども、少なくとも俺はそういうものは作れそうにない。自分で使わないし。
そんな感じ。