คำตอบสั้น ๆ :
การสุ่มตัวอย่างความสำคัญเป็นวิธีการลดความแปรปรวนในการรวม Monte Carlo โดยเลือกตัวประมาณใกล้กับรูปร่างของฟังก์ชันจริง
รูปแบบไฟล์ PDFเป็นตัวย่อสำหรับฟังก์ชั่นน่าจะเป็นความหนาแน่น ให้น่าจะเป็นของตัวอย่างที่สุ่มที่สร้างขึ้นเป็นxหน้า dฉ( x )x
คำตอบยาว:
ในการเริ่มต้นให้ตรวจสอบการรวม Monte Carlo คืออะไรและดูเหมือนว่าทางคณิตศาสตร์
การบูรณาการ Monte Carlo เป็นเทคนิคในการประมาณค่าของอินทิกรัล โดยทั่วไปจะใช้เมื่อไม่มีโซลูชันแบบปิดสำหรับอินทิกรัล ดูเหมือนว่านี้:
∫ฉ( x )dx ≈ 1ยังไม่มีข้อความΣi = 1ยังไม่มีข้อความฉ( xผม)หน้า dฉ( xผม)
ในภาษาอังกฤษสิ่งนี้บอกว่าคุณสามารถประมาณค่าอินทิกรัลได้โดยการสุ่มตัวอย่างแบบต่อเนื่องเฉลี่ยจากฟังก์ชัน เมื่อมีขนาดใหญ่การประมาณจะเข้าใกล้โซลูชันมากขึ้น หมายถึงฟังก์ชันความหนาแน่นของความน่าจะเป็นของตัวอย่างแต่ละตัวอย่างยังไม่มีข้อความหน้า dฉ( xผม)
ขอทำตัวอย่าง: การคำนวณมูลค่าของการหนึ่งฉันผม
ผม= ∫2 π0อี- xบาป( x ) dx
มาใช้ Monte Carlo Integration:
ผม≈ 1ยังไม่มีข้อความΣi = 1ยังไม่มีข้อความอี- xบาป( xผม)พี dฉ( xผม)
โปรแกรมไพ ธ อนง่าย ๆ ในการคำนวณคือ:
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
ด้วยการแยกส่วนต่าง ๆ เราจะได้คำตอบที่ถูกต้อง:
ผม= 12( 1 - e - 2 π ) = 0.4990663
คุณจะสังเกตเห็นว่าโซลูชัน Monte Carlo นั้นไม่ถูกต้องนัก นี่เป็นเพราะมันเป็นค่าประมาณ ที่กล่าวว่าเมื่อไปที่อนันต์การประมาณควรใกล้เคียงกับคำตอบที่ถูกต้องมากขึ้น ตอนนี้ที่วิ่งบางครั้งเกือบเหมือนคำตอบที่ถูกต้องยังไม่มีข้อความยังไม่มีข้อความ= 2000
หมายเหตุเกี่ยวกับ PDF: ในตัวอย่างง่ายๆนี้เรามักจะสุ่มตัวอย่างแบบสม่ำเสมอ ตัวอย่างแบบสุ่มที่สม่ำเสมอหมายถึงทุกตัวอย่างมีความน่าจะเป็นเหมือนกันทุกประการในการเลือก เราสุ่มตัวอย่างในช่วงดังนั้น,[ 0 , 2] π]พี dฉ( x ) = 1 / ( 2) π- 0 )
การสุ่มตัวอย่างความสำคัญทำงานโดยไม่สุ่มตัวอย่างอย่างสม่ำเสมอ แต่เราพยายามเลือกตัวอย่างเพิ่มเติมที่มีส่วนร่วมกับผลลัพธ์ (สำคัญ) และตัวอย่างน้อยลงที่ให้ผลเพียงเล็กน้อย (สำคัญน้อยกว่า) ดังนั้นชื่อตัวอย่างความสำคัญ
หากคุณเลือกฟังก์ชั่นการสุ่มตัวอย่างที่มี pdf ตรงกับรูปร่างของมากคุณสามารถลดความแปรปรวนได้อย่างมากซึ่งหมายความว่าคุณสามารถสุ่มตัวอย่างได้น้อยลง อย่างไรก็ตามหากคุณเลือกฟังก์ชั่นการสุ่มตัวอย่างซึ่งมีค่าแตกต่างจากมากคุณสามารถเพิ่มความแปรปรวนได้ ดูรูปภาพด้านล่าง:
รูปภาพจากวิทยานิพนธ์ของ Wojciech Jarosz ภาคผนวก Aฉฉ
ตัวอย่างหนึ่งของการสุ่มตัวอย่างที่สำคัญในการติดตามเส้นทางคือวิธีการเลือกทิศทางของรังสีหลังจากที่กระทบพื้นผิว หากพื้นผิวไม่ได้เป็นแบบ specular อย่างสมบูรณ์ (เช่นกระจกหรือกระจก) รังสีขาออกสามารถอยู่ที่ใดก็ได้ในซีกโลก
เราสามารถสุ่มตัวอย่างซีกโลกเพื่อสร้างรังสีใหม่ อย่างไรก็ตามเราสามารถใช้ประโยชน์จากความจริงที่ว่าสมการการแสดงผลมีปัจจัยโคไซน์ใน:
Lโอ( p , ωโอ) = Lอี( p , ωโอ) + ∫Ωฉ( p , ωผม, ωโอ) ลผม( p , ωผม) | cosθผม| dωผม
โดยเฉพาะเรารู้ว่ารังสีใด ๆ ที่ขอบฟ้าจะถูกลดทอนอย่างหนัก (โดยเฉพาะ ) ดังนั้นรังสีที่เกิดขึ้นใกล้ขอบฟ้าจะไม่ส่งผลต่อค่าสุดท้ายมากนักcos( x )
เพื่อต่อสู้กับสิ่งนี้เราใช้การสุ่มตัวอย่างที่สำคัญ หากเราสร้างรังสีตามซีกน้ำหนักถ่วงโลกเรามั่นใจว่ามีการสร้างรังสีมากกว่าขอบฟ้าและใกล้กับขอบฟ้าน้อยกว่า สิ่งนี้จะลดความแปรปรวนและลดเสียงรบกวน
ในกรณีของคุณคุณระบุว่าคุณจะใช้ BRDF ที่ใช้ Cook-Torrance รูปแบบทั่วไปคือ:
ฉ( p , ωผม, ωโอ) = F( ωผม, h ) G ( ωผม, ωโอ, h ) D ( h )4 cos( θผม) cos( θโอ)
ที่ไหน
F( ωผม, h ) = ฟังก์ชั่นเฟรสเนลG ( ωผม, ωโอ, h ) = ฟังก์ชั่นการปิดบังทางเรขาคณิตและการบังแสงD ( h ) = ฟังก์ชั่นการกระจายปกติ
บล็อก "A Graphic's Guy's Note" มีการเขียนที่ยอดเยี่ยมเกี่ยวกับวิธีการตัวอย่าง Cook-Torrance BRDF ผมจะนำคุณไปยังโพสต์บล็อกของเขา ที่กล่าวว่าฉันจะพยายามสร้างภาพรวมสั้น ๆ ด้านล่าง:
NDF โดยทั่วไปเป็นส่วนที่โดดเด่นของ Cook-Torrance BRDF ดังนั้นหากเราต้องการตัวอย่างที่สำคัญเราควรสุ่มตัวอย่างตาม NDF
Cook-Torrance ไม่ได้ระบุ NDF เฉพาะที่จะใช้ เรามีอิสระที่จะเลือกสิ่งที่เหมาะสมกับความแฟนซีของเรา ที่กล่าวว่ามี NDF ที่นิยมไม่กี่:
แต่ละ NDF มีสูตรของตัวเองดังนั้นแต่ละตัวอย่างจะต้องแตกต่างกัน ฉันจะแสดงฟังก์ชันการสุ่มตัวอย่างสุดท้ายสำหรับแต่ละรายการเท่านั้น หากคุณต้องการดูว่าสูตรได้รับมาอย่างไรดูโพสต์บล็อก
GGXถูกกำหนดเป็น:
DG G X( m ) = α2π( ( α2- 1 ) cos2( θ ) + 1 )2
ในการเก็บตัวอย่างทรงกลมพิกัดมุมเราสามารถใช้สูตรได้:θ
θ = arccos( α2ξ1( α2- 1 ) + 1------------√)
โดยที่เป็นตัวแปรสุ่มแบบสม่ำเสมอξ
เราคิดว่า NDF isotropic ดังนั้นเราจึงสามารถสุ่มตัวอย่างอย่างสม่ำเสมอ:φ
ϕ = ξ2
Beckmannหมายถึง:
DB e c k m a n n( m ) = 1πα2cos4( θ )อี- ผิวสีแทน2( θ )α2
ซึ่งสามารถสุ่มตัวอย่างด้วย:
θ=arccos(11=α2ln(1−ξ1)−−−−−−−−−−−−−−√)ϕ=ξ2
สุดท้ายBlinnถูกกำหนดเป็น:
DBlinn(m)=α+22π(cos(θ))α
ซึ่งสามารถสุ่มตัวอย่างด้วย:
θ=arccos(1ξα+11)ϕ=ξ2
วางไว้ในการปฏิบัติ
ลองดูที่ tracer path แบบพื้นฐานย้อนหลัง:
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);
}