如果要对Cook-Torrance BRDF进行重要性抽样,该怎么办?
#1 楼
简短答案:重要抽样是一种通过选择接近实际函数形状的估计量来减少蒙特卡洛积分中方差的方法。
PDF是Probability的缩写密度函数。 $ pdf(x)$给出了生成的随机样本为$ x $的概率。
长答案:
首先,让我们回顾一下蒙特卡洛积分,以及数学上的样子。
蒙特卡罗积分是一种估计积分值的技术。通常在没有闭合形式的积分解决方案时使用。看起来是这样的: } {pdf(x_ {i})} $$
用英语说,这可以通过对函数中连续的随机样本求平均来近似积分。随着$ N $变大,近似值越来越接近解决方案。 $ pdf(x_ {i})$表示每个随机样本的概率密度函数。
让我们做一个例子:计算整数$ I $的值。
$$ I = \ int_ {0} ^ {2 \ pi} e ^ {-x} \ sin(x)dx $$
让我们使用Monte Carlo积分:
$$ I \大约\ frac {1} {N} \ sum_ {i = 1} ^ N \ frac {e ^ {-x} \ sin(x_i)} {pdf(x_ {i})} $$
一个简单的python程序来计算它是: br />通过零件分离,我们可以获得确切的解决方案:
$$ I = \ frac {1} {2}(1 − e−2π)= 0.4990663 $$
您会注意到,蒙特卡洛解决方案不太正确。这是因为这是一个估计。也就是说,随着N $达到无穷大,估计值应该越来越接近正确答案。在$ N = 2000 $时,一些运行几乎与正确答案相同。
关于PDF的注释:在这个简单的示例中,我们始终采用统一的随机样本。统一的随机样本意味着每个样本的选择概率完全相同。我们在$ [0,2 \ pi] $范围内进行采样,因此,$ pdf(x)= 1 /(2 \ pi-0)$
重要采样通过不均匀采样而起作用。取而代之的是,我们尝试选择更多对结果有很大贡献的样本(重要),而选择只对结果有少量贡献的样本(重要性不大)。因此,命名为重要性采样。
如果选择的pdf函数与$ f $的形状非常匹配的采样函数,则可以大大减少方差,这意味着您可以减少采样。但是,如果您选择的采样函数的值与$ f $完全不同,则可以增加方差。参见下图:
Wojciech Jarosz论文附录A的图像
路径跟踪中重要采样的一个示例是如何在射线照射到射线之后选择方向表面。如果表面不是完全镜面反射(例如,镜子或玻璃),则出射光线可以在半球中的任何位置。产生新的光线。但是,我们可以利用渲染方程中包含余弦因子的事实:
$$ L _ {\ text {o}}(p,\ omega _ {\ text {o}})= L_ {e}(p,\ omega _ {\ text {o}})+ \ int _ {\ Omega} f(p,\ omega _ {\ text {i}},\ omega _ {\ text {o}})L_ { \ text {i}}(p,\ omega _ {\ text {i}})\ left | \ cos \ theta _ {\ text {i}} \ right | d \ omega _ {\ text {i}} $$
具体地说,我们知道地平线上的任何光线都会被严重衰减(特别是$ \ cos(x)$)。因此,在地平线附近产生的光线对最终值的贡献不大。
为了解决这个问题,我们使用重要性抽样。如果我们根据余弦加权半球生成射线,则可以确保在水平线上方产生更多的射线,而在水平线附近产生的射线更少。这将降低方差并减少噪声。
在您的情况下,您指定将使用基于微面的Cook-Torrance BRDF。常见形式为:
$$ f(p,\ omega _ {\ text {i}},\ omega _ {\ text {o}})= \ frac {F(\ omega _ {\ text {i}},h)G(\ omega _ {\ text {i}},\ omega _ {\ text {o}},h)D(h)} {4 \ cos(\ theta_ {i})\ cos( \ theta_ {o})} $$
其中
$$ F(\ omega _ {\ text {i}},h)= \ text {菲涅耳函数} \ \
G(\ omega _ {\ text {i}},\ omega _ {\ text {o}},h)= \ text {几何遮罩和阴影功能} \\
D(h)= \ text {正态分布函数} $$
博客“ A Graphic's Guy's Note”写了一篇出色的文章,介绍如何对Cook-Torrance BRDF进行采样。我将推荐您阅读他的博客文章。就是说,我将尝试在下面创建一个简短的概述:
NDF通常是Cook-Torrance BRDF的主要部分,因此,如果我们要进行重要性采样,则应基于
Cook-Torrance没有指定要使用的特定NDF;我们可以自由选择适合我们的任何一种。也就是说,有一些流行的NDF:
GGX定义为:
$$ D_ {GGX}(m)= \ frac {\ alpha ^ 2} {\ pi((\\ alpha ^ 2-1)\ cos ^ 2(\ theta)+ 1)^ 2} $$
要采样球坐标角$ \ theta $,我们可以使用以下公式:
$$ \ theta = \ arccos \ left(\ sqrt {\ frac {\ alpha ^ 2} {\ xi_1(\ alpha ^ 2-1-1)+ 1}} \ right)$$
,其中$ \ xi $是统一随机变量。
我们假设NDF是各向同性的,所以我们可以对$ \ phi进行采样$统一:
$$ \ phi = \ xi_ {2} $$
贝克曼定义为:
$$ D_ {贝克曼}(m)= \ frac {1} {\ pi \ alpha ^ 2 \ cos ^ 4(\ theta)} e ^ {-\ frac {\ tan ^ 2(\ theta)} {\ alpha ^ 2}} $$
可以是
$$ \ theta = \ arccos \ left(\ sqrt {\ frac {1} {1 = \ alpha ^ 2 \ ln(1-\ xi_1)}} \ right)\ \
\ phi = \ xi_2 $$
最后,Blinn定义为:
$$ D_ {Blinn}(m)= \ frac {\ alpha + 2} {2 \ pi}(\ cos(\ theta))^ {\ alpha} $$
可以使用以下示例进行采样:
$$ \ theta = \ arccos \ left(\ frac {1} {\ xi_ {1} ^ {\ alpha + 1}} \ right)\\
\ phi = \ xi_2 $$
将其放入在实践中
让我们看一下基本的向后路径跟踪器:
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)
IE。我们在场景中弹跳,并在进行过程中累积颜色和光衰减。每次反弹时,我们都必须为射线选择一个新的方向。如上所述,我们可以对半球进行均匀采样以生成新射线。但是,代码更聪明。重要的是根据BRDF对新方向进行采样。 (注意:这是输入方向,因为我们是反向路径跟踪器)
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);
}
可以实现为:
// 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);
在对inputDirection(代码中的“ wi”)进行采样之后,我们将其用于计算BRDF的值。然后按照蒙特卡洛公式除以pdf:
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;
}
其中Eval()只是BRDF函数本身(Lambert,Blinn-Phong,Cook-托伦斯等):
// Accumulate the brdf attenuation
throughput = throughput * material->Eval(wi, wo, normal) / pdf;
评论
$ \ begingroup $
好的答案。 OP还询问了Cook-Torrance重要性抽样,但答案并未涉及。
$ \ endgroup $
– PeteUK
17年4月13日在8:22
$ \ begingroup $
我更新了答案,添加了关于库克·托伦斯的部分
$ \ endgroup $
–RichieSams
17年4月13日在14:25
$ \ begingroup $
例如GGX,为了采样球坐标角cos(θ),我们使用重要性采样公式来计算角度,然后像往常一样在GGX中使用它?还是公式完全替代了GGX?
$ \ endgroup $
– Arjan Singh
17年4月19日在15:22
$ \ begingroup $
我添加了一个部分来帮助回答您的问题。但是,总之,您的第一种方法是正确的。您可以使用采样公式来生成方向,然后在常规GGX公式中使用该新方向并获得蒙特卡洛公式的pdf。
$ \ endgroup $
–RichieSams
17年4月19日在21:26
$ \ begingroup $
对于GGX,我将如何计算/采样Wi?我了解如何对球坐标角θ进行采样,但是对于实际方向矢量该如何完成?
$ \ endgroup $
– Arjan Singh
17年6月13日在14:59
#2 楼
如果您具有一维函数$ f(x)$,并且想要将该函数从0集成到1,则执行此集成的一种方法是通过获取范围[0,1]中的N个随机样本,求值$ f(x )$,然后计算出样本的平均值。但是,这种“天真”的蒙特卡洛积分被称为“缓慢收敛”,也就是说,您需要大量样本才能接近基本事实,尤其是在函数具有较高频率的情况下。重要抽样,而不是在[0,1]范围内抽取N个随机抽样,而是在$ f(x)$的“重要”区域中抽取更多对最终结果贡献最大的抽样。但是,由于将采样偏向函数的重要区域,因此必须减少这些采样的权重以抵消偏倚,这是PDF(概率密度函数)的来源。 PDF表示给定位置的样本概率,并通过将每个样本除以每个样本位置的PDF值来计算样本的加权平均值。实践是根据正态分布函数NDF分配样本。如果已经对NDF进行了归一化,则可以将其直接用作PDF,这很方便,因为它可以将其从BRDF评估中删除。然后,您只需要做的就是基于PDF分发样本位置,并在不使用NDF术语的情况下评估BRDF,即$$ f = \ frac {FG} {\ pi(n \ cdot \ omega_i)(n \ cdot \ omega_o)} $$
,然后将样本结果的平均值乘以您所积分的区域的立体角(例如,对于半球为$ 2 \ pi $)。需要计算PDF的累积分布函数,以将均匀分布的样本位置转换为PDF加权样本位置。对于各向同性NDF,由于函数的对称性,因此简化为一维函数。有关CDF派生的更多详细信息,请查看此旧的GPU Gems文章。
评论
这是一个很好的链接,解释了什么是PDF。 TL; DR a PDF是描述随机数(连续aka浮点数)的概率的函数。从特定的PDF生成随机数可能很困难,并且有几种技术可以做到这一点。这里谈论其中之一。之后的文章讨论了另一种方式。 blog.demofox.org/2017/08/05/…