Arnold 5.0 では、より高度なレンダリング アルゴリズム、より高度な BSDF、およびより最適化された実装をサポートする目的で、BSDF API が再設計されました。
BSDF は、そのパラメータを保存する構造体と複数のメソッドで構成されます。新しい BSDF は、AiBSDF を使用して作成されます。これによってメソッド テーブルを受け取り、パラメータを格納する構造体を割り当てます。シェーダの評価が終了した後、インテグレータは、BSDF を初期化、評価、およびサンプリングします。
bsdf_init: BSDF が初めて評価または抽出される前に呼び出されます。BSDF の初期化により、提供されたパラメータが有効な範囲内に確実に収まるようになり、後で評価およびサンプリングするためのローカル ジオメトリ データ(ジオメトリ法線、進行中のビュー方向など)が保存され、評価とサンプリングに必要なデータの事前計算が行われます。ここで、BSDF はそのローブに関する情報を提供する必要があります。また、必要に応じてライト カリングの効率を高めるために境界を指定する必要があります。bsdf_eval: 指定された入射ライトの方向と現在進行中のビュー方向について BSDF を評価します。BSDF が複数のローブで構成される場合、どのローブを評価する必要があるかは lobe_mask で指定します。各ローブの評価は次のとおりです。weight (BSDF * cos(N.wi) / pdf として定義)。サーフェス法線とライトの入射方向の間の角度のコサイン(余弦)が含まれている必要があります。ウェイトは確率密度によって除算されます。完全な重要度サンプリングが用意されている BSDF の場合、このウェイトは 1 になります。pdf: bsdf_sample を使用してライトの入射光の方向をサンプリングするための確率密度。bsdf_sample: ライトの入射方向をサンプリングし、その方向について BSDF を評価します。次の関数を返します。wi。weight と pdf。同じ入射光の方向に一致します。bsdf_evalbsdf_interior: オプションで、ボリューム内部を埋めるためのボリューム クロージャのリストを返します。典型的な例は、ボリューム吸収クロージャを返すガラスの 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;
}