重要度サンプリングとは何ですか?


33

重要度サンプリングとは何ですか?私がそれについて読んだすべての記事は「PDF」について言及していますが、それも何ですか?

私が収集したものから、重要度サンプリングは、他よりも重要な半球上の領域のみをサンプリングする手法です。したがって、理想的には、ノイズを減らして速度を上げるために、光線を光源に向けてサンプリングする必要があります。また、いくつかのBRDFのかすめ角では計算にほとんど違いがないため、重要度サンプリングを使用してそれを回避するのは良いことでしょうか。

Cook-Torrance BRDFの重要度サンプリングを実装する場合、どうすればよいですか?


これは、PDFとは何かを説明するリンクです。TL; DR PDFは、(連続する別名浮動小数点)乱数の確率を記述する関数です。特定のPDFから乱数を生成することは困難な場合があり、そのためのテクニックがいくつかあります。これはそのうちの1つについて話します。この後の記事では、別の方法について説明します。blog.demofox.org/2017/08/05/...
アラン・ウルフ

回答:


51

短い答え:

重要度サンプリングは、実際の関数の形状に近い推定量を選択することにより、モンテカルロ積分の分散を減らす方法です。

PDFは、Probability Density Functionの略です。pdf(x)され生成されたランダムサンプルの確率与えるx

長い答え:

まず、モンテカルロ統合とは何か、数学的にどのように見えるかを確認しましょう。

モンテカルロ積分は、積分の値を推定する手法です。通常、積分に対する閉形式の解決策がない場合に使用されます。次のようになります。

f(x)dx1Ni=1Nf(xi)pdf(xi)

英語では、これは、関数からの連続するランダムサンプルを平均化することで積分を近似できることを示しています。N大きくなる、近似解に近づくと近づきます。pdf(xi)は、各ランダムサンプルの確率密度関数を表します。

例を見てみましょう:積分I値を計算します。

I=02πexsin(x)dx

モンテカルロ統合を使用してみましょう:

I1Ni=1Nexsin(xi)pdf(xi)

これを計算する簡単なpythonプログラムは次のとおりです。

import random
import math

N = 200000
TwoPi = 2.0 * math.pi

sum = 0.0

for i in range(N):
    x = random.uniform(0, TwoPi)

    fx = math.exp(-x) * math.sin(x)
    pdf = 1 / (TwoPi - 0.0)

    sum += fx / pdf

I = (1 / N) * sum
print(I)

プログラムを実行すると、I = 0.4986941が得られますI=0.4986941

部品による分離を使用して、正確なソリューションを取得できます。

I=12(1e2π)=0.4990663

あなたは、モンテカルロソリューションが全く正しくないことに気付くでしょう。これは推定値であるためです。つまり、Nが無限大になると、推定値は正解に近づいていくはずです。すでにN=2000いくつかの実行は正解とほとんど同じです。

PDFに関するメモ:この単純な例では、常に一定のランダムサンプルを使用します。均一なランダムサンプルとは、すべてのサンプルが選択される確率がまったく同じであることを意味します。我々は、範囲内のサンプル[0,2π]したがって、pdf(x)=1/(2π0)

重要度サンプリングは、均一にサンプリングしないことで機能します。代わりに、結果に大きく寄与するサンプル(重要)を選択し、結果に少ししか貢献しないサンプル(重要度が低い)を選択します。したがって、名前、重要度サンプリング。

pdfがfの形状に非常に近いサンプリング関数を選択すると、分散を大幅に減らすことができます。つまり、より少ないサンプルを取得できます。ただし、値がfと非常に異なるサンプリング関数を選択した場合、分散を増やすことができます。下の写真を参照してください: 良いサンプリングと悪いサンプリングの比較 Wojciech Jaroszの論文からの画像付録A

パストレースでの重要度サンプリングの1つの例は、サーフェスに到達した後のレイの方向の選択方法です。表面が完全に鏡面ではない場合(つまり、鏡やガラス)、出射光線は半球のどこにでもあります。

発信光線は半球のどこにでも行くことができます

我々は可能性が一様に新しい線を発生させるために半球をサンプリング。ただし、レンダリング方程式には余弦係数が含まれているという事実を活用できます。

Lo(p,ωo)=Le(p,ωo)+Ωf(p,ωi,ωo)Li(p,ωi)|cosθi|dωi

cos(x)

これに対処するために、重要度サンプリングを使用します。余弦重み付き半球に従って光線を生成する場合、水平線のかなり上でより多くの光線が生成され、水平線の近くでは生成されないようにします。これにより、分散が低下し、ノイズが減少します。

