Was ist eine wichtige Stichprobe?

33

Was ist eine wichtige Stichprobe? In jedem Artikel, den ich darüber lese, wird "PDF" erwähnt. Was ist das auch?

Wie ich erfahre, handelt es sich bei der Wichtigkeitsprobe um eine Technik, mit der nur Bereiche auf einer Hemisphäre untersucht werden, die wichtiger sind als andere. Idealerweise sollte ich also Strahlen auf Lichtquellen richten, um das Rauschen zu verringern und die Geschwindigkeit zu erhöhen. Auch haben einige BRDFs bei Streifwinkeln kaum einen Unterschied in der Berechnung, also ist es gut, eine Stichprobe mit Wichtigkeit zu verwenden, um zu vermeiden, dass dies gut ist?

Wie könnte ich dies tun, wenn ich eine Stichprobe für einen Cook-Torrance-BRDF durchführen würde?

Arjan Singh
quelle
Dies ist ein guter Link, der erklärt, was ein PDF ist. TL; DR a PDF ist eine Funktion, die die Wahrscheinlichkeit von (stetigen oder Gleitkomma) Zufallszahlen beschreibt. Das Generieren von Zufallszahlen aus einer bestimmten PDF-Datei kann eine Herausforderung sein. Hierzu gibt es einige Techniken. Dies spricht über einen von ihnen. Der Artikel danach spricht über einen anderen Weg. blog.demofox.org/2017/08/05/…
Alan Wolfe

Antworten:

51

Kurze Antwort:

Die Wichtigkeitsabtastung ist eine Methode zur Verringerung der Varianz in der Monte-Carlo-Integration, indem ein Schätzer ausgewählt wird, der der Form der tatsächlichen Funktion nahe kommt.

PDF ist eine Abkürzung für Probability Density Function . A pdf(x) gibt die Wahrscheinlichkeit an, dass eine erzeugte Zufallsstichprobe x .

Lange Antwort:

Lassen Sie uns zunächst untersuchen, was Monte-Carlo-Integration ist und wie sie mathematisch aussieht.

Die Monte-Carlo-Integration ist eine Technik zur Schätzung des Wertes eines Integrals. Es wird normalerweise verwendet, wenn es keine geschlossene Formlösung für das Integral gibt. Es sieht aus wie das:

f(x)dx1Nich=1Nf(xich)pdf(xich)

Auf Englisch bedeutet dies, dass Sie ein Integral approximieren können, indem Sie aufeinanderfolgende Zufallsstichproben aus der Funktion mitteln. Wenn N groß wird, kommt die Approximation der Lösung immer näher. pdf(xich) repräsentiert die Wahrscheinlichkeitsdichtefunktion jeder Zufallsstichprobe.

Lassen Sie uns ein Beispiel: Berechnen Sie den Wert des Integrals ich .

ich=02πe-xSünde(x)dx

Nutzen wir die Monte Carlo Integration:

ich1Nich=1Ne-xSünde(xich)pdf(xich)

Ein einfaches Python-Programm, um dies zu berechnen, ist:

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)

Wenn wir das Programm ausführen, erhalten wir I=0.4986941

Durch die Trennung nach Teilen erhalten wir die genaue Lösung:

ich=12(1-e-2π)=0,4990663

Sie werden feststellen, dass die Monte-Carlo-Lösung nicht ganz korrekt ist. Dies liegt daran, dass es sich um eine Schätzung handelt. Das heißt, wenn N gegen unendlich geht, sollte die Schätzung immer näher an die richtige Antwort heranreichen. Bereits bei N=2000 einige Läufe fast identisch mit der richtigen Antwort.

Ein Hinweis zum PDF: In diesem einfachen Beispiel nehmen wir immer eine einheitliche Zufallsstichprobe. Eine einheitliche Zufallsstichprobe bedeutet, dass jede Stichprobe mit genau der gleichen Wahrscheinlichkeit ausgewählt wird. Wir tasten im Bereich [0,2π] also ist pdf(x)=1/(2π-0)

Die Stichprobenerhebung erfolgt durch nicht einheitliche Stichprobenerhebung. Stattdessen versuchen wir, mehr Samples auszuwählen, die viel zum Ergebnis beitragen (wichtig), und weniger Samples, die nur wenig zum Ergebnis beitragen (weniger wichtig). Daher der Name, die Bedeutung der Stichprobe.

