APIドキュメントの読み方
前の章では,texture
の新規アセットとrenderer
の新規手法の作成方法の2つを例にプラグインの拡張の仕方を見てきました。もちろん他のアセットや要素に対してもプラグインを作成することができます。この章ではプラグインの作成方法をもう少し細かく説明していきます。
APIドキュメントとは?
APIドキュメントは以下のページから見ることができます。
http://doc.lightmetrica.org/
新しくプラグインを作成するためにはAPIドキュメントを読んでLightmetricaを用いたソースコードの書き方を学んでいく必要があります。
APIドキュメントには、それぞれのAssetsやrenderer等のインターフェースの情報が細かく書かれています。
APIドキュメントページの構成は以下のようになっていて
- Modules
- フレームワークの各要素の説明
- Classes
- 全てのクラスのリンク
というようになっています。
例えば、Assets
のtexture
を拡張したいとします。
APIドキュメントページのModule
に全ての要素の説明欄があるのでその中からAssets
のtexture
を見てみましょう。すると class textureという欄があるので、このtexture
ページに飛ぶことで細かい仕様の解説を見ることができます。
それでは、前章で用いたtexture_checker
とrenderer_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
このページを見てみるとTexture
はAssets
の子関係にあるのがわかりますよね?
それだけでなくfilm
やlight
等全てのアセットが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
インターフェースでは、継承元のAssets
のLoad
関数と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
クラスにはType
とSampleDirection
,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ドキュメントを読んでみましょう。
Modules
→Renderer
->class renderer
とページを飛びましょう。
するとrenderer
クラスの説明ページに移ります。
Renderer
はConfigureable
を継承しています。
なのでConfigureable
のページを見てみると
LM_INTERFACE_F (0, Initialize, bool(const PropertyNode *prop))
と書いてありますね。
renderer
の場合はConfigureable
を継承しているのでInitialize
を記述してパラメータをscene
ファイルから読み込む必要があります。
それに加えてrenderer
クラスのページを見てみると、Render
という関数がありますね。上のソースコードにもこの部分がありますが、ここで実装したいレンダリング手法をfilmに書き出すコードを記述します。
このようにして、APIドキュメントをよむことで、簡単にコンポーネントに必要な関数やコードの書き方を理解することができます。