株式会社デジタル・フロンティア-Digital Frontier

Header

Main

  • TOP
  • DF TALK
  • ◆移植オペ(中巻)◆ ~ Softimage & Mentalray Shader ~

◆移植オペ(中巻)◆ ~ Softimage & Mentalray Shader ~

2011/6/27

Tag: ,,

皆さんお元気ですか~?

この所の梅雨の鬱陶しさにちょいちょいエアコンのスイッチONしている開発室ゴトー[@jackybian]です。
余震も落ち着き、計画停電もなくなった今、確実に意識低下がみられる今日この頃。。。
反省… m(..)m

さて前回の投稿は…と調べてみたら何ともう半年近くも前っ!
もう忘れちゃってますよね~。自分もどんなこと書いたのか?すっかり忘れてました。
という訳で前回ネタを見ながらこの原稿を書いている次第です。(;´∀`)

前回はRalf Havel氏提供のlatlong_lensシェーダーをSoftimage(以下SI)へ移植しちゃえ作戦!でした。
とりあえずオリジナルコードのままSIへ移植オペしてみたんでした。
ご興味ある方はコチラからどうぞ。

というわけで今回は習学も兼ねて”シェーダー内部でどんな計算をしているのか見ちゃえ作戦!”です。
前回ネタをご覧頂いた方はご存知かと思いますが、このlatlong_lensは
レイの方向を変化させることで環境マップ(Spherical)用の画像を生成するレンズシェーダーです。
語源はlatitude(緯度)の”lat“とlongitude(経度)の”long“からlatlongと呼ばれているようです。

通常のレンダリング(レイトレーシング)ではカメラから各ピクセルに対応するレイを飛ばし…となる訳ですが
このシェーダーではレイの方向を操作しカメラを中心とした仮想球上へレイを飛ばし各ピクセル値を計算します。

大まかな流れとしては、
各ピクセルをラスタ空間座標(ピクセル値)からUV座標(0.0~1.0)への変換
このUV値から仮想球上へのベクトルを求める
そのベクトル方向へレイを飛ばしピクセル値を求める
ということになります。

では実際にコードを見ながらどのような計算をしているのか見てみましょう!

extern "C" DLLEXPORT miBoolean
latlong_lens
(
miColor                *result,
miState                *state,
latlong_lens_t         *params
)
{
//①
miBoolean vmirror;
miScalar uval, vval, rayx, rayy, rayz;
miVector raydir, raydir_internal;
//②
vmirror = *mi_eval_boolean(&(params->m_vmirror));
//③
if (vmirror==miTRUE){
 uval = (state->camera->x_resolution - state->raster_x) / state->camera->x_resolution;
}else{
 uval = state->raster_x / state->camera->x_resolution;
}
vval = (state->camera->y_resolution - state->raster_y) / state->camera->y_resolution;
//④
rayx = (float)(sin(vval*M_PI)*cos(M_PI*(2*uval+0.5)));
rayy = (float)(cos(vval*M_PI));
rayz = (float)(sin(vval*M_PI)*sin(M_PI*(2*uval+0.5)));
raydir.x = rayx; raydir.y = rayy; raydir.z = rayz;
//⑤
mi_vector_from_camera(state, &raydir_internal, &raydir);
//⑥
return (mi_trace_eye(result, state, &state->org, &raydir_internal));
}

①【まず各変数を宣言】
シェーダーのUIパラメータ変数  : vmirror
UV座標への変換変数        : uval, vval
レイベクトルのX,Y,Z変数      : rayx, rayy, rayz
レイベクトル用の変数        : raydir, raydir_internal
を宣言しています。

②【シェーダーパラメータの取得】
mi_eval_boolean()関数によりシェーダーのUIパラメータを取得しvmirrorへ格納。
このパラメータはシェーダーにより生成されるlatlogマップを水平方向に反転するかどうかのbool値です。

③【サンプルポイントのUV座標化】
レンダリング解像度(x_resolution, y_resolution)とサンプリング位置(raster_x, raster_y)から
ラスタ空間座標(ピクセル値)からUV座標(0.0~1.0)へ変換を行っています。
座標中心は左下のコーナーになります。
if文でUIパラメータ(vmirror)による水平方向に反転する場合としない場合でuval値を分岐しています。
ここでvval式を見ると

vval = (state->camera->y_resolution - state->raster_y) / state->camera->y_resolution;

v方向に反転した値として格納されていますがのrayy式

rayy = (float)(cos(vval*M_PI));

によって、この反転を戻すようになっているので処理上は問題ありません。こうしている理由は定かではありませんが…

④【適切な方向へレイを変位】
さていよいよこのシェーダーの本題部分です。
いかにしてカメラを中心とした仮想球上へレイを曲げるのか!?
今回のlatlong_lensによって生成された画像は環境マップ(Spherical)で使用されることを前提にしています。
そこでこの環境マップが仮想球に対してどのようにマッピングされているのかをご覧下さい。

このようにGlobalZ(+)方向にシームが来るような形でラップされます。
これでどのピクセルのレイをどの方向に変位させれば良いかイメージ出来たと思います。
ではこれをどのように表せば良いのでしょう?

仮想球上へのベクトルは下図のようにYaw角(水平角),Pitch角(垂直角)によって全方向を定義できます。

ベクトルを求めるにあたり、X,Z成分とY成分に分けて考えます。

~まずX,Z成分について~
話を単純にする為、で変換したUV座標のV値(vval)=0.5の場合について考えます。
この時のX成分をX’, Z成分をZ’とします。
仮想球をXZ平面から見てで変換したU値(uval)の変位と求めるベクトルのX’,Z’値の関係を見てみます。
X’の変化をみると

U値が0.0⇒0.25⇒0.5⇒0.75⇒1.0と変化していった時、X’値は0⇒-1⇒0⇒1⇒0を取ります。
そうsin波です。よって X’ = -sin(2U*π) = sin(-2U*π) と表すことが出来ます。

同じようにZ’の変化をみると

U値が0.0⇒0.25⇒0.5⇒0.75⇒1.0と変化していった時、Z’値は1⇒0⇒-1⇒0⇒1を取ります。
こちらはcos波です。よって Z’ = cos(2U*π) と表せます。

では次にVが0.0⇒0.5⇒1.0と変化した場合を考えます。
今度は仮想球をXY平面から見てで変換したV値(vval)の変位とこのX’値の関係を見ます。

X_multiplyの変化を見ると
V値が0.0⇒0.5⇒1.0と変化していった時、X値は0⇒1⇒0を取ることから
X_multiply = sin(V*π) と表せ、これを先程のX’に乗算してあげればX成分が求まります。

同様に仮想球をYZ平面から見てV値(vval)の変位とこのZ’値の関係を見ます。

Z_multiplyの変化を見ると、X_multiplyの時と同様に
V値が0.0⇒0.5⇒1.0と変化していった時、Z値は0⇒1⇒0を取ることから
Z_multiply = sin(V*π) と表せ、これを先程のZ’に乗算してあげればZ成分が求まります。

というわけで
X = sin(V*π) * sin(-2U*π)
Z = sin(V*π) * cos(2U*π)
となります。

~残るY成分について~
同様に仮想球をXY平面から見てで変換したV値(vval)の変位とこのY値の関係を見ます。

Yの変化をみると
V値が0.0⇒0.5⇒1.0と変化していった時、Y値は-1⇒0⇒1を取ることから
これはcos波です。よって
Y = -cos(V*π)
と表せます。

~レイベクトルの作成について~
これでX,Y,Z全てのベクトル成分が求まりましたので
raydir.x = rayx; raydir.y = rayy; raydir.z = rayz;
でレイベクトル用の変数(raydir)へ値を格納しています。
Havelさんのコードでは
sin(-θ) = cos(θ+π/2)
cosθ = sin(θ+π/2)

と変換できるため
X = sin(V*π) * cos(π*(2U+0.5))
Z = sin(V*π) * sin(π*(2U+0.5))
またのvval式でv方向に反転した値としてV値へ格納しているため
Y = cos(V*π)
として実装しているようです。

⑤【カメラ空間からインターナル空間へ変換】
④でベクトルは求まりましたが最終的にはその方向へレイを飛ばしピクセル値を取得する必要があります。
この部分の処理はMentalrayの関数が用意されているのでそれを使ってしまえばOKなのですが
この関数に渡す為にはベクトルの座標変換が必要になります。
そこでmi_vector_from_camera()関数を使ってカメラ空間からインターナル空間へ変換しています。
※インターナル空間・・・Mentalrayが交差点やその他の点、ベクトルの情報をシェーダーに渡す際に使用される座標系。
state構造体からアクセスするorg,dir,point,normal,normal_geom,motionはこのインターナル空間として渡されます。
多くの場合、インターナル空間とワールド空間は一致しますが厳密にいうと異なるようです。

⑥【変換後のベクトル方向へレイをキャスト】
最後にmi_trace_eye()関数を使ってベクトル方向へレイを飛ばし結果を取得します。
その結果をシェーダーのResult値としてリターンして終了となります。

さていかがだったでしょう? ちょいと長かったですかね^^;
でも解ってしまえば意外とシンプルですよね。ほとんど三角関数だけですし…
で実際に使ってみると結構便利でCG背景から環境マップがサクッと作れちゃいます。

このlatlong_lensは、カメラの位置と向きによって結果が変わってくる訳ですが
生成されたマップをIBL等で使う場合、カメラの向きはGlobalZ(-)方向で良い事がほとんどです。
また最近は多くのCG作業が同時並行で進むので各Cutシーンの段階でマップ生成を行う可能性も高くより手早く
使えるようにちょっとだけ拡張した形で自分は実装してます。その内容は次回の移植オペ(下巻)にてお伝えする予定です。
ある程度MentalrayShaderの知識は必要かと思いますが、ぜひ皆さんもShaderWritingチャレンジしてみて下さい。
という訳でお時間が来たようなので今回はこれにておしまい♪
ではまた~ (´∀`*)ノ アディオス

Some images are taken from the following books and Web sites.
mental ray Handbook Vol.3:Writing mental ray® Shaders: A Perceptual Introduction
http://www.writingshaders.com/
Special Thanks Andy! 😀

Pocket

コメント

コメントはありません

コメントフォーム

コメントは承認制ですので、即時に反映されません。ご了承ください。

*