Коротка відповідь:
Вибірка важливості - це метод зменшення дисперсії інтеграції Монте-Карло шляхом вибору оцінювача, близького до форми фактичної функції.
PDF - це абревіатура для функції ймовірності щільності . дає можливість випадкової вибірки генерується бути .pdf(x)x
Довга відповідь:
Для початку давайте розглянемо, що таке інтеграція Монте-Карло та як це виглядає математично.
Інтеграція Монте-Карло - це методика оцінки значення інтеграла. Зазвичай він використовується, коли для інтеграла немає рішення закритої форми. Це виглядає приблизно так:
∫f(x)dx≈1N∑i=1Nf(xi)pdf(xi)
Англійською мовою це говорить про те, що ви можете наблизити інтеграл, порівнюючи послідовні випадкові вибірки з функції. Коли стає більшим, наближення стає все ближче і ближче до рішення. представляє функцію щільності ймовірності кожного випадкового вибірки.Npdf(xi)
Давайте зробимо приклад: Обчислити значення інтеграла .I
I=∫2π0e−xsin(x)dx
Давайте скористаємося інтеграцією Монте-Карло:
I≈1N∑i=1Ne−xsin(xi)pdf(xi)
Проста програма 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)
Якщо ми запустимо програму, то отримаємоI=0.4986941
Використовуючи розділення на частини, ми можемо отримати точне рішення:
I=12(1−e−2π)=0.4990663
Ви помітите, що рішення Монте-Карло не зовсім правильне. Це тому, що це оцінка. Однак, коли переходить до нескінченності, оцінка повинна наближатися до наближеної до правильної відповіді. Вже при деякі пробіги майже ідентичні правильній відповіді.NN=2000
Примітка про PDF: У цьому простому прикладі ми завжди беремо рівномірну випадкову вибірку. Рівномірний випадковий зразок означає, що кожен зразок має точно таку ж ймовірність бути обраним. Вибираємо вибір у діапазоні так,[0,2π]pdf(x)=1/(2π−0)
Вибірки Важливість роботи по НЕ рівномірно вибірки. Натомість ми намагаємося вибирати більше зразків, які багато сприяють результату (важливо), і менше вибірок, які лише трохи сприяють результату (менш важливі). Звідси назва, важливість вибірки.
Якщо ви вибрали функцію вибірки, чий pdf дуже відповідає формі , ви можете значно зменшити дисперсію, а це означає, що ви можете брати менше вибірок. Однак якщо ви виберете функцію вибірки, значення якої сильно відрізняється від , ви можете збільшити дисперсію. Дивіться малюнок нижче:
Зображення з дисертації Войцеха Яроша Додаток Аff
Одним із прикладів вибірки важливості при відстеженні шляху є те, як вибрати напрямок променя після потрапляння на поверхню. Якщо поверхня не є ідеально окулярною (наприклад, дзеркалом або склом), виїжджаючий промінь може знаходитися в будь-якому місці півкулі.
Ми могли б рівномірно відібрати півсферу для генерування нового променя. Однак ми можемо використати той факт, що рівняння візуалізації має в ньому косинусний фактор:
Lo(p,ωo)=Le(p,ωo)+∫Ωf(p,ωi,ωo)Li(p,ωi)|cosθi|dωi
Зокрема, ми знаємо, що будь-які промені на горизонті будуть сильно ослаблені (конкретно, ). Отже, промені, що утворюються біля горизонту, не дуже сприятимуть кінцевому значенню.cos(x)
Для боротьби з цим ми використовуємо вибірку важливості. Якщо ми генеруємо промені відповідно до півсфери, зваженої косинусом, ми гарантуємо, що більше променів генерується значно над горизонтом і менше біля горизонту. Це зменшить дисперсію та зменшить рівень шуму.
У вашому випадку ви вказали, що будете використовувати файл BRDF на основі Cookface Torrance. Поширена форма:
f(p,ωi,ωo)=F(ωi,h)G(ωi,ωo,h)D(h)4cos(θi)cos(θo)
де
F(ωi,h)=Fresnel functionG(ωi,ωo,h)=Geometry Masking and Shadowing functionD(h)=Normal Distribution Function
У блозі "Примітка хлопця Графіка" є чудовим записом щодо вибірки BRDF-файлів Cook-Torrance. Я посилаюсь на його допис у блозі . З цього приводу я спробую створити короткий огляд нижче:
NDF, як правило, є домінуючою частиною BRDF Cook-Torrance, тому, якщо ми збираємося до важливої вибірки, ми повинні зробити вибірку на основі NDF.
Cook-Torrance не визначає конкретний NDF для використання; ми можемо вибирати те, що підходить для нашої фантазії. Однак, є кілька популярних НДФ:
Кожен NDF має свою власну формулу, тому кожен повинен відбирати вибірки по-різному. Я збираюся лише показати остаточну функцію вибірки для кожного. Якщо ви хочете побачити, як формується формула, перегляньте допис у блозі.
GGX визначається як:
DGGX(m)=α2π((α2−1)cos2(θ)+1)2
Для вибірки куля сферичних координат ми можемо використовувати формулу:θ
θ=arccos(α2ξ1(α2−1)+1−−−−−−−−−−−−√)
де - рівномірна випадкова величина.ξ
Ми припускаємо, що NDF є ізотропним, тому ми можемо проводити вибірку рівномірно:ϕ
ϕ=ξ2
Бекмана визначають як:
DBeckmann(m)=1πα2cos4(θ)e−tan2(θ)α2
Яку можна взяти на вибірку:
θ=arccos(11=α2ln(1−ξ1)−−−−−−−−−−−−−−√)ϕ=ξ2
Нарешті, Блінн визначається як:
DBlinn(m)=α+22π(cos(θ))α
Яку можна взяти на вибірку:
θ=arccos(1ξα+11)ϕ=ξ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 (Ламберт, Блінн-Фонг, Кук-Торранс тощо):
float3 LambertBRDF::Eval(float3 inputDirection, float3 outputDirection, float3 normal) const override {
return m_albedo * M_1_PI * dot(inputDirection, normal);
}