什么是重要性抽样?


33

什么是重要性抽样?我读到的每篇文章都提到“ PDF”,那又是什么?

据我所知,重要性采样是一种仅对半球比其他区域更重要的区域进行采样的技术。因此,理想情况下,我应该向光源采样光线以减少噪声并提高速度。而且,某些BRDF在掠射角上的计算几乎没有差异,因此使用重要性抽样来避免这种情况是好的吗?

如果要对Cook-Torrance BRDF进行重要性抽样,该怎么做?


这是一个很好的链接,解释了什么是PDF。TL; DR PDF是描述随机数(连续的又称浮点数)的概率的函数。从特定的PDF生成随机数可能很困难,并且有几种技术可以做到这一点。这里谈论其中之一。之后的文章讨论了另一种方式。blog.demofox.org/2017/08/05/…–
艾伦·沃尔夫

Answers:


51

简短答案:

重要采样是一种通过选择接近实际函数形状的估计量来减少蒙特卡洛积分中方差的方法。

PDF是“ 概率密度函数”的缩写。甲给出生成是随机样本的概率。pdFXX

长答案:

首先,让我们回顾一下什么是Monte Carlo积分,以及数学上的外观。

蒙特卡洛积分是一种估计积分值的技术。通常在没有闭合形式的积分解决方案时使用。看起来像这样:

FXdX1个ñ一世=1个ñFX一世pdFX一世

用英语说,这可以通过对函数中连续的随机样本求平均来近似积分。随着变大,近似值越来越接近解。表示每个随机样本的概率密度函数。ñpdFX一世

让我们做一个例子:计算积分的值。一世

一世=02πË-XXdX

让我们使用蒙特卡洛积分:

一世1个ñ一世=1个ñË-XX一世pdFX一世

一个简单的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)

如果我们运行程序,则得到一世=0.4986941

使用零件分离,我们可以获得确切的解决方案:

一世=1个21个-Ë-2π=0.4990663

您会注意到,蒙特卡洛解决方案不太正确。这是因为这是一个估计。也就是说,随着趋于无穷大,估计值应该越来越接近正确答案。在一些运行几乎与正确答案相同。ññ=2000

关于PDF的注释:在这个简单的示例中,我们始终采用统一的随机样本。统一的随机样本意味着每个样本的选择概率完全相同。我们在范围内采样,因此[02π]pdFX=1个/2π-0

重要性抽样的工作方式不是统一抽样。取而代之的是,我们尝试选择更多对结果有很大贡献的样本(重要),而选择只对结果有少量贡献的样本(重要性较低)。故名,重要性抽样。

如果选择pdf与的形状非常匹配的采样函数,则可以大大减少方差,这意味着可以减少采样。但是,如果选择值与完全不同的采样函数,则可以增加方差。参见下图: Wojciech Jarosz 论文附录A的图像FF良好采样与不良采样的比较

“路径跟踪”中重要性采样的一个示例是如何在光线撞击表面后选择其方向。如果表面不是完全镜面反射(例如,镜子或玻璃),则出射光线可以在半球的任何位置。

出射光线可以传播到半球的任何地方

我们可以对半球进行均匀采样以生成新​​射线。但是,我们可以利用以下事实:渲染方程中包含余弦因子:

大号ØpωØ=大号ËpωØ+ΩFpω一世ωØ大号一世pω一世|cosθ一世|dω一世

具体来说,我们知道地平线上的所有光线都会被严重衰减(特别是)。因此,在地平线附近产生的光线对最终值的贡献不大。cosX

为了解决这个问题,我们使用重要性抽样。如果我们根据余弦加权半球生成射线,则可以确保在水平线上方产生更多的射线,而在水平线附近产生的射线更少。这将降低方差并减少噪声。

在您的情况下,您指定将使用基于微面的Cook-Torrance BRDF。常见形式为:

Fpω一世ωØ=Fω一世HGω一世ωØHdH4cosθ一世cosθØ

哪里