あなたのケースでは、マイクロファセットベースのBRDFであるCook-Torranceを使用することを指定しました。一般的な形式は次のとおりです。

fpωωo=FωhGωωohDh4cosθcosθo

どこで

Fωh=フレネル関数Gωωoh=ジオメトリマスキングとシャドウイング機能Dh=正規分布関数

ブログ「A Graphic's Guy's Note」には、Cook-Torrance BRDFのサンプリング方法に関する優れた記事があります。彼のブログ投稿を紹介します。とはいえ、以下の簡単な概要を作成してみます。

NDFは一般的にCook-Torrance BRDFの主要部分であるため、重要度のサンプリングを行う場合は、NDFに基づいてサンプリングする必要があります。

Cook-Torranceは、使用する特定のNDFを指定していません。好みに合わせて自由に選択できます。とはいえ、人気のあるNDFがいくつかあります。

  • GGX
  • ベックマン
  • ブリン

各NDFには独自の式があるため、それぞれを別々にサンプリングする必要があります。それぞれの最終サンプリング関数のみを表示します。公式の導出方法を確認するには、ブログの投稿を参照してください。

GGXは次のように定義されます。

DGGバツm=α2πα21cos2θ+12

θ

θ=アークコスα2ξ1α21+1

ξ

ϕ

ϕ=ξ2

ベックマンは次のように定義されます:

DBeckmannm=1πα2cos4θe日焼け2θα2

以下でサンプリングできます:

θ=アークコス11=α2ln1ξ1ϕ=ξ2

最後に、Blinnは次のように定義されます。

DBlnnm=α+22πcosθα

以下でサンプリングできます:

θ=アークコス1ξ1α+1ϕ=ξ2

実践する

基本的な後方パストレーサーを見てみましょう。

void RenderPixel(uint x, uint y, UniformSampler *sampler) {
    Ray ray = m_scene->Camera.CalculateRayFromPixel(x, y, sampler);

    float3 color(0.0f);
    float3 throughput(1.0f);

    // Bounce the ray around the scene
    for (uint bounces = 0; bounces < 10; ++bounces) {
        m_scene->Intersect(ray);

        // The ray missed. Return the background color
        if (ray.geomID == RTC_INVALID_GEOMETRY_ID) {
            color += throughput * float3(0.846f, 0.933f, 0.949f);
            break;
        }

        // We hit an object

        // Fetch the material
        Material *material = m_scene->GetMaterial(ray.geomID);
        // The object might be emissive. If so, it will have a corresponding light
        // Otherwise, GetLight will return nullptr
        Light *light = m_scene->GetLight(ray.geomID);

        // If we hit a light, add the emmisive light
        if (light != nullptr) {
            color += throughput * light->Le();
        }

        float3 normal = normalize(ray.Ng);
        float3 wo = normalize(-ray.dir);
        float3 surfacePos = ray.org + ray.dir * ray.tfar;

        // Get the new ray direction
        // Choose the direction based on the material
        float3 wi = material->Sample(wo, normal, sampler);
        float pdf = material->Pdf(wi, normal);

        // Accumulate the brdf attenuation
        throughput = throughput * material->Eval(wi, wo, normal) / pdf;


        // Shoot a new ray

        // Set the origin at the intersection point
        ray.org = surfacePos;

        // Reset the other ray properties
        ray.dir = wi;
        ray.tnear = 0.001f;
        ray.tfar = embree::inf;
        ray.geomID = RTC_INVALID_GEOMETRY_ID;
        ray.primID = RTC_INVALID_GEOMETRY_ID;
        ray.instID = RTC_INVALID_GEOMETRY_ID;
        ray.mask = 0xFFFFFFFF;
        ray.time = 0.0f;
    }

    m_scene->Camera.FrameBuffer.SplatPixel(x, y, color);
}

IE。シーンをバウンスして、色と光の減衰を累積します。バウンスごとに、レイの新しい方向を選択する必要があります。上記のように、半球を均一にサンプリングして新しい光線を生成できます。ただし、コードはよりスマートです。重要性は、BRDFに基づいて新しい方向をサンプリングします。(注:これは入力方向です。これは、後方パストレーサーであるためです)

// Get the new ray direction
// Choose the direction based on the material
float3 wi = material->Sample(wo, normal, sampler);
float pdf = material->Pdf(wi, normal);

次のように実装できます:

