カメラの向きを操作しよう

カメラの向きを操作しよう

Python で始める Blender 第4回

公開日: 6/16/2019

更新日: 8/17/2019

読了目安:

11分

更新履歴
  • 2019/08/17: 記事の内容を Blender 2.80 に対応させました。
Blender を Python で操作するシリーズ第 4 回です。
前回はカメラの位置を操作したり、 クリッピング面について勉強したりしました。 今回はカメラの撮影方向の制御方法と、 カメラの注視点を指定して適切な角度を算出する方法について説明します。

Blender の座標系

前回特に触れませんでしたが、 3D 空間上で座標を扱う時は、 その座標がどの座標系でのものなのかを意識する必要があります。 座標系には ワールド座標系 と ローカル座標系 がありますが、 詳しい説明はここでは割愛します。
また、座標軸にも注意が必要です。 手をフレミングの左手の法則の形にして、 親指を xx 軸、人差し指を yy 軸、中指を zz 軸と見立てた時に、 右手と左手のどちらの手の形と合うかどうかで、 右手系と左手系とそれぞれ名前がついています。
さらに yy 軸を高さ方向に取るのか、zz 軸を高さ方向に取るのかで、 Y-up や Z-up と呼び方が変わります。
確認してもらえれば分かりますが、 Blender は Z-up の右手座標系になります。

カメラを回転させる

前回、カメラの位置を変更させる方法を説明しましたが、 より柔軟にレンダリングするためには回転動作が必要不可欠です。
カメラを回転させるには、 bpy.data.objects から取得できるカメラオブジェクトの、 rotation_euler プロパティに値をセットすれば良いです。 セットする値は XYZ オイラー角 を表す要素数 3 のリストになります。 リストでなくても 3 つの浮動小数点値のセットなら大丈夫かもしれません。 タプルと ndarray は大丈夫なのを確認しました。
rotation_mode プロパティの値を変更することで、 別の系のオイラー角で指定することができます。 このプロパティの初期値は XYZ で、XYZ オイラー角を意味します。 オイラー角の説明はここでは割愛します。

カメラを注視点で制御する

カメラを回転させられるようになったので、 自由自在にいろいろな場所をレンダリングできるようになったわけですが、 どこか特定の位置座標を中心に捉えたい時に角度で制御するのは大変そうですよね。
そこで、中心に捉えたい点のワールド位置座標と現在のカメラのワールド座標から、 rotation_euler プロパティにセットするべき値を計算する方法を考えてみます。
そのためにはまず、 全て 0∘0^\circ の状態を確認して、 それぞれの軸の回転がどの軸を基準にしているか確認する必要があります。
Blender を起動してカメラを選択し、 右側にあるコントロールパネルの内の以下の画像の赤枠の部分を変更することで、 回転の角度を変更することができるので、 色々値を変えてどの軸の回転によってカメラがどの様に変わるのか確認してみて下さい。
カメラオブジェクト選択時のプロパティの一部
確認できましたか? 確認できましたら、 注視点の位置座標がカメラを原点にしたローカル座標系で (X,Y,Z)(X, Y, Z) である時、 以下のような画像の状態にあることが分かると思います。
注視点を中心に捉えたカメラの各状態を表す図
図中で、α\alpha は xx 軸周りの回転角度を、γ\gamma は zz 軸周りの回転角度をそれぞれ表しています。 ここで、α\alpha と γ\gamma をそれぞれどの様に計算すればよいか考えていきましょう。
まずは α\alpha から考えていきます。 以下のように zz 軸と注視点へのベクトルが作る平面を抜き出して考えます。
alpha に関係する平面
図中の小豆色の xx 軸と若緑色の yy 軸はそれぞれ、 α\alpha を arctan2 を用いて計算しようとする時に対応する軸の方向を表しています。
ここで、α\alpha を arctan2() を用いて計算しようとすると、 α\alpha は zz 軸を 0∘0^\circ の基準として時計回りに正の方向となっているので、 arctan2 に渡す引数の値に対応する軸はそれぞれ、 図中の小豆色の xx 軸と、若緑色の yy 軸と見なせます。
従って、引数として渡す y と x にはそれぞれ以下の値を渡せば良いことが分かります。
y=X2+Y2x=−Z\begin{split} y &= \sqrt{X^2 + Y^2} \\ x &= -Z \end{split}
xx 軸と zz 軸の向きが逆になっていることで、 注視点の zz 座標の値を −1-1 倍して渡していることに注意して下さい。
同じ様に今度は γ\gamma を計算する方法を考えます。 考える必要があるのは x−yx-y 平面です。
gamma に関係する平面
図中の小豆色の xx 軸と若緑色の yy 軸はそれぞれ、 γ\gamma を arctan2 を用いて計算しようとする時に対応する軸の方向を表しています。
γ\gamma の 0∘0^\circ の基準となるのは yy 軸で、 こちらも時計回りに正の方向なので、 arctan2 に渡す引数の値に対応する軸はそれぞれ、 図中の小豆色の xx 軸と、若緑色の yy 軸と見なせます。
従って、引数として渡す y と x にはそれぞれ以下の値を渡せば良いことが分かります。
y=−Xx=Y\begin{split} y &= -X \\ x &= Y \end{split}
以上で α\alpha と γ\gamma を計算するための式は揃いましたが、 私は numpy で一度に計算するために、 以下のようにベクトル形式に纏めました。
[α0γ]=arctan⁡([∣(X,Y,0)∣0−X],[−Z+0Y])\left[ \begin{array}{c} \alpha \\ 0 \\ \gamma \end{array} \right] = \arctan \left( \left[ \begin{array}{c} |(X, Y, 0)| \\ 0 \\ -X \end{array} \right], \left[ \begin{array}{c} -Z \\ +0 \\ Y \end{array} \right] \right)
∣(X,Y,0)∣|(X, Y, 0)| は 位置ベクトル (X,Y,0)(X, Y, 0) の大きさを表しています。
以上を踏まえて以下のようなプログラムを書いてみました。
import bpy
import numpy as np
import numpy.linalg as LA
 
