APIドキュメントの読み方


前の章では,textureの新規アセットとrendererの新規手法の作成方法の2つを例にプラグインの拡張の仕方を見てきました。もちろん他のアセットや要素に対してもプラグインを作成することができます。この章ではプラグインの作成方法をもう少し細かく説明していきます。

APIドキュメントとは?

APIドキュメントは以下のページから見ることができます。
http://doc.lightmetrica.org/
新しくプラグインを作成するためにはAPIドキュメントを読んでLightmetricaを用いたソースコードの書き方を学んでいく必要があります。 APIドキュメントには、それぞれのAssetsやrenderer等のインターフェースの情報が細かく書かれています。 APIドキュメントページの構成は以下のようになっていて

というようになっています。 例えば、Assetstextureを拡張したいとします。 APIドキュメントページのModuleに全ての要素の説明欄があるのでその中からAssetstextureを見てみましょう。すると class textureという欄があるので、このtextureページに飛ぶことで細かい仕様の解説を見ることができます。

それでは、前章で用いたtexture_checkerrenderer_normalをもう一度参考にしながら、APIドキュメントの読み方と少し細かいプラグインの作成方法を見ていきましょう。

APIドキュメントの読み方を学ぼう! その1

まずはtexture_checker.cppについて見ていきましょう。 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")
まず、最初のLM_IMPL_CLASSについてですが、これはお約束的な構文なのでAPIドキュメントではなくここで説明します。 今クラス名はTexture_Checkerでインターフェース名はTextureとして定義しています。 このLM_IMPL_CLASSにはそのクラス名とインターフェース名を引数として与えてやります。

LM_IMPL_CLASS(クラス名,インターフェース名)

この一文を書くことで実装したいインターフェースの新しいプラグインを作ることができます。 もう一つお約束ごととして、LM_COMPONENT_REGISTER_IMPLを記述する必要があります。これは

LM_COMPONENT_REGISTER_IMPL(クラス名,“インターフェース名::プラグイン名”)

という形で記述します。ここで記述したプラグイン名とsceneファイルのtypeが一致することで、プラグインをつかえることができます。(前の章を思い出してください.)

さて、本題に入っていきます。 LM_IMPL_F(Load)という関数がありますね。 これは何でしょう?APIドキュメントページを読んでみましょう。 http://doc.lightmetrica.org/class_texture.html
このページを見てみるとTextureAssetsの子関係にあるのがわかりますよね? それだけでなくfilmlight等全てのアセットがAssetsの子関係にあります。

AssetsのページをみてみるとLM_IMPL_F(0, Load, bool(const PropertyNode *prop, Assets *assets, const Primitive *primitive)) という関数がありますね。これを記述することでSceneファイルからパラメータを読み込むことができます。 Assetsを継承しているものは全てこのLM_IMPL_F(Load)を記述してパラメータを読み込む必要があります。
次を見てみると LM_IMPL_F(Evaluate)という関数があります。 これは、APIドキュメントのtextureクラスをみてみると、 LM_INTERFACE_F (0, Evaluate, Vec3(const Vec2 &uv)) というものがありますね。 このEvaluate関数を記述することで、テクスチャを評価するコードを書くことができます。 このように、textureインターフェースでは、継承元のAssetsLoad関数とtexture本体のEvaluate関数の2つを必ず書く必要があります。

あとは簡単で、実装したい任意の関数を

LM_IMPL_F(任意のメソッド名)

のように書いて、実装するだけです。簡単ですね。 あと一つ説明として、本来c++では戻り値は

void 関数名 (引数){ }

という風に最初に書きますが、Lightmetricaではラムダ関数を使っているので、 LM_IMPL_F(関数名)=[this](引数) -> 戻り値 となっています。

もう一つBSDFを例にみてみましょう。 http://doc.lightmetrica.org/class_generalized_b_s_d_f.html
同じようにAssetsを継承しているので、Load関数は書く必要があります。それに加えてBSDFクラスにはTypeSampleDirection,EvaluateDirectionPDF,EvaluateDirection関数を記述してやる必要があります。 このようにAPIドキュメントを読むことで、そのコンポーネントに必要な関数やその説明を理解することができます。

APIドキュメントの読み方を学ぼう! その2

もう一つ例をみてみましょう。以下はrenderer_normal.cppのソースコードです。

#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

APIドキュメントを読んでみましょう。 ModulesRenderer->class rendererとページを飛びましょう。 するとrendererクラスの説明ページに移ります。 RendererConfigureableを継承しています。 なのでConfigureableのページを見てみると

LM_INTERFACE_F (0, Initialize, bool(const PropertyNode *prop)) と書いてありますね。 rendererの場合はConfigureableを継承しているのでInitializeを記述してパラメータをsceneファイルから読み込む必要があります。 それに加えてrendererクラスのページを見てみると、Renderという関数がありますね。上のソースコードにもこの部分がありますが、ここで実装したいレンダリング手法をfilmに書き出すコードを記述します。

このようにして、APIドキュメントをよむことで、簡単にコンポーネントに必要な関数やコードの書き方を理解することができます。


さらなるステップへ