Blender to Arnold: 開発初期のコード解析から見えてくること
はじめに
Luna Digital社が中心となって、BlenderからArnoldを使えるようにするアドオン(BtoA)を開発中です。 まだアルファ版の段階です。
BtoAの開発が開始された初期の頃のコードを解析した結果、以下が分かりました。
- レンダリング対象のモデル、カメラ、照明などをハードコードしてテスト的にレンダリングしていた
- ArnoldのPython APIを抽象化するコードは、まだ存在せず、Python APIをそのまま使用していた
つまり、ArnoldのPython APIをはじめて触れる開発者にとって、理解しやすいコードになっていました。
今回は、BtoAの開発が開始された最初の頃のコードを復元して、そのコードで画像をレンダリングしてみます。 最後に、BtoA開発で必要になる作業とは何なのかも考えてみます。
BtoAのインストール
BtoAは、現状Blender 2.8系しかサポートされていないようです。 そこで、2.83 LTS版を使用することにします。 githubからコードをcloneして、Blenderからアドオンとして認識できるようにシンボリックリンクを作成します。
% mkdir ~/projects % cd ~/projects % git clone https://github.com/lunadigital/btoa.git % ln -s /Users/yoshinori_sano/projects/btoa /Users/yoshinori_sano/Library/Application\ Support/Blender/2.80/scripts/addons/btoa
Arnold SDKをインストールし、BtoAの設定画面を開いてArnold SDKのパスを指定します。
BtoAのコードリーディング
BtoAは、アルファ版でまだコミット数も少ないです。 開発初期のコミットを順番に読んでいくと、いきなり途中段階のコードがgitに追加されたのではなく、少しずつ順番に開発されていることが分かりました。
このコミットで、ArnoldのPython API経由でレンダリングする実験的なコードが追加されました。 github.com
そこで、このコードに遡って、コミットされた当時の状態を再現してみることにします。
% git checkout ece49981dfd55b8ae0e2ccb2cb9bbb43f1ffb00b
Blenderを再起動して、Render Properties > Render Engine > Arnoldに変更します。 そして、Render > Render Image を実行します。 ですが、期待通りに動きませんでした。
コミットログにはホームディレクトリにレンダリング結果が書き込まれるとあるのですが、ローカル環境では権限の関係でうまく書き出されなかったので、/tmp以下に書き込まれるように修正しました。
% git diff diff --git a/engine/__init__.py b/engine/__init__.py index aa00af9..9e0ebd3 100644 --- a/engine/__init__.py +++ b/engine/__init__.py @@ -58,7 +58,7 @@ class ArnoldRenderEngine(bpy.types.RenderEngine): driver = arnold.AiNode("driver_jpeg") arnold.AiNodeSetStr(driver, "name", "jpegDriver") - arnold.AiNodeSetStr(driver, "filename", "myFirstRender.jpg") + arnold.AiNodeSetStr(driver, "filename", "/tmp/myFirstRender.jpg") filter = arnold.AiNode("gaussian_filter") arnold.AiNodeSetStr(filter, "name", "gaussianFilter")
もう一度同じ操作をすると、以下のファイルがレンダリングされました。
コードの詳細
engine/init.py を読んでいきます。
Blenderに新たなレンダリングエンジンを認識させるには、bpy.types.RenderEngine
を継承したクラスを実装すれば良いようです。
# For more info, visit: # https://docs.blender.org/api/current/bpy.types.RenderEngine.html class ArnoldRenderEngine(bpy.types.RenderEngine):
コードにコメントされている以下のURLには、bpy.types.RenderEngine
の継承のやり方の解説が書かれています。
docs.blender.org
Blenderでレンダリングを実行すると、このrender()
が呼ばれます。
depsgraph
から、レンダリング対象の幅と高さを取得します。
その後は、ArnoldのPython APIを使って、Arnoldがレンダリングに必要なモデル情報のジオメトリ、そのジオメトリのマテリアル、カメラ、照明などを構築していきます。
その他、レンダリングに必要な情報をセットした後、arnold.AiRender()
でレンダリングが実行されます。
class CustomRenderEngine(bpy.types.RenderEngine): [...] def render(self, depsgraph): scene = depsgraph.scene scale = scene.render.resolution_percentage / 100.0 self.size_x = int(scene.render.resolution_x * scale) self.size_y = int(scene.render.resolution_y * scale) arnold.AiBegin() sphere = arnold.AiNode("sphere") arnold.AiNodeSetStr(sphere, "name", "mysphere") arnold.AiNodeSetVec(sphere, "center", 0, 4, 0) arnold.AiNodeSetFlt(sphere, "radius", 4) shader = arnold.AiNode("standard_surface") arnold.AiNodeSetStr(shader, "name", "redShader") arnold.AiNodeSetRGB(shader, "base_color", 1, 0.02, 0.02) arnold.AiNodeSetFlt(shader, "specular", 0.05) arnold.AiNodeSetPtr(sphere, "shader", shader) camera = arnold.AiNode("persp_camera") arnold.AiNodeSetStr(camera, "name", "Camera") arnold.AiNodeSetVec(camera, "position", 0, 10, 35) arnold.AiNodeSetVec(camera, "look_at", 0, 3, 0) arnold.AiNodeSetFlt(camera, "fov", 45) light = arnold.AiNode("point_light") arnold.AiNodeSetStr(light, "name", "pointLight") arnold.AiNodeSetVec(light, "position", 15, 30, 15) arnold.AiNodeSetFlt(light, "intensity", 4500) arnold.AiNodeSetFlt(light, "radius", 4) options = arnold.AiUniverseGetOptions() arnold.AiNodeSetInt(options, "AA_samples", 8) arnold.AiNodeSetInt(options, "xres", self.size_x) arnold.AiNodeSetInt(options, "yres", self.size_y) arnold.AiNodeSetInt(options, "GI_diffuse_depth", 4) arnold.AiNodeSetPtr(options, "camera", camera) driver = arnold.AiNode("driver_jpeg") arnold.AiNodeSetStr(driver, "name", "jpegDriver") arnold.AiNodeSetStr(driver, "filename", "/tmp/myFirstRender.jpg") filter = arnold.AiNode("gaussian_filter") arnold.AiNodeSetStr(filter, "name", "gaussianFilter") outputs = arnold.AiArrayAllocate(1, 1, arnold.AI_TYPE_STRING) arnold.AiArraySetStr(outputs, 0, "RGBA RGBA gaussianFilter jpegDriver") arnold.AiNodeSetArray(options, "outputs", outputs) arnold.AiRender(arnold.AI_RENDER_MODE_CAMERA) arnold.AiEnd() [...]
結局、BtoA開発で必要になる作業とは何なのか?
Blenderでモデリングされたジオメトリ、マテリアル、カメラ、照明などレンダリングに必要な情報を、Arnoldが理解できるデータ構造やAPI呼び出しの形にひたすら整えて行く。 この作業が、BtoA開発の大半で必要な作業になると予想します。
例えば、メッシュのレンダリングは以下で実装されました。ここでも、Blender上にあるシーンのメッシュ情報をArnoldのデータ構造やAPI呼び出しに対応付けています。
それらに加えて、ユーザーからArnoldの機能をアクセスするための、UIの実装ももちろん必要になると思います。
参考情報
ArnoldをPython APIから使用する際に必要になるリファレンスです。
APIリファレンス
このBlogによると、Arnold SDKのC++ APIリファレンスを参照とのことです。APIは基本的に一対一対応しているそうです。
For documentation, you use the Arnold SDK documentation. It’s for the C++ API, but the Python API is basically a one-to-one wrapper around the C++ API.
ノードリファレンス
ArnoldのPython APIでノードを構築していく際、こちらのノードリファレンスが参考になります。
例えば、上記のパースペクティブありのカメラ(persp_camera
)の仕様は以下にあります。
開発の際は、この仕様書に記載されているパラメータを参照しながら、ノードにセットしていくことになります。