locations = [(5, 5, 5),
             (-5, 5, 5),
             (-5, -5, 5),
             (5, -5, 5),
             (5, 5, -5),
             (-5, 5, -5),
             (-5, -5, -5),
             (5, -5, -5)]
camera = bpy.data.objects['Camera']
 
def look_at(target):
    ray = np.subtract(target, camera.location)
    ray_xy = np.array([ray[0], ray[1], 0])
    x = np.array([-ray[2], +0, ray[1]])
    y = np.array([LA.norm(ray_xy), 0, -ray[0]])
    camera.rotation_euler = np.arctan2(y, x)
 
def render(filepath='img/', filename='look_at.png'):
    bpy.ops.render.render()
    bpy.data.images['Render Result'].save_render(filepath + filename)
 
look_at_point = [0, 0, 0]
for i, location in enumerate(locations):
    camera.location = location
    look_at(look_at_point)
    render(filename=f'render{i}.png')
16 行目から 21 行目までが今回の肝になる部分です。 17 行目で注視点のカメラのローカル座標系での座標を計算し、 21 行目で α\alpha と γ\gamma を計算して rotation_euler プロパティに設定しています。
このコードを実行すると、 5 行目から 12 行目で定義している 8 種類の位置から、 座標 (0,0,0)(0, 0, 0) に注目した画像をレンダリングします。 画像を全て貼るのは大変なので、ぜひご自分の環境で試してみて下さい。

レンダリングの解像度

2019/08/17 追記
Blender 2.80 から、 以下で説明する resolution_percentage プロパティのデフォルト値が 100 になりました。 従って、特に設定することなく標準で生成される画像は 1920×10801920 \times 1080 になります。
これまで何度か画像をレンダリングしてきましたが、 生成された画像の解像度の確認をした方は居ますか? 確認してもらえれば分かりますが、 恐らく 960×540960 \times 540 になっていると思います。
では、カメラの設定値がこれらの値になっているかというと、 そういう訳でもないようです。
import bpy
 
render_settings = bpy.data.scenes['Scene'].render
print(render_settings.resolution_x)
print(render_settings.resolution_y)
上のコードを試すと、Full HD である 1920×10801920 \times 1080 になっていることが確認できると思います。
実は、上記コードの render_settings に含まれている resolution_percentage プロパティの値によって、 レンダリングの解像度が半分にされていました。
従って、以下のコードをレンダリング前に実行することで、 設定した解像度でレンダリングできるようになります。
bpy.data.scenes['Scene'].render.resolution_percentage = 100

最後に

今回は以上になります。
実は Blender のカメラには、 指定したオブジェクトをトラッキングする機能があるので、 それを使えばカメラの注視点を手動で操作する必要は無くなるのですが、 オブジェクトが無い空間のどこかを注視点にしたい時は自分で角度を計算したりする必要があります。 そのような際に、今回紹介した方法を活用してもらえれば良いかなと思います。
次回は光源について説明します。

Rafka

Rafka

Software Engineer

Rafka って言います。品川でソフトウェアエンジニアやってます。

Limbus Company の攻略と感想、中央競馬 G1 レースの予想、参加したイベントの感想などを書いています。

大学では計算論的神経科学や高性能計算を研究していたので、数値シミュレーションの解説も書いています。

Ace Combat 8 を買いました!シリーズファンなのでめっちゃ楽しんでいます!

関連記事