BlenderをPythonで操作するシリーズ第4回です。
前回はこちら → Blenderのカメラを操作しよう
前回はカメラの位置を操作したり、クリッピング面について勉強したりしました。今回はカメラの撮影方向の制御方法と、カメラの注視点を指定して適切な角度を算出する方法について説明します。
※ 2019/08/17 追記
記事の内容をBlender 2.80に対応させました。
Blenderの座標系
前回特に触れませんでしたが、3D空間上で座標を扱う時はその座標がどの座標系でのものなのかを意識する必要があります。座標系にはワールド座標系とローカル座標系がありますが、詳しい説明はここでは割愛します。
また、座標軸にも注意が必要です。手をフレミングの左手の法則の形にして、親指を \(x\) 軸、人差し指を \(y\) 軸、中指を \(z\) 軸と見立てた時に、右手と左手のどちらの手の形と合うかどうかで、右手系と左手系とそれぞれ名前がついています。
さらに \(y\) 軸を高さ方向に取るのか、\(z\) 軸を高さ方向に取るのかで、Y-upやZ-upと呼び方が変わります。
確認してもらえれば分かりますが、BlenderはZ-upの右手座標系になります。
カメラを回転させる
前回、カメラの位置を変更させる方法を説明しましたが、より柔軟にレンダリングするためには回転動作が必要不可欠です。
カメラを回転させるにはbpy.data.objects
から取得できるカメラオブジェクトの、rotation_euler
プロパティに値をセットすれば良いです。セットする値はXYZオイラー角を表す要素数3のリストになります。リストでなくても3つの浮動小数点値のセットなら大丈夫かもしれません。タプルとndarray
は大丈夫なのを確認しました。
rotation_mode
プロパティの値を変更することで、別の系のオイラー角で指定することができます。このプロパティの初期値はXYZ
で、XYZオイラー角を意味します。オイラー角の説明はここでは割愛します。
カメラを注視点で制御する
カメラを回転させられるようになったので、自由自在にいろいろな場所をレンダリングできるようになったわけですが、どこか特定の位置座標を中心に捉えたい時に角度で制御するのは大変そうですよね。
そこで、中心に捉えたい点のワールド位置座標と現在のカメラのワールド座標から、rotation_euler
プロパティにセットするべき値を計算する方法を考えてみます。
そのためにはまず、全て \(0^\circ\) の状態を確認して、それぞれの軸の回転がどの軸を基準にしているか確認する必要があります。
Blenderを起動してカメラを選択し、右側にあるコントロールパネルの内の以下の画像の赤枠の部分を変更することで、回転の角度を変更することができるので、色々値を変えてどの軸の回転によってカメラがどの様に変わるのか確認してみて下さい。

確認できましたか?確認できましたら、注視点の位置座標がカメラを原点にしたローカル座標系で \((X, Y, Z)\) である時、以下のような画像の状態にあることが分かると思います。

\(\alpha\) は \(x\) 軸周りの回転角度を、\(\gamma\) は \(z\) 軸周りの回転角度をそれぞれ表す。
ここで、\(\alpha\) と \(\gamma\) をそれぞれどの様に計算すればよいか考えていきましょう。
まずは \(\alpha\) から考えていきます。以下のように \(z\) 軸と注視点へのベクトルが作る平面を抜き出して考えます。

\(\alpha\) を
arctan2
を用いて計算しようとする時に対応する軸の方向を表しています。ここで、\(\alpha\) をarctan2()
を用いて計算しようとすると、\(\alpha\) は \(z\) 軸を \(0^\circ\) の基準として時計回りに正の方向となっているので、arctan2
に渡す引数の値に対応する軸はそれぞれ、図中の小豆色の \(x\) 軸と、若緑色の \(y\) 軸と見なせます。
従って、引数として渡すy
とx
にはそれぞれ以下の値を渡せば良いことが分かります。
\[
\begin{align}
y &= \sqrt{X^2 + Y^2} \\
x &= -Z
\end{align}
\]
\(x\) 軸と \(z\) 軸の向きが逆になっていることで、注視点の \(z\) 座標の値を \(-1\)倍して渡していることに注意して下さい。
同じ様に今度は \(\gamma\) を計算する方法を考えます。考える必要があるのは \(x-y\) 平面です。

\(\gamma\) を
arctan2
を用いて計算しようとする時に対応する軸の方向を表しています。\(\gamma\) の \(0^\circ\) の基準となるのは \(y\) 軸で、こちらも時計回りに正の方向なので、arctan2
に渡す引数の値に対応する軸はそれぞれ、図中の小豆色の \(x\) 軸と、若緑色の \(y\) 軸と見なせます。
従って、引数として渡すy
とx
にはそれぞれ以下の値を渡せば良いことが分かります。
\[
\begin{align}
y &= -X \\
x &= Y
\end{align}
\]
以上で \(\alpha\) と \(\gamma\) を計算するための式は揃いましたが、私はnumpy
で一度に計算するために以下のようにベクトル形式に纏めました。
\[
\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)\) の大きさを表しています。
以上を踏まえて以下のようなプログラムを書いてみました。
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)\) に注目した画像をレンダリングします。画像を全て貼るのは大変なので、ぜひご自分の環境で試してみて下さい。
レンダリングの解像度
これまで何度か画像をレンダリングしてきましたが、生成された画像の解像度の確認をした方は居ますか?確認してもらえれば分かりますが、恐らく \(960 \times 540\) になっていると思います。
※ 2019/08/17 追記
Blender 2.80から以下で説明するresolution_percentage
プロパティのデフォルト値が100
になりました。従って特に設定することなく標準で生成される画像は \(1920 \times 1080\) になります。
では、カメラの設定値がこれらの値になっているかというと、そういう訳でもないようです。
import bpy
render_settings = bpy.data.scenes['Scene'].render
print(render_settings.resolution_x)
print(render_settings.resolution_y)
上のコードを試すと、Full HDである \(1920 \times 1080\) になっていることが確認できると思います。
実は、上記コードのrender_settings
に含まれているresolution_percentage
プロパティの値によって、レンダリングの解像度が半分にされていました。
従って、以下のコードをレンダリング前に実行することで、設定した解像度でレンダリングできるようになります。
bpy.data.scenes['Scene'].render.resolution_percentage = 100
今回は以上になります。
実はBlenderのカメラには、指定したオブジェクトをトラッキングする機能があるので、それを使えばカメラの注視点を手動で操作する必要は無くなるのですが、オブジェクトが無い空間のどこかを注視点にしたい時は自分で角度を計算したりする必要があります。そのような際に、今回紹介した方法を活用してもらえれば良いかなと思います。
次回は光源について説明します。