Wenn Sie eine Sampling-Funktion wählen, deren PDF-Datei sehr genau der Form von f , können Sie die Varianz stark reduzieren, was bedeutet, dass Sie weniger Samples nehmen können. Wenn Sie jedoch eine Abtastfunktion wählen, deren Wert sich stark von f , können Sie die Varianz erhöhen . Siehe folgendes Bild: Vergleich von guter und schlechter Probenahme Bild aus der Dissertation von Wojciech Jarosz, Anhang A

Ein Beispiel für eine wichtige Abtastung bei der Pfadverfolgung ist die Auswahl der Richtung eines Strahls, nachdem er auf eine Oberfläche auftrifft. Wenn die Oberfläche nicht perfekt spiegelnd ist (z. B. ein Spiegel oder ein Glas), kann der austretende Strahl überall auf der Hemisphäre sein.

Ausgehende Strahlen können überall auf der Hemisphäre auftreten

Wir könnten die Hemisphäre gleichmäßig abtasten, um den neuen Strahl zu erzeugen. Wir können jedoch die Tatsache ausnutzen, dass die Rendering-Gleichung einen Kosinusfaktor enthält:

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

Insbesondere wissen wir, dass alle Strahlen am Horizont stark abgeschwächt werden (speziell cos(x) ). Nahe am Horizont erzeugte Strahlen tragen also nicht viel zum Endwert bei.

Um dem entgegenzuwirken, verwenden wir Wichtigkeitsproben. Wenn wir Strahlen gemäß einer Kosinus-Halbkugel erzeugen, stellen wir sicher, dass mehr Strahlen weit über dem Horizont und weniger in der Nähe des Horizonts erzeugt werden. Dies verringert die Varianz und reduziert das Rauschen.

In Ihrem Fall haben Sie angegeben, dass Sie ein auf Mikrofacetten basierendes Cook-Torrance-BRDF verwenden. Die übliche Form ist:

f(p,ωi,ωo)=F(ωi,h)G(ωi,ωo,h)D(h)4cos(θi)cos(θo)

woher

F(ωi,h)=Fresnel functionG(ωi,ωo,h)=Geometry Masking and Shadowing functionD(h)=Normal Distribution Function

Der Blog "A Graphic's Guy's Note" bietet hervorragende Informationen zum Probieren von Cook-Torrance-BRDFs. Ich werde Sie auf seinen Blog-Beitrag verweisen . Trotzdem werde ich versuchen, im Folgenden eine kurze Übersicht zu erstellen:

Der NDF ist im Allgemeinen der dominierende Anteil des Cook-Torrance-BRDF. Wenn wir also eine Stichprobe von Bedeutung durchführen, sollten wir die Stichprobe auf der Grundlage des NDF durchführen.

Cook-Torrance gibt keinen bestimmten NDF an, der verwendet werden soll. es steht uns frei zu wählen, was unseren Vorstellungen entspricht. Das heißt, es gibt ein paar beliebte NDFs:

  • GGX
  • Beckmann
  • Blinn

Jeder NDF hat eine eigene Formel, daher muss jede anders abgetastet werden. Ich werde nur die endgültige Sampling-Funktion für jeden zeigen. Wenn Sie sehen möchten, wie die Formel abgeleitet wird, lesen Sie den Blog-Beitrag.

GGX ist definiert als:

DGGX(m)=α2π((α2-1)cos2(θ)+1)2

Zum Abtasten des sphärischen Koordinatenwinkels θkönnen wir die Formel verwenden:

θ=Arccos(α2ξ1(α2-1)+1)

woher ξ ist eine einheitliche Zufallsvariable.

Wir gehen davon aus, dass der NDF isotrop ist, damit wir eine Probe entnehmen können ϕ gleichmäßig:

ϕ=ξ2

Beckmann ist definiert als:

DBeckmeinnn(m)=1πα2cos4(θ)e-bräunen2(θ)α2

Welche kann mit probiert werden:

θ=Arccos(11=α2ln(1-ξ1))ϕ=ξ2

Schließlich ist Blinn definiert als:

DBlichnn(m)=α+22π(cos(θ))α

Welche kann mit probiert werden:

θ=Arccos(1ξ1α+1)ϕ=ξ2

Umsetzen in die Praxis

Schauen wir uns einen grundlegenden Pfadfinder für Rückwärtsgänge an:

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. Wir hüpfen durch die Szene und akkumulieren dabei Farbe und Lichtschwächung. Bei jedem Sprung müssen wir eine neue Richtung für den Strahl wählen. Wie oben erwähnt, könnten wir die Hemisphäre gleichmäßig abtasten, um den neuen Strahl zu erzeugen. Der Code ist jedoch schlauer. es ist wichtig, die neue Richtung auf der Grundlage der BRDF abzutasten. (Hinweis: Dies ist die Eingaberichtung, da wir ein Rückwärtspfadverfolger sind.)

