myara CG blog

CG Design Blog. Thoughts, experiments and experiences.

SI | FOV & 久々にMath

今やっているプロジェクトでは、GUIのある映像を作っているので、
GUIと合うようなカメラワークやレイアウトを作るために
SI上でもGUIをリアルタイムで表示させたかった話です。

ロトスコープはモデルの後ろで表示されてしまうのでNGです。

思いついたやり方は、カメラの前に居たポリを付けるぐらいです。
おそらく誰でもすぐに思いつくやり方でしょう。

問題はですね、画角をいじると、この板ポリが小さく見えてしまったり、大きく見えてしまったりするところですよ。
画角を固定にすれば良いだけですが、そうすると映像の表現が結構限られてしまいます。

FOV(画角)に合わせて、この板ポリがスケール、または移動すれば良いだけですね。
そう、簡単に聞こえますが、そんなに簡単ではなかった。

SIのデフォルトのオプションではFilm Aperture (開口) は固定で、FOV と Focal Length (焦点距離)だけが動きます。

居たポリのサイズを開口サイズに合わせ、焦点距離と同じ距離にすれば良いだけです。
カメラの「projplanedist」を使って、簡単なエクスプレッションで済みます。

しかし!

FOVをいじるとSIが自動に FocalLength の数値を更新してくれますが、FocalLengthを使っているエクスプレッションまで反映されません!
今回は FocalLengthより FOV を使っているので、エクスプレッションをFOVにつなげないとちょっと不便。

FOV情報をFocalLengthに変える方法ですね。ここは問題でした。
ネットで検索してみたけど、なにも見つかりませんでした。しょうがなく頭を使ってみるしかないですね。

FOV と Film Aperture をイメージにすると、三角形になりますよね。
その真ん中に FocalLength の線をひくと、直角三角形になります。
これなら簡単な三角関数で計算が出来るじゃん・・・

CAMFOV1.jpg


・・・と思ったけど、あれ?

関数って・・・なんだっけ?

大学を中退してから使ったことないな~

因みに、数学系オペレーションリサーチ専攻で数学トラウマを受け、1年で辞めたものです。
オペレーションリサーチって知っている人いるのかな?

現在は電卓がないとワリカンの計算も出来ない人ですけど。

とにかく、簡単な三角関数が必要なので、ネットを調べながら、高校の授業を思い出しながら、まずは移動でやってみました。

こうなりました:

CAMFOV2.jpg

ということで、


cos( Camera.camera.fov / 2 ) * Camera.camera.projplaneheight * 25.4 / 2 / sin( Camera.camera.fov / 2 )


というエクスプレッションになりました。

これで Focal Lenght が分かるので、後はこっちのもんですね。

簡単な帰一算を使えば、スケールバージョンも作れます。


■ 注意するべきところ:
Film Aperture は mm ではなく、 inches で表示されているので、 mm に変換する必要があります。
単純に 「* 25.4 」 で ミリになります。

エクスプレッションの三角関数は角度で計算します。
JScript や XSI.Math は Radians で計算します。

これは重要ですね。エクスプレッションよりスクリプトになれているので、Radiansに変換していたから変な数値になっていました。
マニュアルには載っていない気がします。載っていますか?

ちなみに、Degrees -> Radians の変換は
Degrees = Radians * 180 / PI

JavaScript には Math.Pi があるので、簡単に計算が出来ますが、
XSIMath を使えば関数なんか忘れていちゃっても簡単に出来ます:

XSIMath.RadiansToDegrees
XSIMath.DegreesToRadians



スクリプト化してみたらこんな感じになった:

注意:  コードは全く整理していなくて汚い!
注意2: 計算より直感の方が早かったから、数値はちょっと適当です。格好つけて三角関数などの記事を書いて結局直感的にやってしまう適当な人ですいません。

実行方法:
カメラ・カメラルートを選択して、実行

GUIの画像ファイルは「GUIImgPath」変数で設定出来ます。
画像の比率はグリッドのサイズで指定しています(16/9)


//JScript<br /><br />GUIImgPath = "
// 選択フィルター
if (selection.count !=0){
if (selection(0).type == "camera") var cam = selection(0)
else if (selection(0).type == "CameraRoot") var cam = selection(0).FindChildren("","camera")(0)
else if (selection(0).type == "CameraInterest") var cam = selection(0).Parent.FindChildren("","camera")(0)
else var cam = ActiveSceneRoot.FindChildren("","camera")(0)
}
else var cam = ActiveSceneRoot.FindChildren("","camera")(0)

// グリッド作成
var grid = ActiveSceneRoot.AddGeometry ("Grid", "MeshSurface","GUI")
grid.rotx = 90
grid.subdivu = 1
grid.subdivv = 1
grid.ulength = 16
grid.vlength = 9
ResetTransform(grid, siCtr, siRot, siXYZ);
ResetTransform("GUI", siCtr, siScl, siXYZ);
grid.sclx = 0.05
grid.scly = 0.05
ResetTransform("GUI", siCtr, siScl, siXYZ);
FreezeObj(grid);

// グリッドをカメラにコンストレイン
var cns = ApplyCns("Pose", "GUI", cam, null);
SetValue(cns+".posz", -1.68, null);
SetValue(cns+".cnsscl", false, null);

// Focal Length を計算するエクスプレッションを作る
var planedist= "cos(" + cam + ".camera.fov/2) * " + cam + ".camera.projplaneheight * 25.4 /2 / sin(" + cam +".camera.fov/2)"

// 上記の関数を使って、グリッドのスケールをFocal Length につなげる。
SetExpr(grid +".kine.local.sclx", grid +".kine.local.sclx / ( "+grid+".kine.local.sclx * ( "+ planedist +" / 10 ))", null);
SetExpr(grid +".kine.local.scly", grid +".kine.local.scly / ( "+grid+".kine.local.scly * ( "+ planedist +" / 10 ) )", null);

// マテリアル作成:
ApplyShader("$XSI_DSPRESETS\\Shaders\\Material\\Constant.Preset", grid, null, "", siLetLocalMaterialsOverlap);
var mat = grid.material

mat.shaders("Constant").color.red = 1
mat.shaders("Constant").color.green = 1
mat.shaders("Constant").color.blue = 1

// GUI画像を貼る:
SICreateImageClip(GUIImgPath, "gui");
CreateShaderFromProgID("Softimage.txt2d-image-explicit.1.0", mat, "Image");
SIConnectShaderToCnxPoint("Clips.gui", mat +".Image.tex", false);
SIConnectShaderToCnxPoint(mat +".Image.out", mat +".Constant.color", false);
SIConnectShaderToCnxPoint(mat +".Image.out", mat +".Constant.transparency", false);
SetValue(mat +".Constant.usealphatrans", true, null);
SetValue(mat +".Constant.inverttrans", true, null);

CreateProjection(grid, siTxtPlanarXY, siTxtDefaultSpherical, "Texture_Support", "Texture_Projection", null, null, null);
SetInstanceDataValue(null, mat +".Image.tspace_id", "Texture_Projection");

FreezeObj(grid);