Blenderから自作レンダラー向けに設定をエクスポート
こんにちは。 このページは レイトレ合宿6 の記事です.
今年もレイトレ合宿の日が近くなってきて どんなシーンをレンダリングしようかと考えている頃ではないでしょうか。 そこで、 1 Blenderで扱うシーンの設定を自作のレンダラー向けにエクスポートできるように してみます。 Blenderからエクスポートすることの利点は、
- Blenderという強力なGUIツールを用いて効率良くシーンを作成できる
- Cyclesレンダラーなどの他のエンジンと同じ設定でレンダリングすることで、レンダリング結果を比較できる
などが挙げられます。
デモファイルのダウンロード
設定をエクスポートするに当たって、 Blenderの公式サイトで配布されている ヘリコプターデモ “scene-Helicopter-27.Blend” を用います。
以下の画像の場所からダウンロードできます。感謝!。
Blenderの設定
設定をエクスポートする前に、Blenderの設定を確認します。
1. RenderEngineの設定
使用するレンダリングエンジンを選択します。 ここは Cycles Render を選択します (ヘリコプターデモの場合は既にCycles Renderが選択されています)。
2. Dimensionsの設定
出力する画像の解像度を設定します。 Resolution の項目を100%にし、XとYに画像の解像度を設定します。
3. Samplingの設定
Pattern で使用する乱数のサンプラーを選択し、 Settings の S に乱数のシード値を指定しします。 Samples の Render に1ピクセル当りのレイのサンプル数を指定します。
4. LightPathsの設定
様々な経路長のパスを考慮するため、Full Global Illumination を選択します。
5. PostProcessingの設定
画像処理前のレンダリング画像を取得するため、 Post Processingのチェックを全て外しておきます。
6. Surfaceの設定
Cyclesエンジンの初期設定で、うっすらとした環境光が設定されている場合があります。 意図しない光源が設定されていると比較の際に困るので、 Background の Strength が 0 になっているか確認しておきます。
7. Performanceの設定
パフォーマンスの比較を行う場合は、Threads で使用するスレッド数を指定します。
リファレンスの作成
以上の設定を確認してレンダリングを行います。
自作レンダラーとの比較のために、マテリアルの設定をもう少しシンプルにしておきます。
これで、設定をエクスポートする準備が整いました。
Blenderエクスポーターの作成
Blenderから設定をエクスポートするツールを作成します。
BlenderはPythonインタプリタを内蔵しており、 Blender内でPythonスクリプトを実行することができます。 そこで、Blenderから設定をエクスポートするPythonスクリプトを作成してみます。
Pythonスクリプトの実行
Pythonスクリプトは、Blender起動時に引数として渡して実行することができます。 試しにスクリプトを実行してみます。 以下は.Blendファイルのファイル名を表示するスクリプトです。
#
# exporter1.py
#
import bpy
file_name = bpy.path.display_name_from_filepath(bpy.data.filepath)
print("## FileName: {0}".format(file_name))
端末を開いて、Blenderに引数として渡して実行します。
% blender --background Helicopter.blend --python exporter1.py
Read blend: Helicopter.blend
## FileName: Helicopter
Blender quit
Blenderの実行ファイルの場所は、Windowsなら C:\Program Files\Blender Foundation\Blender\blender.exe
、 macOSなら/Applications/Blender/blender.app/Contents/MacOS/blender
にあると思います。
--background
オプションを指定すると、BlenderのGUIを起動せずに Pythonスクリプトを実行することができます。
実行する際のポイントは、Helicopter.blend
よりも後に--python exporter1.py
を指定することです。 Helicopter.blend
の設定が読み込まれた後にスクリプトが実行されます。
スクリプト内でimport bpy
を実行することで、BlenderのAPIを利用できるようになります。 利用できるAPIの詳細は Blender Documentation Python API で参照できます。
シーンデータの取得
.Blendファイルを読み込んだ際の各種設定は、 bpy.data
に記録されます (bpy.data
の構造については BlendData を参照して下さい)。
各シーンの情報については bpy.data.scenes
に記録されています。 (bpy.data.scenes
の構造については Scene を参照して下さい)。
特定のシーンデータを取得する場合はシーン名を指定することで取得できます。 シーン名は Outliner で確認できます。
以下はSceneという名前のシーンから情報を取得するスクリプトの例です。
#
# exporter2.py
#
import bpy
# Get a scene
scene_name = "Scene"
scene = bpy.data.scenes[scene_name]
# Get a image resolution
x = scene.render.resolution_x
y = scene.render.resolution_y
print("Image resolution: {0} x {1}".format(x, y))
# Get a sampler type
sampler = scene.cycles.sampling_pattern
print("Sampler: {0}".format(sampler))
# Get a sampler seed
seed = scene.cycles.seed
print("Sampler seed: {0}".format(seed))
# Get the number of threads
threads = scene.render.threads
print("Threads: {0}".format(threads))
# Get a render samples
samples = scene.cycles.samples
print("Render samples: {0}".format(samples))
最初に、 scene = bpy.data.scenes[scene_name]
で欲しいシーン情報を取得し、 シーンの各種情報を取得しています。 上のスクリプトでは、情報を標準出力に出力しているだけですが、 実際には自作レンダラーのフォーマットに加工してファイルに出力します。
実行結果は以下のようになります。
% blender --background Helicopter.blend --python exporter2.py
Read blend: Helicopter.blend
Image resolution: 1920 x 1080
Sampler: CORRELATED_MULTI_JITTER
Sampler seed: 123456789
Threads: 4
Render samples: 512
Blender quit
もし欲しい情報のAPIがわからない場合は、 BlenderのGUI上でその情報を編集するためのボタンやメニューの上に マウスカーソルを置いてみて下さい。 しばらくするとポップアップが現れて、その情報を利用するためのAPIを表示してくれます。
カメラ
シーンのアクティブカメラの情報は、scene.camera
に入っています。
#
# exporter3.py
#
import bpy
# Get a scene
scene_name = "Scene"
scene = bpy.data.scenes[scene_name]
# Get an active camera
camera_obj = scene.camera
# Get rotation angles
for axis in camera_obj.rotation_euler.order:
axis_index = ord(axis) - ord('X')
angle = camera_obj.rotation_euler[axis_index]
print("{0} axis rotation: {1} radian.".format(axis, angle))
# Get a camera position
location = camera_obj.location
print("position: ({0}, {1}, {2})".format(location[0], location[1], location[2]))
# Get a field of view
print("Horizontal field of view: {0}".format(camera_obj.data.angle_x))
print("Vertical field of view: {0}".format(camera_obj.data.angle_y))
scene.camera
自体は Object 構造になっており、 カメラの位置や回転などの情報が入っています。 scene.camera.data
にはカメラの画角やレンズなどの情報が入っています (Camera を参照して下さい)。
実行結果は以下のようになります。
% blender --background Helicopter.blend --python exporter3.py
Read blend: Helicopter.blend
X axis rotation: 4.169113636016846 radian.
Y axis rotation: 3.1490345001220703 radian.
Z axis rotation: 3.9584245681762695 radian.
position: (1.483912467956543, -1.3075470924377441, 1.6609470844268799)
Horizontal field of view: 0.8575560450553894
Vertical field of view: 0.5033799409866333
Blender quit
オブジェクト
シーン内のオブジェクトのデータは scene.objects
に入っています (各オブジェクトの構造は Object を参照して下さい)。 例えば、シーン内のオブジェクト名を全て表示するスクリプトは、
#
# exporter4.py
#
import bpy
# Get a scene
scene_name = "Scene"
scene = bpy.data.scenes[scene_name]
# Display object names
for obj in scene.objects:
print(obj.name)
のようになります。
試しに上画像のOutlinerのシーンで実行してみると、
blender --background TestScene.blend --python exporter4.py
Read blend: TestScene.blend
ObjectB
ObjectA
ObjectA-2
ObjectA-1
Lamp
Camera
Blender quit
scene.objects
にはオブジェクト同士の親子関係に関わらず全てのオブジェクトが 入っています。 親子関係を考慮する場合は、
#
# exporter5.py
#
import bpy
# Get a scene
scene_name = "Scene"
scene = bpy.data.scenes[scene_name]
def printChildObjects(objects):
for obj in objects:
print(" child node: " + obj.name)
# Display object names
for obj in scene.objects:
if obj.parent == None:
print(obj.name)
printChildObjects(obj.children)
のように書くことができます。 obj.parent
には親となるオブジェクトが入っており (トップレベルの場合は None)、 obj.children
は子となるオブジェクトの配列(子がいない場合は要素を持たない配列) となっており、これらを用いて親子階層を意識した処理を書くことができます。
実行結果は
% blender --background TestScene.blend --python exporter5.py
Read blend: TestScene.blend
ObjectB
ObjectA
child node: ObjectA-1
child node: ObjectA-2
Lamp
Camera
Blender quit
となります。
次は、オブジェクト内の情報をエクスポートしてみます。
#
# exporter6.py
#
import bpy
# Get a scene
scene_name = "Scene"
scene = bpy.data.scenes[scene_name]
# Display object names
for obj in scene.objects:
if not obj.type in {'MESH', 'CURVE', 'SURFACE'}:
continue
print(obj.name)
# Get the active material of an object
material = obj.active_material
if material != None:
print(" Material name: {0}".format(material.name))
print(" Material index: {0}".format(bpy.data.materials.find(material.name)))
# Export a object data to wavefromt format
bpy.ops.object.select_all(action='DESELECT')
obj.select = True
bpy.ops.export_scene.obj( \
filepath = obj.name + ".obj", \
check_existing = True, \
axis_forward = 'Y', \
axis_up = 'Z', \
use_selection = True, \
use_animation = False, \
use_mesh_modifiers = True, \
use_edges = False, \
use_smooth_groups = False, \
use_smooth_groups_bitflags = False, \
use_normals = True, \
use_uvs = True, \
use_materials = False, \
use_triangles = True, \
use_nurbs = False, \
use_vertex_groups = False, \
use_blen_objects = True, \
group_by_object = False, \
group_by_material = False, \
keep_vertex_order = True, \
global_scale = 1.0, \
path_mode = 'AUTO')
obj.type
を見ることで、どんな種類のオブジェクトなのかがわかります (例えば ‘MESH’、‘CURVE’、’CAMERA’など。Type of Object)。 また、この種類の違いによって、 obj.data
の構造も異なります (’MESH’なら Mesh 、’CURVE’なら Curve など)。
bpy.ops.export_scene.obj
はオブジェクトを .obj
形式で書き出すAPIです (Export Scene Operators を参照)。 オブジェクトの種類毎に処理を変えるのは大変なので、 ここでは bpy.ops.export_scene.obj
を用いて ’MESH’や’CURVE’などのオブジェクトは全て .obj
形式に書き出すようにしました。
以下は実行結果の一部を切り出したものです。
% blender --background Helicopter.blend --python exporter6.py
Read blend: Helicopter.blend
...
Bolt+Nut.011
Material name: Metal
Material index: 3
( 0.0409 sec | 0.0000 sec) OBJ Export path: 'Bolt+Nut.011.obj'
( 0.2900 sec | 0.2481 sec) Finished writing geometry of 'Bolt+Nut.011'.
( 0.2964 sec | 0.2550 sec) Finished exporting geometry, now exporting materials
( 0.2964 sec | 0.2550 sec) OBJ Export Finished
Progress: 100.00%
...
Blender quit
オブジェクト“Bolt+Nut.011”が“Bolt+Nut.011.obj”として書き出されました。 自作レンダラーでは、このobjファイルを読み込むように設定をエクスポートしています。
マテリアル
マテリアルの情報は bpy.data.materials
に入っています (各マテリアルの構造は Material を参照して下さい)。 Blenderのマテリアルデータは複雑で、また、自作レンダラーのマテリアルデータとは 取るパラメータも異なるため、 マテリアルデータに関してはエクスポートは行わず後から手動で設定するようにしました。
Cyclesレンダラーと自作レンダラーの比較
エクスポートした設定を自作レンダラーに読み込ませて実際にレンダリングしてみました。
以下はCyclesレンダラーとの比較になります。
上画像がCyclesレンダラーでレンダリングした画像で、 下画像が自作レンダラーでレンダリングした画像です。 カメラ設定やオブジェクトの場所は一致させられたと思います。
しかし、レンダラー間でのマテリアルが取るパラメータの違いや係数の違い、 色空間設定の違いから、 結果を完全に一致させるにはまだ工夫が必要だと感じました。 一応今のままでも簡単な比較には使えそうです。
私のレンダラー向けのエクスポーターは、 blender_scene_exporter.py にあります。 参考にどうぞ。
参考
- Blender Documentation Python API: https://docs.blender.org/api/current/
この記事内で使用したBlenderのバージョンは 2.79b です。↩