この記事はUniversal Scene Description Advent Calendar 2024の11日目の記事です。
カメラの話。 実際はさらにTransformも計算が必要です。今回は割愛!
本文
Houdini上で作成したカメラ
Houdiniを使用していると勘違いしがちですが、USDのデフォルトMerter Per Unitはcmです。
つまり1Unitが1cmなのでMayaと同じです。(Mayaを10倍運用しているとかいうことを言い出すととてもややこしい)
試しにStageのMerter per unitを変更してみます。



何故かViewport上のカメラの大きさが変わりました。
これはCameraノードに入っているパラメーターのGuide ScaleのPythonが1 / merter per unitになっているためにこうなります。

実際にPropertyを見てもGuide Scaleが100(1/ 0.01)になっています。

基本的にはSolaris上はMeter per unitが1ですが、もし変更した場合にちゃんとこのカメラのガイドオブジェクトが大きくなるようになっているわけです。
と、いうことはカメラにとって重要な他のパラメーター、つまりFocal LengthやAperture Sizeなども変わるはずです。


ちゃんとガイドスケールと同じ値で計数がかかっているのはわかります。
Mayaのカメラを使っていると見落としそうになるのですが、よくよく見るとカメラのFocal Lengthが50, 5っておかしくないでしょうか?
1Unitが1cmなのであれば50cm 1Unitが1mなのであれば50cm
そんなカメラ聞いたことありますか?
この変換は正しいんでしょうか?でもHoudiniが間違ってるはずないですよね?大抵自分が間違ってます。
ということでUSDの仕様を確認するとこう書いてあります。
前略 ただし、レンズとフィルムバックの特性は、「生の」シーン単位ではなく、シーン単位の10分の1で測定されるという譲歩を行います。 これは、メートル単位あたりの0.01のフォールバック値(つまりセンチメートルのシーン単位)の場合、これらの「シーン単位の10分の1」プロパティは事実上ミリメートルであることを意味します。
つまり使用上勝手に10分の1にした値として計算するよ!ってことですね。
1Unitが1cmなのであれば50cmをさらに10分の1にして5cm = 50mm 1Unitが1mなのであれば50cmをさらに10分の1にして5cm = 50mm
なんかよく聞く標準レンズっぽい値になりました。
外部から読み込んだ場合
上記は全てHoudini上で変換されるとこうなります。という結果でした。
では、外部からMeter per unitの値が0.01のusdファイルを読み込んだらどうなるでしょう?
もちろん勝手に変換はかからないので値としては作成した時点のものになります。

どちらを並べても見え方は変わらないようですね。
これはFocal LengthとAperture Sizeが同じ比率で増えている場合は画角(見えている範囲)が変わらないので正しいです。

と、いうことはつまりMerter per unitが違うDCCツールから読み込んだカメラをそのまま使っても問題ないな!とはなりません。
何故かというと


正直無視しても問題ないと言えばないです。
ですが、把握した上でワークフローが組まれているかどうか?3Dでデフォーカスかけて試したい(参考にしたい)時とかはどうするのか?
この辺りも押さえておかないと、せっかくカメラの勉強をして現実のカメラとCGのカメラをちゃんと同じ設定にしているのに何だか見え方が違うぞ?ということになります。注意しましょう。
Transformはどうする?
デフォルト
Scene Importで読み込んだ場合も、Solaris上でカメラを作成した場合のどちらも4x4 Matrixでカメラの動きが表現されている。


Houdini上ではこれで特に問題はないが、別DCCへ持って行ったり、もしくはMayaなどから書き出して持ってくる際にこの4x4 matrixでカメラを表現するのはあまり良くない。
何故なのかというとMeter per unitを後処理で合わせたい場合にMatrixベースでの計算が必要になる or 簡単に処理するならカメラにスケールをかけることになる。
このカメラにスケールをかけるのが曲者で、あまりツール間に互換性がないことが多い。 (例えばMantraだとカメラにスケールをかけてしまうとVolumeのDensityなどに影響したりしていた。)
なので4x4 Matrixではない状態でカメラのやり取りをしたほうが移動(Translate)にのみMerter per unitの補正値をかければいいことになるので単純だしわかりやすい(と思う)
Maya等はPythonなどで出すしかない?デフォルトの書き出し処理次第なので確認しておくといい。(未検証)
移動と回転を分離
例えばHoudini上ではこういうVEXを使用することで、MatrixをTranslateとRotateに分解してカメラのxform処理にできる。
#include <usd.h>
//
vector t,r,s,pivot;
string xform_ops[] = usd_transformorder(0, @primpath);
string xform_name = xform_ops[0];
//get primitive world transform
matrix m = usd_worldtransform(0, @primpath);
cracktransform(XFORM_SRT, XFORM_XYZ, {0,0,0}, m, t, r, s);
//reset transform
usd_cleartransformorder(0, @primpath);
usd_blockattrib(0, @primpath, xform_name);
//set transform, rotate
usd_addtranslate(0, @primpath, "cam", t);
usd_addrotate(0, @primpath, "cam", USD_ROTATE_XYZ, r);
こうすることで本来の4x4 MatrixがxformOp:translateとxformOp:rotateXYZに分離して処理される。

この状態あればxformOp:translateにMutiplyでMeter per unitの変換分の値を与えてあげればカメラの移動だけunitに合わせた値になる。
こちらの方がDCCごとに後処理でMerter per unitごとに合わせる場合に合わせやすいはず。
もちろんワークフロー上、HDAなどで内部処理してしまえばMatrixが面倒だなとかはわからなくはなるんですが、問題が起きた時などにこちらの方が確実に値が比較できます。
Camera Scale(Over Scan)はどうする?
画ぶれなどを足すためにレンダリングする際に一回り大きくレンダリングしたりすることがあるはず。
Mayaの場合はこれをCamera Scaleのアトリビュートを変更して再現していることがまだ多いような気がする。(3dsMaxはちょっとわからないです)
これも基本的には他のDCCへ持っていった場合に正しく再現されない。
なのでワークフローが整っている会社であればこのカメラスケール分の乗数をレンダリング解像度とAperture Sizeをスケールしてカメラスケールに頼らない値で処理していると思う。
が、これも本来のカメラの値がわからなくなってしまったり、受け取った側でこのレンダリング素材だけちょっとさらに大きくレンダリングしたいなどといった場合に比率が倍の倍になるので計算が非常にめんどくさくなる。
このカメラスケールでの処理はレンダリング時のOpenEXRの仕様にあるData Windowとしてカメラスケール分をレンダリングする方が綺麗なデータになり、カメラだけを受け取った側もわかりやすいデータになる。
KarmaではKarma Render Settingsのこの部分にある。

蛇足
ここから先はこういうのが好きな人向け
実際の変換処理はどこにあるのか?
$HH/husdplugins/このフォルダ以下にHoudiniからSolarisへ持ち込んだ場合の各種変換処理などが入っています。
objtranslators
このフォルダはHoudini内で作成した場合の変換処理が入っていて、
中にあるhoudini.pyに以下のような
vg_api = husd.UsdHoudini.HoudiniViewportGuideAPI.Apply(prim)
self.populateAttr(vg_api.CreateHoudiniGuidescaleAttr(),
self._node.parm('iconscale'))
なんかこの辺でObj階層のicon scale読んできてるのかな?とか
UsdHoudini〜API系はこの辺にドキュメントがあったりなかったり。
cam.pyを見ると
# A few of the parameters need to be converted into 1/10 scene unit space stage = prim.GetStage() factor = husd.utils.convertFromMillimetersToCameraUnits(stage, 1.0)
# Start by grabbing a few values we'll use to scale the attributes aperture = self._node.parm('aperture').eval() self.populateAttr(cam.CreateHorizontalApertureAttr(), self._node.parm('aperture'), lambda value: value * factor * win_size[0]) self.populateAttr(cam.CreateVerticalApertureAttr(), self._node.parm('aperture'), lambda value: value * aspect * factor * win_size[1])
この辺でFocal LengthやAperture Sizeを変えているんだな?というのがわかります。
husd
$HFS/houdini/python3.11libs/husd/ここにはHelpには記載のないSolarisで内部的に使われている関数が入ってるので、
例えば、utils.py
def getMetersPerUnit(stage): """ Returns the meters per unit value of the supplied stage. ~~~~~ def convertFromMillimetersToCameraUnits(stage, mmvalue): """ Returns a numeric value scaled from a representation in mm to a representation in 1/10 stage units (for example, if stage units are m-based then this function applies a 0.01 scaling; if stage units are cm-based then this function applies a 1.0 scaling). ~~~~~ def convertFromCameraUnitsToMillimeters(stage, value): """ Returns a numeric value scaled from a representation in 1/10 stage units to to a representation in mm (for example, if stage units are m-based then this function applies a 100.0 scaling; if stage units are cm-based then this function applies a 1.0 scaling). ~~~~~
今回みたいなケースで変換するのに便利そうな関数があるっぽいぞ!とかいうことを知ることができます。