¿Qué es el muestreo de importancia?


33

¿Qué es el muestreo de importancia? Cada artículo que leí al respecto menciona 'PDF', ¿qué es eso también?

Por lo que deduzco, el muestreo de importancia es una técnica para muestrear solo áreas en un hemisferio que importan más que otras. Entonces, idealmente, debería tomar muestras de rayos hacia fuentes de luz para reducir el ruido y aumentar la velocidad. Además, algunos BRDF en ángulos de pastoreo tienen poca diferencia en el cálculo, por lo que usar el muestreo de importancia para evitar eso es bueno.

Si tuviera que implementar un muestreo de importancia para un BRDF Cook-Torrance, ¿cómo podría hacer esto?


Este es un enlace de una buena lectura que explica qué es un PDF. TL; DR a PDF es una función que describe la probabilidad de números aleatorios (punto flotante continuo). Generar números aleatorios a partir de un PDF específico puede ser un desafío y existen algunas técnicas para hacerlo. Esto habla de uno de ellos. El artículo después de este habla de otra manera. blog.demofox.org/2017/08/05/…
Alan Wolfe

Respuestas:


51

Respuesta corta:

El muestreo de importancia es un método para reducir la varianza en la integración de Monte Carlo al elegir un estimador cercano a la forma de la función real.

PDF es una abreviatura de la función de densidad de probabilidad . A pdf(x) da la probabilidad de que una muestra aleatoria generada sea x .

Respuesta larga:

Para comenzar, repasemos qué es la integración de Monte Carlo y cómo se ve matemáticamente.

La integración de Monte Carlo es una técnica para estimar el valor de una integral. Por lo general, se usa cuando no hay una solución de forma cerrada para la integral. Se parece a esto:

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

En inglés, esto dice que puede aproximar una integral promediando muestras aleatorias sucesivas de la función. A medida que norte crece, la aproximación se acerca más y más a la solución. pagsreF(Xyo) representa la función de densidad de probabilidad de cada muestra aleatoria.

Hagamos un ejemplo: Calcular el valor de la integral yo .

yo=0 02πmi-Xpecado(X)reX

Usemos la integración de Monte Carlo:

yo1norteyo=1nortemi-Xpecado(Xyo)pagsreF(Xyo)

Un programa simple de Python para calcular esto es:

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)

Si ejecutamos el programa obtenemos yo=0.4986941

Usando la separación por partes, podemos obtener la solución exacta:

yo=12(1-mi-2π)=0.4990663

Notarás que la solución Monte Carlo no es del todo correcta. Esto se debe a que es una estimación. Dicho esto, a medida que norte llega al infinito, la estimación debería acercarse cada vez más a la respuesta correcta. Ya en norte=2000 algunas corridas son casi idénticas a la respuesta correcta.

Una nota sobre el PDF: en este sencillo ejemplo, siempre tomamos una muestra aleatoria uniforme. Una muestra aleatoria uniforme significa que cada muestra tiene exactamente la misma probabilidad de ser elegida. Tomamos muestras en el rango [0 0,2π] , entonces, pagsreF(X)=1/ /(2π-0 0)

El muestreo de importancia funciona mediante un muestreo no uniforme. En cambio, tratamos de elegir más muestras que contribuyan mucho al resultado (importante) y menos muestras que solo contribuyan un poco al resultado (menos importante). De ahí el nombre, muestreo de importancia.

FFComparación de buen muestreo versus mal muestreo

Un ejemplo de muestreo importante en Path Tracing es cómo elegir la dirección de un rayo después de que golpea una superficie. Si la superficie no es perfectamente especular (es decir, un espejo o vidrio), el rayo saliente puede estar en cualquier parte del hemisferio.

Los rayos salientes pueden ir a cualquier parte del hemisferio.

Nos podíamos probar de manera uniforme el hemisferio para generar el nuevo rayo. Sin embargo, podemos explotar el hecho de que la ecuación de representación tiene un factor coseno:

Lo(pags,ωo)=Lmi(pags,ωo)+ΩF(pags,ωyo,ωo)Lyo(pags,ωyo)El |cosθyoEl |reωyo

cos(X)

Para combatir esto, utilizamos muestras de importancia. Si generamos rayos de acuerdo con un hemisferio ponderado de coseno, nos aseguramos de que se generen más rayos muy por encima del horizonte y menos cerca del horizonte. Esto reducirá la varianza y reducirá el ruido.

En su caso, especificó que utilizará un BRDF Cook-Torrance, basado en microfacet. La forma común es:

F(pags,ωyo,ωo)=F(ωyo,h)sol(ωyo,ωo,h)re(h)4 4cos(θyo)cos(θo)

dónde

F(ωyo,h)=Función Fresnelsol(ωyo,ωo,h)=Función de enmascaramiento y sombreado de geometríare(h)=Función de distribución normal

El blog "A Graphic's Guy's Note" tiene una excelente redacción sobre cómo probar los BRDF de Cook-Torrance. Te referiré a su blog . Dicho esto, intentaré crear una breve descripción general a continuación:

El NDF es generalmente la porción dominante del Cook-Torrance BRDF, por lo que si vamos a una muestra importante, deberíamos tomar una muestra basada en el NDF.