Fω一世H=菲涅耳功能Gω一世ωØH=几何遮罩和阴影功能dH=正态分布函数

博客“ A Graphic's Guy's Note”写了一篇出色的文章,介绍了如何对Cook-Torrance BRDF进行采样。我将推荐您阅读他的博客文章。也就是说,我将尝试在下面创建一个简短的概述:

NDF通常是Cook-Torrance BRDF的主要部分,因此,如果要进行重要性采样,则应基于NDF进行采样。

Cook-Torrance没有指定要使用的特定NDF。我们可以自由选择适合我们的任何一种。也就是说,有一些流行的NDF:

  • GGX
  • 贝克曼
  • 布林

每个NDF都有自己的公式,因此每个样本必须以不同的方式采样。我将仅展示每个样本的最终采样功能。如果您想了解公式的派生方式,请参阅博客文章。

GGX定义为:

dGGX=α2πα2-1个cos2θ+1个2

要采样球坐标角,我们可以使用以下公式:θ

θ=arccosα2ξ1个α2-1个+1个

其中是统一随机变量。ξ

我们假设NDF是各向同性的,所以我们可以对均匀采样:ϕ

ϕ=ξ2

贝克曼的定义为:

dËCķ一种ññ=1个πα2cos4θË-棕褐色2θα2

可以通过以下方式采样:

θ=arccos1个1个=α2ln1个-ξ1个ϕ=ξ2

最后,布林定义为:

d一世ññ=α+22πcosθα

可以通过以下方式采样:

θ=arccos1个ξ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;

其中Eval()只是BRDF函数本身(Lambert,Blinn-Phong,Cook-Torrance等):

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

好答案。OP还询问了Cook-Torrance重要性抽样,但此答案并未涉及。
PeteUK '17

6
我更新了答案,添加了关于库克-托伦斯的部分
RichieSams,2013年

例如GGX,要采样球坐标角cos(θ),我们使用重要性采样公式来计算角度,然后像往常一样在GGX中使用该对吧?还是公式完全替代了GGX?
Arjan Singh

3
我添加了一个部分来帮助回答您的问题。但是,总之,您的第一种方法是正确的。您可以使用采样公式生成方向,然后在常规GGX公式中使用该新方向,获取蒙特卡洛公式的pdf。
RichieSams

对于GGX,我将如何计算/采样wi?我知道如何采样球坐标角θ,但是对于实际方向矢量,该怎么做?
Arjan Singh

11

如果您具有一维函数并且想要将该函数从0集成到1,则执行此集成的一种方法是通过取[0,1]范围内的N个随机样本,对每个评估采样并计算样本平均值。但是,这种“幼稚的”蒙特卡洛积分被称为“缓慢收敛”,即,您需要大量样本才能接近基本事实,尤其是在函数具有较高频率的情况下。FXFX

使用重要性采样,您可以在的“重要”区域中获取更多对最终结果贡献最大的样本,而不是在[0,1]范围内获取N个随机样本。但是,由于将采样偏向函数的重要区域,因此必须减少这些采样的权重以抵消偏倚,这是PDF(概率密度函数)所伴随的地方。PDF表示给定位置的样本概率,并通过将每个样本除以每个样本位置的PDF值来计算样本的加权平均值。FX

使用Cook-Torrance重要抽样时,通常的做法是根据正态分布函数NDF分配样本。如果已经对NDF进行了归一化,则可以将其直接用作PDF,这很方便,因为它可以将其从BRDF评估中删除。只有你需要做的事情是一个基于PDF分发样本的位置和评估BRDF没有NDF项,即 和计算样本结果的平均值乘以您积分的区域的立体角(例如,半球为)。

F=FGπñω一世ñωØ
2π

对于NDF,您需要计算PDF的累积分布函数,以将均匀分布的样本位置转换为PDF加权样本位置。对于各向同性NDF,由于函数的对称性,因此简化为一维函数。有关CDF派生的更多详细信息,请查看此旧的GPU Gems文章

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.