プラグインの作成


ここまでTutorial[入門編]では基本的なシーンの作成方法やアセットの設定方法などについて話を進めてきました。 発展編では新たに実装したいレンダリングシステムなどの拡張をLightmetricaではいかに簡単に作成できるかについて説明していきたいと思います。

プラグインの役割

Lightmetricaではほとんどの構成要素(例:新規アセットやレンダリング手法)を簡単に拡張することができます。 Lightmetricaにすでに実装されているシステムの正確性は保障されているので、例えば研究用に新規に実装したレンダリング手法を比較したい時や新たに作成したシステムを試したい時,一般化したいシステムを作成したい時などにLightmetricaでは簡単に拡張することができます。

プラグインの実装方法

実装環境

Lightmetricaで実装する際に用いる言語はc++です。
#include <lightmetrica/lightmetrica.h>を最初に宣言するだけで必要なライブラリを参照でき、手軽に新規プラグインを作成できます。 新たに作成したプラグインファイル名(hogehoge.cpp)は任意でいいですが、動的ライブラリを作成時には以下のようにしなくてはなりません。
動的ライブラリの名前

というように記述します。 例えば、新しいレンダラの手法としてmymethodという実装名の、自分の手法を用いたレンダラを作成したいとしたら、 renderer_mymethod.dll(or .dylib or .so)のように動的ライブラリの名前を設定します。

百聞は一見に如かず。ということで、2つのプラグインを例に拡張方法を見て理解を深めていきましょう。

例1: 新規テクスチャの作成

まずはLightmetricaの中のpluginフォルダ内にあるtexture_checkerフォルダにtexture_checker.cppというソースファイルが入っているのを確認してください. これは既に作成されたtextureの拡張プラグインになります。

コマンドラインを開いて lightmetrica->bin->pluginフォルダまで下図のように移動したあとに,

  cd lightmetrica/      
  cd bin/
  cd plugin/

コンパイルして動的ライブラリを作ってやりましょう。わずかなコマンドだけでできます。

このコマンドでMacの場合dylibファイルが、Windowsの場合dllファイルがbinフォルダのpluginフォルダに追加されました。

先ほどの注意を思い出してください。 今texture_checkerという名前で動的ライブラリを作りました。 なのでinterfacetextureに,typeは実装名のcheckerにしてやる必要があります。

それに注意した状態であとはsceneファイルを書き換えてやるだけです。

   bsdf_checker:
      interface: bsdf
      type: diffuse
      params:
        TexR: checker

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

textureをtexture_checkerで拡張したのでinterfacetextureに、typecheckerと記述してtextureを作成します。

あとは今までと同じように実行すると、

元画像

元画像

texture画像

texture画像

下図のように床がチェック模様の画像が出来上がりました。

実はこのtexture_checker.cppは画像を用いてtextureを作成するのではなく、meshの位置を計算してチェック模様のtextureを作成するように作られています。scaleで縞模様の大きさをcolor1color2でそれぞれの格子の色を決定できるようにしています。 試しに色と大きさを変えてみましょう。

    bsdf_checker:
      interface: bsdf
      type: diffuse
      params:
        TexR: checker

    checker:
      interface: texture
      type: checker
      params:
        scale: 50
        color1: 1 1 0
        color2: 1 0 1
大きさと色変えてみたよ

大きさと色変えてみたよ

ちゃんと色と格子の大きさが変わりましたね。 このように自分の好きなようにプラグインを用いて拡張することができます。

それではtexture_checker.cppのコードを見てどのように作成しているかを見てみましょう。

 
#include <lightmetrica/lightmetrica.h>


LM_NAMESPACE_BEGIN

class Texture_Checker final : public Texture
{
public:

    LM_IMPL_CLASS(Texture_Checker, Texture);

public:

    LM_IMPL_F(Load) = [this](const PropertyNode* prop, Assets* assets, const Primitive* primitive) -> bool
    {
        scale_ = prop->ChildAs<Float>("scale", 100_f);
        color1_ = prop->ChildAs<Vec3>("color1", Vec3(1_f, 0_f, 0_f));
        color2_ = prop->ChildAs<Vec3>("color2", Vec3(1_f));
        return true;
    };

