フレームワークの拡張
プラグインによるフレームワークの拡張方法はじめに
拡張性はレンダラの研究開発において重要な要素です。 レンダリング研究の典型的なワークフローにおいて、拡張性は大いに役立ちます。 例えば、すでに実装された機能を用いることで、 再実装を避けることができ、新機能の実装にリソースを割くことができるようになります。 また、シーンやマテリアルの設定を変更することなしに様々な実装の比較を行うことができます。
このような拡張を可能とするため、我々はプラグインシステムの設計と実装を行いました。 プラグインシステムを用いることによって、ほとんどのレンダラの構成要素、例えばレンダリング手法やマテリアル、メッシュ、またはアクセラレーション構造等、を拡張することができます。
プラグインシステムの設計を行うにあたって、我々は拡張のシンプルさにフォーカスを当てました。
プラグインはシンプルなAPIや簡単なビルドコマンドでプラグインを作成することができます。
またポータブルなヘッダオンリーライブラリを用いることで、静的リンクの必要がなくライブラリの互換性を気にする必要なく作成を行うことができます。
プラグインを作成するために必要なものは一つの.cpp
ファイルと1行のコマンド、これだけです。
すでにある機能を拡張するプラグインに加えて、 本プラグインシステムは拡張可能な新たな機能を作成することができます。 例えば、シーンファイルから制御できる新たなアセットを作成することさえ可能です。
特徴:
- 柔軟性
- ほとんどのレンダラの構成要素を拡張することができます
- シンプルさ
- ヘッダオンリーライブラリを用いてプラグインを作成することができます
- メタな拡張性
- 新たな機能を拡張するためのインタフェースを作成することができます
プラグインの作成とビルド
必要なもの
ビルド環境
- 本フレームワークのバイナリ配布版のインストール
- バイナリ配布版にはプラグインを作成するためのヘッダファイルが含まれています
- ビルド環境のインストール
- Windows: Visual Studio 2015のインストール
- Mac OS X: XCodeのインストール
必要な知識
- C++言語の知識
- Lightmetricaを使ってシーンをレンダリングする知識
インターフェースの実装
バイナリ配布版にはいくつかの例となるプラグインが含まれています。
ここでは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
説明:
1行目: プラグインを作成するために必要なヘッダファイルを用いるために、
lightmetrica/lightmetrica.h
をインクルードします。このヘッダファイルはバイナリ配布版に含まれています。5行目: この例では
texture
インターフェースの実装を行うので、Texture
クラスを継承したクラスを作成します。実装となるクラスにfinal
を付けるとさらなる継承を防ぐことができます。9行目: インターフェースを実装するためにいくつかのマクロを用いる必要があります。
LM_IMPL_CLASS
は3つの引数を取ります。最初の引数に本クラス名、次にインターフェースのクラス名を指定します。13行目、21行目: テクスチャを実装するためには
Load
とEvaluate
(doc, source)の2つの関数を実装する必要があります。Load
関数はシーン定義ファイルで指定されたパラメータを読み込みます。シーン定義ファイルのparams
以下の要素が引数prop
に格納されます。要素にアクセスするAPI (例えば,ChildAs<T>
, doc) を用いることにより、ここではscale
,color1
,color2
の3つの値を読み込んでいます。Evaluate
関数は与えられたuv座標に対応する色を返します。 ここではチェッカー模様を生成するためのロジックを実装しています。36行目: 実装となるクラスは
LM_COMPONENT_REGISTER_IMPL
マクロを用いてフレームワークに登録を行う必要があります。このマクロは2つの引数を取ります。 一つ目に実装となるクラス名、二つ目に実装のIDを指定します。 IDは<インターフェースID>::<実装ID>
の型である必要があります。
プラグインのビルド
次にtexture_checker.cpp
をプラグインとしてビルドを行います。
プラグインのビルドは非常に簡単で、
ヘッダファイルのみでビルドを行うことができます。
Visual Studio 2015 を起動して
File > New > Project
を選択します。Templates > Visual C++
、次にWin32 Console Application
を選択し, プロジェクト名とディレクトリを指定してOKを押します. ここではtexture_checker
という名前のプロジェクトを作成します。Next
をクリックし、アプリケーションの種類からDLL
を選択します。追加オプションのEmpty project
を選択し、Finishをクリックします。Solution Platformを
x64
に変更します。Solution ConfigurationをRelease
に変更します。Project > Add Existing Items...
を選択し、texture_checker.cpp
を追加します。Project > texture_checker Properties...
を選択し、ConfigurationをAll Configurations
に変更します。Configuration Properties > VC++ Directories
を選択し、Edits...
をクリックします。ここでバイナリ配布版に含まれるinclude
ディレクトリを指定します。Build > Build Solution
を選択し、ビルドを行います。texture_checker.dll
が生成されます。texture_checker.dll
をバイナリ配布版のbin/plugin
ディレクトリにコピーします。
コマンドプロンプトを開き、バイナリ配布版の
bin/plugin
ディレクトリに移動します。ビルド環境をロードします。
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall" x86_amd64
プラグインをビルドします。
cl /LD /EHsc "../../texture_test.cpp" /I"../../include" /link /out:texture_checker.dll
ターミナルを開き、バイナリ配布版の
bin/plugin
ディレクトリに移動します。プラグインをビルドします。
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
を返します。
動的ライブラリの境界におけるメモリ管理を適切に行うため、
自動的にデリーターが登録された状態で返されます。