Cook-Torrance no especifica un NDF específico para usar; somos libres de elegir el que más le convenga. Dicho esto, hay algunos NDF populares:

  • GGX
  • Beckmann
  • Blinn

Cada NDF tiene su propia fórmula, por lo tanto, cada uno debe muestrearse de manera diferente. Solo voy a mostrar la función de muestreo final para cada uno. Si desea ver cómo se deriva la fórmula, consulte la publicación del blog.

GGX se define como:

resolsolX(metro)=α2π((α2-1)cos2(θ)+1)2

Para muestrear el ángulo de coordenadas esféricas θ, podemos usar la fórmula:

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

dónde ξ es una variable aleatoria uniforme

Suponemos que el FDN es isotrópico, por lo que podemos tomar muestras ϕ uniformemente

ϕ=ξ2

Beckmann se define como:

resimidokmetrounanortenorte(metro)=1πα2cos4 4(θ)mi-bronceado2(θ)α2

Que se puede muestrear con:

θ=arcos(11=α2En(1-ξ1))ϕ=ξ2

Por último, Blinn se define como:

resilyonortenorte(metro)=α+22π(cos(θ))α

Que se puede muestrear con:

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

Poniéndolo en práctica

Echemos un vistazo a un trazador de ruta básico hacia atrás:

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

ES DECIR. saltamos alrededor de la escena, acumulando color y atenuación de luz a medida que avanzamos. En cada rebote, tenemos que elegir una nueva dirección para el rayo. Como se mencionó anteriormente, podríamos muestrear uniformemente el hemisferio para generar el nuevo rayo. Sin embargo, el código es más inteligente; Su importancia muestra la nueva dirección basada en el BRDF. (Nota: esta es la dirección de entrada, porque somos un trazador de ruta hacia atrás)

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

Que podría implementarse como:

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

Después de muestrear inputDirection ('wi' en el código), lo usamos para calcular el valor de BRDF. Y luego dividimos por el pdf según la fórmula de Monte Carlo:

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

Donde Eval () es solo la función BRDF en sí misma (Lambert, Blinn-Phong, Cook-Torrance, etc.):

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

Buena respuesta. El OP también preguntó sobre el muestreo de importancia de Cook-Torrance que esta respuesta no menciona.
PeteUK


Por ejemplo, GGX, para muestrear el ángulo de coordenadas esféricas cos (θ), usamos la fórmula muestreada de importancia para calcular el ángulo y la usamos en GGX como de costumbre, ¿verdad? ¿O la fórmula reemplaza a GGX por completo?
Arjan Singh

3
Agregué una sección para ayudar a responder sus preguntas. Pero, en resumen, su primer método es correcto. Usas la fórmula de muestreo para generar una dirección, luego usas esa nueva dirección en la fórmula GGX normal y obtienes el pdf para la fórmula de Monte Carlo.
RichieSams

Para GGX, ¿cómo calcularía / muestrearía wi? Entiendo cómo muestrear el ángulo de coordenadas esféricas θ pero para el vector de dirección real, ¿cómo se hace?
Arjan Singh

11

Si tienes una función 1D F(X) y desea integrar esta función de digamos 0 a 1, una forma de realizar esta integración es tomar N muestras aleatorias en el rango [0, 1], evaluar F(X)para cada muestra y calcule el promedio de las muestras. Sin embargo, se dice que esta integración "ingenua" de Monte Carlo "converge lentamente", es decir, que necesita una gran cantidad de muestras para acercarse a la verdad básica, particularmente si la función tiene frecuencias altas.

Con el muestreo de importancia, en lugar de tomar N muestras aleatorias en el rango [0, 1], se toman más muestras en las regiones "importantes" de F(X)que más contribuyen al resultado final. Sin embargo, debido a que sesga el muestreo hacia las regiones importantes de la función, estas muestras deben ponderarse menos para contrarrestar el sesgo, que es donde aparece el PDF (función de densidad de probabilidad). PDF indica la probabilidad de una muestra en una posición determinada y se utiliza para calcular el promedio ponderado de las muestras dividiendo cada muestra con el valor de PDF en cada posición de muestra.

Con el muestreo de importancia Cook-Torrance, la práctica común es distribuir muestras basadas en la función de distribución normal NDF. Si el NDF ya está normalizado, puede servir directamente como PDF, lo cual es conveniente ya que cancela el término de la evaluación BRDF. Lo único que debe hacer es distribuir posiciones de muestra basadas en PDF y evaluar BRDF sin el término NDF, es decir

F=Fsolπ(norteωyo)(norteωo)
Y calcule el promedio de los resultados de la muestra multiplicado por el ángulo sólido del dominio sobre el que integra (p. Ej. 2π para hemisferio).

Para NDF, debe calcular la función de distribución acumulativa del PDF para convertir la posición de muestra distribuida uniformemente en la posición de muestra ponderada de PDF. Para NDF isotrópico, esto se simplifica a la función 1D debido a la simetría de la función. Para obtener más detalles sobre la derivación de CDF, puede consultar este antiguo artículo de GPU Gems .