这一部是优化之前的渲染,我们之前的渲染有很多噪声,我们只有提高采样点数量才行,但是这会降低性能,那么如何才能在性能和画面做权衡?
1·蒙特卡洛Monte Carlo, 二维MC积分
两种随机算法:
Monte Carlo算法可能给出近似解(有偏的,但可以收敛到正确解),算法的运行时间通常是固定的,会以概率形式给出近似值
而Las Vegas算法总是提供精确解,算法的运行时间是不确定的,取决于找到正确答案所需的时间,比如代码中的random_in_unit_sphere()函数
收敛速度:花费越少的样本,更快求得近似解
例子:用蒙特卡洛方法估计PI:
我们知道圆形和方形的面积比,当r==1时== π / 4,πr^2 / (2r)^2 = π / 4,面积比就是概率比
π == 4 * (圆形和方形的概率比),总样本为N个(落在方形的概率),落在单位球面的样本为inside_circle == if (x*x + y*y < 1)个
int main() {
std::cout << std::fixed << std::setprecision(12);
int inside_circle = 0;
int N = 100000;
for (int i = 0; i < N; i++) {
auto x = random_double(-1,1);
auto y = random_double(-1,1);
if (x*x + y*y < 1)
inside_circle++;
}
std::cout << "Estimate of Pi = " << (4.0 * inside_circle) / N << '\n';
}
显示收敛效果:
我们可以改为一直运行,当运行到100000的倍速次打印PI值,这样我们可以观测收敛结果,可以发现,当样本总数越大,结果越准确
int inside_circle = 0;
int runs = 0;
while (true) {
runs++;
auto x = random_double(-1,1);
auto y = random_double(-1,1);
if (x*x + y*y < 1)
inside_circle++;
if (runs % 100000 == 0)
std::cout << "\rEstimate of Pi = " << (4.0 * inside_circle) / runs;
分层采样:
我们把正方形分成100万个网格,在每个像素内生成随机采样点,
((i + random_double()) / sqrt_N),首先在每个像素生成随机位置,通过 / sqrt_N变换到0--1的正方形内
通过*2-1变换到-1---1的单位立方体内,这样的结果更准确
int inside_circle = 0;
int inside_circle_stratified = 0;
int sqrt_N = 1000;
for (int i = 0; i < sqrt_N; i++) {
for (int j = 0; j < sqrt_N; j++) {
x = 2*((i + random_double()) / sqrt_N) - 1;
y = 2*((j + random_double()) / sqrt_N) - 1;
if (x*x + y*y < 1)
inside_circle_stratified++;
}
}
std::cout
<< "Stratified Estimate of Pi = "
<< (4.0 * inside_circle_stratified) / (sqrt_N*sqrt_N) << '\n';
好了,让我们的追踪器使用蒙特卡洛吧!
我们不再使用for (int sample = 0; sample < samples_per_pixel; sample++) 在像素内随机采样
使用,其中sqrt_spp是原来samples_per_pixel像素采样次数的平方根(嵌套后还是原来的采样次数),recip_sqrt_spp = 1.0 / sqrt_spp;
for (int s_j = 0; s_j < sqrt_spp; s_j++)
{
for (int s_i = 0; s_i < sqrt_spp; s_i++)
{
ray r = get_ray(i, j, s_i, s_j);
pixel_color += ray_color(r, max_depth, world);
}
}
然后用蒙特卡洛方式随机采样,首先通过* recip_sqrt_spp变换到0--1的范围内,然后- 0.5变换到-0.5---0.5的范围
vec3 sample_square_stratified(int s_i, int s_j) const
{
auto px = ((s_i + random_double()) * recip_sqrt_spp) - 0.5;
auto py = ((s_j + random_double()) * recip_sqrt_spp) - 0.5;
return vec3(px, py, 0);
}
原有的:
蒙特卡洛的:
低差异序列:(比如Hammersley序列)
样本分布更均匀:具有更快的收敛速度
2·一维MC
蒙特卡洛积分:是一种求解积分的方式,采用离散的思想,方便了那些无法解的积分
从a到b区间的积分(dx是积分的标准写法),fx被积函数
N是随机采样的样本数,1/N是权重,fxk为随机点的高,pxk为随机点的概率(PDF(Probability Density Function,概率密度函数))
用蒙特卡洛解积分:例如被积函数x^2(在0--2之间)
相当于在区间的面积 I,或者2 * 平均值(MC方法采样求平均值)
int main()
{
int N = 1000000;
auto sum = 0.0;
for (int i = 0; i < N; i++)
{
auto x = random_double(0,2);
sum += x*x;
}
std::cout << std::fixed << std::setprecision(12);
std::cout << "I = " << 2.0 * (sum/(float)N) << '\n';
}
让我们回到追踪器,我们之前的采样方式是均匀 的,每个值的概率都是相同的,这次我们重要性采样,以便加快收敛速度
概率密度函数PDF: