8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HoudiniAdvent Calendar 2024

Day 11

Copernicusノードからカメラ情報を取得し、カメラ光線を求める

Last updated at Posted at 2024-12-10

はじめに

Copernicusノードを使用した時、 カメラの情報ってどうやって取得するんだろう? それができればカメラ光線が定義できるのでWrangle COPでintersect関数を使用して光線を飛ばせるのにと思い、今回はその方法を紹介したいと思います。

シーンの平面について

カメラから見たシーンの平面の素材を書き出す場合の基本的なCopernicusノードのネットワークは図のような構成になると思います。
まず、このようなネットワークでビューポートに表示される平面について見ていきます。

Copernicusネットワーク

  1. Camera Import COPを使用して、カメラを取り込む。
  2. SOP Import COPを使用して、シーンを取り込む。
  3. Rasterize Setup COPを使用して、そのシーンのジオメトリをラスター化するための事前準備をする。
  4. Rasterize Geometry COPを使用して、ジオメトリをラスター化する。

Copernicusが搭載されてSolarisと同様にScene Importがあるのかなと思ってたのですが、COPコンテキストにシーンを取り込むのはSOP Import COPしかなさそうです。
Scene Import COPみたいなのあったら嬉しいです。
現状だと、SOP Import COPの中でObject Merge SOPを追加して、
Object1パラメータに、

エクプレッション
`run("opfind -p ルートパス -n * -t geo")`

ObjectMerge
のようにすることで、指定したルートパス以下のすべてのジオメトリを読み込むことができます。

これでRasterize Gemetry COPでP平面を出力してみたのですが、

image.png

そのRasterize Gemetry COPが表示する平面からカメラまでの距離って何なのか気になりませんか?
自分は気になったので調べました。この距離は以下の式で表現されていました。

カメラからこの平面までの距離d
d = Focal Lenght*2.0 / Aperture

次に、この平面からカメラの情報を取得したいです。
それはカメラノードを参照すれば情報が取得できるだろと言われそうですが、
COPコンテキスト内だけで完結させてカメラ情報を取得したい のです。

ネットワークエディタでCamera Import COPノードを中クリックしてノードInfoを表示すると、
image.png
COPノード自体がカメラ情報を所有してることがわかりました。
この情報をパラメータエクスプレッションとかWrangleとかOpenCLとかで利用したいのですが、どうやって取得すればよいのか随分悩みました。
結局、PythonでノードのinfoTree (https://www.sidefx.com/ja/docs/houdini/hom/hou/NodeInfoTree.html) の ブランチのブランチ から取得できました(取得する方法に悩んだのは、まさかブランチのブランチに情報が隠れてたとは思いませんでした)。

Infoツリー情報を返すPythonコード
output = hou.node("Camera Importノードのパス").infoTree().branches()["COP Info"].branches()["camera_ref"].rows()

#結果
(('camera_ref', 'layer, use_count 3'), ('dataWindow', '1280×720'), ('buffer', '1280
×720×1, float32, wrap, constant values, onCPU'), ('camera', 'persp, aperture: 41.4
214, focal: 50, camera: (0, 0, 2.41421), fstop: 5.6, focus: 5 shutter: -0.25-0.25'),
 ('xform', '[0.8925389352890299, 0.15737869562426265, 0.42261826174069944, 0, 0.0577
1513718295637, 0.8895619774524623, -0.4531538935183249, 0, -0.4472619053005489, 0.42
884896459531124, 0.7848855672213958, 0, -1.9202153352182654, 2.964668061398222, 3.10
5120536646928, 1]'))

ここで注意すべき事は、この xform はカメラのトランスフォーム行列ではなく、 シーン平面のトランスフォーム行列 です。

カメラのトランスフォーム行列とApertureとFocal Lengthを取得するパラメータエクスプレッションを作成する

今回は、Wrangle COP上に、カメラのトランスフォーム行列とApertureとFocalLengthの値が文字列として格納されるSpareパラメータを作成したいと思います。

  1. Wrangle COP上にStringタイプのパラメータを追加、名前をcameInfoにします。
    image.png
  2. "camInfo" SpareパラメータのテキストフィールドでAltを押しながらEキーを押してエディタを開きます。
    image.png
  3. エクスプレッション言語をPython Expressionに変更して、以下のエクスプレッションを入力します。
    image.png
カメラのトランスフォーム行列とApertureとFolcal Lengthを文字列で返すPythonエクスプレッション
outputBranches = hou.node("../Camera_Import").infoTree().branches()["COP Info"].branches()
cameraRef = None
for i in outputBranches:
        cameraRef = outputBranches[i]
        break
cameraInfo = dict(cameraRef.rows())

output = ""
for i in cameraInfo["xform"].strip("[]").split(","):
        output+= i.strip() + " "

aperture = ""
focal = ""
for i in cameraInfo["camera"].split(","):
        if "aperture" in i:
                aperture = i.split(":")[-1].strip()
        elif "focal" in i:
                focal = i.split(":")[-1].strip()
        if aperture!="" and focal!="":
                break

output += aperture+" "+focal

return output
トランスフォーム行列16個のfloatとApertureとFocal Lengthをスペースで区切った文字列
0.8925389352890299 0.15737869562426265 0.42261826174069944 0
0.05771513718295637 0.8895619774524623 -0.4531538935183249 0
-0.4472619053005489 0.42884896459531124 0.7848855672213958 0
-1.9202153352182654 2.964668061398222 3.105120536646928 1
41.4214 50

これでCOPコンテキスト内でカメラ情報を取得し、Spareパラメータとして参照できるようになりました。

Wrangle COPでintersect関数をカメラ光線として使用する

Wrangle COPでは、@P@ix@iy@resx@resyとか使用できます。
@ix@iyは平面のX方向、Y方向の1ピクセル単位の位置です。
@resx@resyは平面のX方向、Y方向の解像度です。
@Pはシーン平面のグローバル座標です。
ということは、@Pからカメラ原点座標を引けばカメラ光線の方向が求まります。
今回は、Rasterize Geometry COPを使用せずに、Wrangle COPを使用してP平面を定義したいと思います。

Wrangle COPには上記の"camInfo"Spareパラメータとエクスプレッションを設定した上で、
以下の2つの入力を用意して
image.png
以下のコードを入力します。

カメラから@Pに向かったカメラ光線がオブジェクトに当たった位置を出力する
string camInfo = chs("camInfo");
string tokens[]=split(camInfo);

matrix mat =ident();
float transform[]={1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
for(int i=0;i<16;i++){
    transform[i]= atof(tokens[i]);
}
mat = set(transform);
float focal_length = atof(tokens[-1]);
float aperture = atof(tokens[-2]);

vector cameraPos = {0.0,0.0,0.0};
cameraPos+=set(0.0,0.0,focal_length*2/aperture);
cameraPos*=mat;
vector dir = normalize(@P - cameraPos);

vector pos;
vector uv;
int prim = intersect(1, cameraPos, 10000.0*dir, pos, uv);
if (prim>-1) {
    @C =pos;
} else {
    @C = 0.0;
}

image.png
このノード構成だけで、P平面を表現することができました。

image.png

シーン平面をXY平面に置いた場合

シーン平面をカメラのフラスタム領域に配置するよりは、Match Camera COPを使用してシーン平面を常にXY平面に置きたいことでしょう。

image.png

そうした場合、カメラ光線は、上記のWrangleコードでは動作しません。
image.png

XY平面のP座標とカメラのXYZ軸の関係からカメラ光線を計算する必要があります。
これは、Houdiniのドキュメント( https://www.sidefx.com/ja/docs/houdini/ref/cameralenses.html )に載っている図のようにFocal LengthとApertureの関係性から求めることができます。

fov.png

コードは以下のようになります。

シーン平面をMatch Cameraでトランスフォームさせた場合のカメラ光線
string camInfo = chs("camInfo");
string tokens[]=split(camInfo);

matrix mat =ident();
float transform[]={1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
for(int i=0;i<16;i++){
    transform[i]= atof(tokens[i]);
}
mat = set(transform);

float focal_length = atof(tokens[-1]);
float aperture = atof(tokens[-2]);
float theta = atan(0.5f*aperture/focal_length);

vector cameraPos = {0.0,0.0,0.0};
cameraPos+=set(0.0,0.0,focal_length*2/aperture);
cameraPos*=mat;

float distance = -0.5f/tan(theta);
matrix3 rot = (matrix3)mat;
vector camX = rot*{1,0,0};
vector camY = rot*{0,1,0};
vector camZ = rot*{0,0,1};
vector dir = normalize(0.5*@P.x*camX + 0.5*@P.y*camY + distance*camZ);

vector pos;
vector uv;
int prim = intersect(1, cameraPos, 10000.0*dir, pos, uv);
if (prim>-1) {
    @C =pos;
} else {
    @C = 0.0;
}

すると、以下の図のように正しい挙動になります。
image.png

P平面だけじゃ寂しいので、intersect関数を使用してコンターを求めてみました。
2024-12-09 23_49_19-Window.png

シーンファイル:
https://drive.google.com/file/d/1F-TC9pyAd3lC-r_YargDbXKtfExoy3uY/view?usp=drive_link

検証環境:
Houdini20.5.410Py3.11

まとめ

  • SOP Import COPでもシーン上のジオメトリをまるごとインポートする方法があるよ。
  • CopernicusノードからPythonエクスプレッションを使用することでカメラ情報を取得することができるよ。
  • Rasterize Geometry COPはパックプリミティブに対して動作しなかった。代わりにWrangle COPでintersect関数を使って光線を飛ばすことでパックプリミティブに対してP平面を出力することができたよ。
8
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?