// 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);

Welche könnte implementiert werden als:

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;
}

Nachdem wir die inputDirection ('wi' im Code) abgetastet haben, verwenden wir diese, um den Wert der BRDF zu berechnen. Und dann teilen wir nach der Monte-Carlo-Formel durch das pdf:

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

Wobei Eval () nur die BRDF-Funktion selbst ist (Lambert, Blinn-Phong, Cook-Torrance usw.):

float3 LambertBRDF::Eval(float3 inputDirection, float3 outputDirection, float3 normal) const override {
    return m_albedo * M_1_PI * dot(inputDirection, normal);
}
RichieSams
quelle
Gute Antwort. Das OP fragte auch nach Cook-Torrance-Stichproben, die in dieser Antwort nicht angesprochen werden.
PeteUK
6
Ich die Antwort aktualisiert , um einen Abschnitt über Cook-Torrance hinzufügen
RichieSams
Zum Beispiel GGX, um den sphärischen Koordinatenwinkel cos (θ) abzutasten, verwenden wir die abgetastete Wichtigkeitsformel, um den Winkel zu berechnen und diese in GGX wie üblich zu verwenden, oder? Oder ersetzt die Formel GGX vollständig?
Arjan Singh
3
Ich habe einen Abschnitt hinzugefügt, um Ihnen bei der Beantwortung Ihrer Fragen zu helfen. Kurz gesagt, Ihre erste Methode ist richtig. Sie verwenden die Stichprobenformel, um eine Richtung zu generieren, dann verwenden Sie diese neue Richtung in der normalen GGX-Formel und erhalten das PDF für die Monte-Carlo-Formel.
RichieSams
Wie würde ich für GGX berechnen / probieren wi? Ich verstehe, wie man den sphärischen Koordinatenwinkel θ abtastet, aber wie wird das für den tatsächlichen Richtungsvektor gemacht?
Arjan Singh
11

Wenn Sie eine 1D-Funktion haben f(x) und Sie möchten diese Funktion von beispielsweise 0 bis 1 integrieren. Eine Möglichkeit, diese Integration durchzuführen, besteht darin, N Zufallsstichproben im Bereich [0, 1] auszuwerten f(x)für jede Stichprobe und berechnen Sie den Durchschnitt der Stichproben. Diese "naive" Monte-Carlo-Integration soll jedoch "langsam konvergieren", dh Sie benötigen eine große Anzahl von Abtastwerten, um der Grundwahrheit nahe zu kommen, insbesondere wenn die Funktion hohe Frequenzen aufweist.

Bei der Wichtigkeitsabtastung nehmen Sie anstelle von N Zufallsstichproben im Bereich [0, 1] mehr Stichproben in den "wichtigen" Regionen von f(x)Das trägt am meisten zum Endergebnis bei. Da Sie die Abtastung jedoch auf die wichtigen Bereiche der Funktion ausrichten, müssen diese Abtastungen weniger gewichtet werden, um der Verzerrung entgegenzuwirken, mit der die PDF-Funktion (Wahrscheinlichkeitsdichtefunktion) einhergeht. PDF gibt die Wahrscheinlichkeit einer Stichprobe an einer bestimmten Position an und berechnet den gewichteten Durchschnitt der Stichproben, indem jede Stichprobe durch den PDF-Wert an jeder Stichprobenposition dividiert wird.

Bei Cook-Torrance-Stichproben besteht die übliche Praxis darin, Stichproben auf der Grundlage der Normalverteilungsfunktion NDF zu verteilen. Wenn NDF bereits normalisiert ist, kann es direkt als PDF dienen, was praktisch ist, da der Begriff aus der BRDF-Auswertung gestrichen wird. Sie müssen dann nur noch Beispielpositionen auf Basis von PDF verteilen und BRDF ohne den NDF-Begriff auswerten, d. H

f=FGπ(nωich)(nωO)
Und berechnen Sie den Durchschnitt der Probenergebnisse multipliziert mit dem Raumwinkel der Domäne, über die Sie integrieren (z. B. 2π für die Hemisphäre).

Für NDF müssen Sie die kumulative Verteilungsfunktion des PDF- Dokuments berechnen , um die gleichmäßig verteilte Probenposition in die PDF-gewichtete Probenposition zu konvertieren. Für isotrope NDF vereinfacht sich dies aufgrund der Symmetrie der Funktion zur 1D-Funktion. Weitere Informationen zur CDF-Ableitung finden Sie in diesem alten GPU Gems-Artikel .

JarkkoL
quelle