BSDF

Arnold 5.0 では、より高度なレンダリング アルゴリズム、より高度な BSDF、およびより最適化された実装をサポートする目的で、BSDF API が再設計されました。

メソッド

BSDF は、そのパラメータを保存する構造体と複数のメソッドで構成されます。新しい BSDF は、AiBSDF を使用して作成されます。これによってメソッド テーブルを受け取り、パラメータを格納する構造体を割り当てます。シェーダの評価が終了した後、インテグレータは、BSDF を初期化、評価、およびサンプリングします。

ローブ

BSDF は複数のローブで構成されます。たとえば、レイヤ化された BSDF に含まれるさまざまなレイヤ、またはガラスの BSDF に含まれる個別の反射コンポーネントと屈折コンポーネントで構成されます。 各ローブには関連するレイ タイプおよび AOV 名があります。これにより以下のことが可能になります。

BSDF の初期化では、BSDF はそれを構成するローブの数を指定し、ローブごとにレイ タイプおよび AOV 名を配列に割り当てます。

評価メソッドおよび抽出メソッドは、どのローブをそれぞれ評価または抽出するかを示すローブ ビットマスクを受け取ります。抽出メソッドが複数のローブを含むローブ マスクを受け取る場合、サンプリング(理想的には、各ローブの影響度に基づく重点サンプリング)対象のローブを 1 つ選択するのは BSDF です。

これらのメソッドは、ローブのサンプルの配列を出力し、どのローブ サンプルが入力されたのかを示すローブ ビットマスクを返します。これは、入力ローブ マスクで指定されたローブのサブセットまたはスーパーセットである場合があります。

境界

BSDF の反射されるすべての入射ライトが半球に含まれている場合、その半球の法線を指定できます。この情報を指定することは厳密には必要ありませんが、Arnold が BSDF の境界の外側にあるライトをすばやく破棄できるようにすることで、レンダリングの処理速度を上げることができます。

バンプ マッピング

BSDF は、バンプ マッピングまたは法線マッピングを使用して、スムーズなサーフェス法線 Ns とは異なる法線を使用する場合があります。しかし、この修正された法線とスムーズなサーフェス法線の間に相違がありすぎる場合には、アーティファクトが表示されることがあります。BSDF の評価の一部としてシャドウを追加することにより、そのようなアーティファクトを非表示にする AiBSDFBumpShadow ユーティリティ関数が提供されます。

この関数は、前向きのスムーズな法線、バンプ マッピングされた法線、およびライトの入射方向を入力として取り、BSDF ウェイトを使用して乗算する係数を出力します。

レイ微分

bsdf_sample によって返されるレイ方向にはレイ微分が含まれます。テクスチャ フィルタリングの良好なパフォーマンスと品質を確保するには、レイ微分を提供することが重要です。AiReflectWithDerivs および AiRefractWithDerivs ユーティリティ関数は、微分係数を伴う反射ベクトルまたは屈折ベクトルの計算に使用できます。

シンプルで完全にシャープな鏡面反射光 BSDF の例:

AtVectorDv I = AtVectorDv(sg->Rd, sg->dDdx, sg->dDdy);
AtVectorDv N = AtVectorDv(sg->Nf, sg->dNdx, sg->dNdy);
AtVectorDv R = AiReflectWithDerivs(I, N); 

粗さのクランプ

単方向パス トレーサでは、コースティクスを効率的に解決することができません。粗い鏡面反射光または拡散反射光バウンスを通して見るシャープな鏡面反射光バウンスは、補正を一切行わないとノイズが極端に多くなります。ビルトインの BSDF は、自動的に粗さを増やすことで、バイアスを犠牲にしてコースティクスのノイズを減らします。options.indirect_glossy_blur はブラーの量をコントロールし、0 を指定すると、レンダーのバイアスがなくなります。

カスタム BSDF でも同じ方法を使用するには、AiBSDFMinRoughness 関数によって提供される、自動的に推定された粗さの下限値によって粗さをクランプします。

bsdf_init
{
   ...
   // clamp roughness based on path history
   data->roughness = AiMax(AiBSDFMinRoughness(sg), data->roughness);
   ...
} 

エグジット カラー

ガラス シェーダでは、多数のバウンスのエスケープが必要になることがよくあります。バウンスの数が制限されている場合、パッチが暗くなります。各 BSDF ローブには、バウンスの数が超過している場合にバックグラウンド カラーまたは固定カラーの白を使用するよう設定できるフラグがあります。

双方向レンダリング

API は、双方向レンダリング アルゴリズムで機能するように設計されています。しかし、Arnold はまだそのようなインテグレータを提供していないため、BSDF はそうしたレンダリング手法と互換性がある実装環境を用意する必要がありません。ローブ サンプルの reverse_pdf メンバーはプレースホルダであり、現在は無視されます。

拡散反射光の例

以下に、拡散反射光 BSDF の簡単な例を取り上げます。この例では、BSDF を使って直接光と間接光を統合するシェーダを使用します。

diffuse_bsdf.cpp