    LM_IMPL_F(Evaluate) = [this](const Vec2& uv) -> Vec3
    {
        const int u = (int)(uv.x * scale_);
        const int v = (int)(uv.y * scale_);
        return (u + v) % 2 == 0 ? color1_ : color2_;
    };

private:

    Float scale_;
    Vec3 color1_;
    Vec3 color2_;

};

LM_COMPONENT_REGISTER_IMPL(Texture_Checker, "texture::checker")

細かい作成方法は次のAPIの読み方で説明しますので、ここでは簡単にだけ説明します。 プラグインに与えてやりたいパラメータはLM_IMPL_F(Load)メソッド内に記述します。つまりsceneファイルのparams以下に入るパラメータをここで記述してやります。 今、texture_checkerscalecolor1color2の3つのパラメータがありましたよね。 sceneファイルの中のparams以下の木構造がpropに入るので、その値をchildAsで取得してやることでsceneファイルに記述した値が読み込まれます。
後はLM_IMPL_F(Evaluate)で、戻り値がVec3Evaluateという関数を作って、床の場所の位置によってcolor1_color2_のどちらを返すかを決定するように作成して、格子模様を作っています。

例2:新規レンダラの作成

例1とやることは変わりません。 (フォルダ名)にrender_nomal.cppというファイルが入っています。 先ほどと同じようにコンパイルしてやるだけです。

今回はrenderer_normalという名前にしたのでtypenormalと記述します。

それでは実行結果を見てみましょう。

法線マップ

法線マップ

今回は法線マップを作成できるようにrendererを拡張しました。

同じようにソースコードを見てみましょう。

#include <lightmetrica/lightmetrica.h>

LM_NAMESPACE_BEGIN

class Renderer_Normal final : public Renderer
{
public:

    LM_IMPL_CLASS(Renderer_Normal, Renderer);

public:

    LM_IMPL_F(Initialize) = [this](const PropertyNode* prop) -> bool
    {
        return true;
    };

    LM_IMPL_F(Render) = [this](const Scene* scene, Film* film) -> void
    {
        const int w = film->Width();
        const int h = film->Height();

        for (int y = 0; y < h; y++)
        {
            for (int x = 0; x < w; x++)
            {
                // Raster position
                Vec2 rasterPos((Float(x) + 0.5_f) / Float(w), (Float(y) + 0.5_f) / Float(h));

                // Position and direction of a ray
                const auto* E = scene->Sensor()->emitter;
                SurfaceGeometry geomE;
                E->SamplePosition(Vec2(), geomE);
                Vec3 wo;
                E->SampleDirection(rasterPos, 0_f, 0, geomE, Vec3(), wo);

                // Setup a ray
                Ray ray = { geomE.p, wo };

                // Intersection query
                Intersection isect;
                if (!scene->Intersect(ray, isect))
                {
                    // No intersection -> black
                    film->SetPixel(x, y, SPD());
                    continue;
                }

                // Set color to the pixel
                film->SetPixel(x, y, SPD::FromRGB(Vec3(
                    Math::Abs(isect.geom.sn.x),
                    Math::Abs(isect.geom.sn.y),
                    Math::Abs(isect.geom.sn.z))));
            }
        }
    };

};

LM_COMPONENT_REGISTER_IMPL(Renderer_Normal, "renderer::normal");

LM_NAMESPACE_END

これも細かい説明はここでは省きますが、LM_IMPL_F(Initialize)メソッドでは、パラメータを読み込んだり(今回の例ではなし),そのレンダリング手法に必要な前処理などを記述します。 あとはLM_IMPL_F(Render)で実装したいレンダリング手法をfilmに書き出すように記述していきます。

基本的な作り方はこのようになります。 次章のAPIの読み方では、どのようなAPIがあるか、又より詳しくプラグインの作成方法を説明します。


APIドキュメントの読み方へ