フレームワークの拡張

プラグインによるフレームワークの拡張方法

はじめに

拡張性はレンダラの研究開発において重要な要素です。 レンダリング研究の典型的なワークフローにおいて、拡張性は大いに役立ちます。 例えば、すでに実装された機能を用いることで、 再実装を避けることができ、新機能の実装にリソースを割くことができるようになります。 また、シーンやマテリアルの設定を変更することなしに様々な実装の比較を行うことができます。

このような拡張を可能とするため、我々はプラグインシステムの設計と実装を行いました。 プラグインシステムを用いることによって、ほとんどのレンダラの構成要素、例えばレンダリング手法やマテリアル、メッシュ、またはアクセラレーション構造等、を拡張することができます。

プラグインシステムの設計を行うにあたって、我々は拡張のシンプルさにフォーカスを当てました。 プラグインはシンプルなAPIや簡単なビルドコマンドでプラグインを作成することができます。 またポータブルなヘッダオンリーライブラリを用いることで、静的リンクの必要がなくライブラリの互換性を気にする必要なく作成を行うことができます。 プラグインを作成するために必要なものは一つの.cppファイルと1行のコマンド、これだけです。

すでにある機能を拡張するプラグインに加えて、 本プラグインシステムは拡張可能な新たな機能を作成することができます。 例えば、シーンファイルから制御できる新たなアセットを作成することさえ可能です。

特徴:


プラグインの作成とビルド

必要なもの

ビルド環境

必要な知識

インターフェースの実装

バイナリ配布版にはいくつかの例となるプラグインが含まれています。 ここではpluginディレクトリのtexture_checker.cppを用いてプラグインを作成してみましょう。 texture_checkerプラグインはtextureインターフェースを実装することによって、 チェッカー模様のテクスチャを計算によって実現します。 以下でtexture_checker.cppの各行を解説します。

 1 #include <lightmetrica/lightmetrica.h>
 2 
 3 LM_NAMESPACE_BEGIN
 4 
 5 class Texture_Checker final : public Texture
 6 {
 7 public:
 8 
 9     LM_IMPL_CLASS(Texture_Checker, Texture);
10 
11 public:
12 
13     LM_IMPL_F(Load) = [this](const PropertyNode* prop, Assets*, const Primitive*) -> bool
14     {
15         scale_ = prop->ChildAs<Float>("scale", 100_f);
16         color1_ = prop->ChildAs<Vec3>("color1", Vec3(1_f, 0_f, 0_f));
17         color2_ = prop->ChildAs<Vec3>("color2", Vec3(1_f));
18         return true;
19     };
20 
21     LM_IMPL_F(Evaluate) = [this](const Vec2& uv) -> Vec3
22     {
23         const int u = (int)(uv.x * scale_);
24         const int v = (int)(uv.y * scale_);
25         return (u + v) % 2 == 0 ? color1_ : color2_;
26     };
27 
28 private:
29 
30     Float scale_;
31     Vec3 color1_;
32     Vec3 color2_;
33 
34 };
35 
36 LM_COMPONENT_REGISTER_IMPL(Texture_Checker, "texture::checker")
37 
38 LM_NAMESPACE_END

説明:

プラグインのビルド

次にtexture_checker.cppをプラグインとしてビルドを行います。 プラグインのビルドは非常に簡単で、 ヘッダファイルのみでビルドを行うことができます。

  1. Visual Studio 2015 を起動して File > New > Project を選択します。

  2. Templates > Visual C++、次にWin32 Console Applicationを選択し, プロジェクト名とディレクトリを指定してOKを押します. ここではtexture_checkerという名前のプロジェクトを作成します。

  3. Nextをクリックし、アプリケーションの種類からDLLを選択します。追加オプションのEmpty projectを選択し、Finishをクリックします。

  4. Solution Platformをx64に変更します。Solution ConfigurationをReleaseに変更します。

  5. Project > Add Existing Items...を選択し、texture_checker.cppを追加します。

  6. Project > texture_checker Properties...を選択し、ConfigurationをAll Configurationsに変更します。

  7. Configuration Properties > VC++ Directoriesを選択し、Edits...をクリックします。ここでバイナリ配布版に含まれるincludeディレクトリを指定します。

  8. Build > Build Solutionを選択し、ビルドを行います。texture_checker.dllが生成されます。

  9. texture_checker.dllをバイナリ配布版のbin/pluginディレクトリにコピーします。

  1. コマンドプロンプトを開き、バイナリ配布版のbin/pluginディレクトリに移動します。

  2. ビルド環境をロードします。

    "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall" x86_amd64
    

  3. プラグインをビルドします。

    cl /LD /EHsc "../../texture_test.cpp" /I"../../include" /link /out:texture_checker.dll
    

  1. ターミナルを開き、バイナリ配布版のbin/pluginディレクトリに移動します。

  2. プラグインをビルドします。

    clang++ -dynamiclib -std=c++1y -march=native -I"../../include/" "../../plugin/texture_checker.cpp" -o texture_checker.dylib
    