#include "diffuse_bsdf.h"


struct DiffuseBSDF
{
   /* parameters */
   AtVector N;
   /* set in bsdf_init */
   AtVector Ng, Ns;
};

AI_BSDF_EXPORT_METHODS(DiffuseBSDFMtd);

bsdf_init
{
   DiffuseBSDF *data = (DiffuseBSDF*)AiBSDFGetData(bsdf);

   // store forward facing smooth normal for bump shadowing
   data->Ns = (sg->Ngf == sg->Ng) ? sg->Ns : -sg->Ns;

   // store geometric normal to clip samples below the surface
   data->Ng = sg->Ngf;

   // initialize the BSDF lobes. in this case we just have a single
   // diffuse lobe with no specific flags or label
   static const AtBSDFLobeInfo lobe_info[1] = { {AI_RAY_DIFFUSE_REFLECT, 0, AtString()} };
   AiBSDFInitLobes(bsdf, lobe_info, 1);

   // specify that we will only reflect light in the hemisphere around N
   AiBSDFInitNormal(bsdf, data->N, true);
}

bsdf_sample
{
   DiffuseBSDF *data = (DiffuseBSDF*)AiBSDFGetData(bsdf);

   // sample cosine weighted incoming light direction
   AtVector U, V;
   AiV3BuildLocalFrame(U, V, data->N);
   float sin_theta = sqrtf(rnd.x);
   float phi = 2 * AI_PI * rnd.y;
   float cosNI = sqrtf(1 - rnd.x);
   AtVector wi = sin_theta * cosf(phi) * U +
                 sin_theta * sinf(phi) * V +
                 cosNI * data->N;

   // discard rays below the hemisphere
   if (!(AiV3Dot(wi, data->Ng) > 0))
      return AI_BSDF_LOBE_MASK_NONE;

   // since we have perfect importance sampling, the weight (BRDF / pdf) is 1
   // except for the bump shadowing, which is used to avoid artifacts when the
   // shading normal differs significantly from the smooth surface normal
   const float weight = AiBSDFBumpShadow(data->Ns, data->N, wi);

   // pdf for cosine weighted importance sampling
   const float pdf = cosNI * AI_ONEOVERPI;

   // return output direction vectors, we don't compute differentials here
   out_wi = AtVectorDv(wi);

   // specify that we sampled the first (and only) lobe
   out_lobe_index = 0;

   // return weight and pdf
   out_lobes[0] = AtBSDFLobeSample(AtRGB(weight), 0.0f, pdf);

   // indicate that we have valid lobe samples for all the requested lobes,
   // which is just one lobe in this case
   return lobe_mask;
}

bsdf_eval
{
   DiffuseBSDF *data = (DiffuseBSDF*)AiBSDFGetData(bsdf);

   // discard rays below the hemisphere
   const float cosNI = AiV3Dot(data->N, wi);
   if (cosNI <= 0.f)
      return AI_BSDF_LOBE_MASK_NONE;

   // return weight and pdf, same as in bsdf_sample
   const float weight = AiBSDFBumpShadow(data->Ns, data->N, wi);
   const float pdf = cosNI * AI_ONEOVERPI;
   out_lobes[0] = AtBSDFLobeSample(AtRGB(weight), 0.0f, pdf);

   return lobe_mask;
}

AtBSDF* DiffuseBSDFCreate(const AtShaderGlobals* sg, const AtRGB& weight, const AtVector& N)
{
   AtBSDF* bsdf = AiBSDF(sg, weight, DiffuseBSDFMtd, sizeof(DiffuseBSDF));
   DiffuseBSDF* data = (DiffuseBSDF*)AiBSDFGetData(bsdf);
   data->N = N;
   return bsdf;
} 

diffuse_bsdf.h

#pragma once

#include <ai_shader_bsdf.h>
#include <ai_shaderglobals.h>

AtBSDF* DiffuseBSDFCreate(const AtShaderGlobals* sg, const AtRGB& weight, const AtVector& N); 

diffuse_shader.cpp

#include "diffuse_bsdf.h"

#include <ai.h>

AI_SHADER_NODE_EXPORT_METHODS(DiffuseMtd)

enum DiffuseParams {
   p_color,
};

node_parameters
{
   AiParameterRGB("color", 8f, 0.8f, 0.8f);
}

node_initialize
{
}

node_update
{
}

node_finish
{
}

shader_evaluate
{
   // early out for shadow rays and black color
   if (sg->Rt & AI_RAY_SHADOW)
      return;

   AtRGB color = AiShaderEvalParamRGB(p_color);
   if (AiColorIsSmall(color))
      return;

   sg->out.CLOSURE() = DiffuseBSDFCreate(sg, color, sg->Nf);
}

node_loader
{
   if (i>0)
      return false;

   node->methods      = DiffuseMtd;
   node->output_type  = AI_TYPE_CLOSURE;
   node->name         = "diffuse";
   node->node_type    = AI_NODE_SHADER;
   strcpy(node->version, AI_VERSION);
   return true;
}