Blender Hack Blog

オープンソースの総合3DCGソフトウェアのBlenderのコード解析や開発を記録していきます。

Blender to Arnold: 開発初期のコード解析から見えてくること

はじめに

Luna Digital社が中心となって、BlenderからArnoldを使えるようにするアドオン(BtoA)を開発中です。 まだアルファ版の段階です。

BtoAの開発が開始された初期の頃のコードを解析した結果、以下が分かりました。

つまり、ArnoldのPython APIをはじめて触れる開発者にとって、理解しやすいコードになっていました。

今回は、BtoAの開発が開始された最初の頃のコードを復元して、そのコードで画像をレンダリングしてみます。 最後に、BtoA開発で必要になる作業とは何なのかも考えてみます。

github.com

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のパスを指定します。

f:id:fixme:20210307131728p:plain
BtoAの設定

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")

もう一度同じ操作をすると、以下のファイルがレンダリングされました。

f:id:fixme:20210307130547j:plain
Arnoldでのレンダリングの結果

コードの詳細

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呼び出しに対応付けています。

github.com

それらに加えて、ユーザーからArnoldの機能をアクセスするための、UIの実装ももちろん必要になると思います。

参考情報

ArnoldをPython APIから使用する際に必要になるリファレンスです。

APIリファレンス

このBlogによると、Arnold SDKC++ 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.

arnoldsupport.com

docs.arnoldrenderer.com

ノードリファレンス

ArnoldのPython APIでノードを構築していく際、こちらのノードリファレンスが参考になります。

docs.arnoldrenderer.com

例えば、上記のパースペクティブありのカメラ(persp_camera)の仕様は以下にあります。 開発の際は、この仕様書に記載されているパラメータを参照しながら、ノードにセットしていくことになります。

docs.arnoldrenderer.com