void LambertBRDF::Sample(float3 outputDirection, float3 normal, UniformSampler *sampler) {
    float rand = sampler->NextFloat();
    float r = std::sqrtf(rand);
    float theta = sampler->NextFloat() * 2.0f * M_PI;

    float x = r * std::cosf(theta);
    float y = r * std::sinf(theta);

    // Project z up to the unit hemisphere
    float z = std::sqrtf(1.0f - x * x - y * y);

    return normalize(TransformToWorld(x, y, z, normal));
}

float3a TransformToWorld(float x, float y, float z, float3a &normal) {
    // Find an axis that is not parallel to normal
    float3a majorAxis;
    if (abs(normal.x) < 0.57735026919f /* 1 / sqrt(3) */) {
        majorAxis = float3a(1, 0, 0);
    } else if (abs(normal.y) < 0.57735026919f /* 1 / sqrt(3) */) {
        majorAxis = float3a(0, 1, 0);
    } else {
        majorAxis = float3a(0, 0, 1);
    }

    // Use majorAxis to create a coordinate system relative to world space
    float3a u = normalize(cross(normal, majorAxis));
    float3a v = cross(normal, u);
    float3a w = normal;


    // Transform from local coordinates to world coordinates
    return u * x +
           v * y +
           w * z;
}

float LambertBRDF::Pdf(float3 inputDirection, float3 normal) {
    return dot(inputDirection, normal) * M_1_PI;
}

inputDirection(コードでは「wi」)をサンプリングした後、それを使用してBRDFの値を計算します。そして、モンテカルロ公式に従ってpdfで除算します:

// Accumulate the brdf attenuation
throughput = throughput * material->Eval(wi, wo, normal) / pdf;

ここで、評価は()だけBRDF関数自体である(ランバート、ブリン・フォン、クック・トーランス、など):

float3 LambertBRDF::Eval(float3 inputDirection, float3 outputDirection, float3 normal) const override {
    return m_albedo * M_1_PI * dot(inputDirection, normal);
}

いい答えだ。OPは、この回答が触れていないクックトーランスの重要度サンプリングについても質問しました。
PeteUK

6
回答を更新して、クックトーランスに関するセクションを追加しました
-RichieSams

たとえばGGXでは、球面座標の角度cos(θ)をサンプリングするために、重要度サンプリング式を使用して角度を計算し、通常どおりGGXで使用しますか?または、式はGGXを完全に置き換えますか?
アルジャンシン

3
質問に答えるのに役立つセクションを追加しました。しかし、要するに、最初の方法は正しいです。あなたは、あなたは、通常のGGX式にその新しい方向性を使用し、方向を生成するためのサンプリングの式を使用し、モンテカルロ式のためのPDFファイルを取得します。
-RichieSams

GGXの場合、どのように計算/サンプリングしwiますか?私は球面座標角θをサンプリングする方法を理解していますが、実際の方向ベクトルについてはどのように行われますか?
アルジャンシン

11

1D関数がある場合 fバツ そして、この関数を0から1まで統合したい場合、この統合を実行する1つの方法は、範囲[0、1]のN個のランダムサンプルを取得することです。 fバツ各サンプルについて、サンプルの平均を計算します。ただし、この「単純な」モンテカルロ積分は「ゆっくり収束する」と言われています。つまり、特に関数の周波数が高い場合、グラウンドトゥルースに近づくには多数のサンプルが必要です。

重要度サンプリングでは、[0、1]範囲のN個のランダムサンプルを取得する代わりに、次の「重要な」領域でより多くのサンプルを取得します。 fバツ最終結果に最も貢献します。ただし、関数の重要な領域にサンプリングをバイアスするため、PDF(確率密度関数)が生じるバイアスに対抗するために、これらのサンプルの重みを小さくする必要があります。PDFは、指定された位置でのサンプルの確率を示し、各サンプルを各サンプル位置でPDF値で除算することにより、サンプルの加重平均を計算するために使用されます。

クックトーランスの重要度サンプリングでは、一般的な方法は、正規分布関数NDFに基づいてサンプルを配布することです。NDFが既に正規化されている場合は、PDFとして直接提供できます。これは、BRDF評価から用語をキャンセルするため便利です。次に必要なのは、PDFに基づいてサンプル位置を配布し、NDF用語なしでBRDFを評価することです。

f=FGπnωnωo
そして、サンプル結果の平均に、積分するドメインの立体角を掛けて計算します(例: 2π 半球用)。

NDFの場合、PDFの累積分布関数を計算して、均一に分布したサンプル位置をPDF加重サンプル位置に変換する必要があります。等方性NDFの場合、関数の対称性により、これは1D関数に単純化されます。CDF派生の詳細については、この古いGPU Gemsの記事をご覧ください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.