プラグインの使用

実行ファイルlightmetricaと同じディレクトリ中のpluginディレクトリ中の動的ライブラリ がプラグインとして自動的にロードされます。 pluginディレクトリにプラグインをコピーするだけで、プラグインを使用してレンダリングを行う準備が完了します。

例として、001_bunnyを改良したシーンをscene_ex.ymlとして用意しました。 scene.ymlとの違いをハイライトで示します。 新たなシーンでは新しくtex_checkerとして新たに作成したプラグインを用いるtextureアセットと、それを用いるbsdfアセットを定義しました。 tex_checkerアセットの定義ではtypeとして今回作成したcheckerを指定します。 同じく、params以下でプラグインに対するパラメータの設定を行うことができます。

オリジナルのシーン

オリジナルのシーン

プラグインを用いたシーン

プラグインを用いたシーン

assets:
    ...
    bsdf_checker:
      interface: bsdf
      type: diffuse
      params:
        TexR: tex_checker

    tex_checker:
      interface: texture
      type: checker
      params:
        scale: 100
        color1: 1 0 0
        color2: 1 1 1

scene:
    ...
    nodes:
      - id: n4
        ...
        bsdf: bsdf_checker

実行ファイルlightmetricaと同じディレクトリに移動したのち、 次のコマンドでレンダリング画像を得ます。 正しくレンダリングされると、右図下のような画像が得られます。

$ ./lightmetrica render -s ../example/001_bunny/scene_ex.yml


プラグインシステムの設計

プラグインを実装するためにはC++言語を使用します。 C++のクラスとして定義されたインターフェースを実装することによって、プラグインを作成することができます。 例えば、textureのプラグインを作成する場合、 Textureクラス を継承して実装する必要があります。 ここで実装されるクラスをインターフェースクラス(例ではTextureクラス)、 また継承して実装を行うクラスを実装クラスと呼びます。

インターフェース

インターフェースを定義するために、マクロを用いたクラス(または構造体)を用います。 インターフェースとなるクラスはLM_INTERFACE_CLASSマクロを用いて定義します。 メンバ関数はLM_INTERFACE_Fマクロを用いて定義します。 現在のところ、オーバーロードやconstメンバ関数はサポートしていません。

例えば、関数Func1, Func1を定義したインターフェースAは以下のように書くことができます。 ここで、インターフェースAは基底となるインターフェースがないことに着目します。 このような場合、Componentクラスを基底クラスとします。

struct A : public Component
{
    LM_INTERFACE_CLASS(A, Component, 2);
    LM_INTERFACE_F(0, Func1, void());
    LM_INTERFACE_F(1, Func2, int(int, int));
};

またインターフェースを継承することによって新たなインターフェースを作成することができます。

struct B : public A
{
    LM_INTERFACE_CLASS(B, A, 1);
    LM_INTERFACE_F(0, Func3, int());
};

実装

インターフェースの実装もまた、いくつかのクラスを用いて定義します。 例えば、インターフェースAの実装は次のように書くことができます。

struct A_Impl : public A
{
    LM_IMPL_CLASS(A_Impl, A);
    LM_IMPL_F(Func) = [this]() -> void { ... }
};

LM_COMPONENT_REGISTER_IMPL(A_Impl, "a::impl");

実装クラスはLM_IMPL_CLASSを用いて定義します。 また、インターフェース関数はLM_IMPL_Fマクロとラムダ関数を用いて定義します。

最後に実装クラスはLM_COMPONENT_REGISTER_IMPLマクロを用いてフレームワークに登録する必要があります。 この際、引数として実装のIDを当たる必要があります。 このIDは実装クラスのインスタンスを生成するために用いられます。

インスタンスの生成

インターフェースと実装を作成することで、実装クラスのインスタンスを作成する準備が整います。 インスタンス生成はファクトリクラスであるComponentFactoryを用います。

例えば、インターフェースクラスAがヘッダa.hに定義されているとして、 実装クラスであるA_ImplのインスタンスをComponentFactory::Create を用いて次のように作成することができます。

#include "a.h"
...
const auto a = ComponentFactory::Create<A>("a::impl");
...

ComponentFactory::Create関数はテンプレート引数で指定された インターフェースクラスを型に持つunique_ptrを返します。 動的ライブラリの境界におけるメモリ管理を適切に行うため、 自動的にデリーターが登録された状態で返されます。