짧은 답변:
중요도 샘플링 은 실제 함수의 모양에 가까운 추정기를 선택하여 Monte Carlo Integration의 분산을 줄이는 방법입니다.
PDF 는 확률 밀도 함수 (Probability Density Function )의 약자입니다 . 인 랜덤 샘플 생성 확률 제공 .pdf(x)x
긴 답변 :
우선, Monte Carlo Integration의 정의와 수학적으로 어떤 것이 있는지 살펴 보겠습니다.
Monte Carlo Integration은 적분 값을 추정하는 기술입니다. 일반적으로 적분에 대한 닫힌 양식 솔루션이 없을 때 사용됩니다. 다음과 같이 보입니다 :
∫f(x)dx≈1N∑i=1Nf(xi)pdf(xi)
영어에서, 이것은 함수에서 연속적인 무작위 샘플을 평균화하여 적분을 근사화 할 수 있다고 말합니다. 으로 커질, 근사는 점점 더 가까이 솔루션을 가져옵니다. 는 각 랜덤 샘플의 확률 밀도 함수를 나타냅니다.Npdf(xi)
예를 들어 보자 : 적분 의 값을 계산하십시오 .I
I=∫2π0e−xsin(x)dx
Monte Carlo Integration을 사용합시다 :
I≈1N∑i=1Ne−xsin(xi)pdf(xi)
이것을 계산하는 간단한 파이썬 프로그램은 다음과 같습니다.
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
Monte Carlo Solution이 정확하지 않다는 것을 알 수 있습니다. 추정치이기 때문입니다. 즉, 이 무한대에 가까울수록 추정값이 정답에 가까워지고 가까워 져야합니다. 이미 일부 런은 정답과 거의 동일합니다.NN=2000
PDF에 대한 참고 사항 :이 간단한 예에서는 항상 균일 한 임의 샘플을 가져옵니다. 균일 한 랜덤 샘플은 모든 샘플이 정확히 같은 확률로 선택 될 수 있음을 의미합니다. 범위에서 샘플링 하므로[0,2π]pdf(x)=1/(2π−0)
중요도 샘플링은 균일 하지 않은 샘플링으로 작동합니다 . 대신 결과에 많은 기여를하는 더 많은 샘플 (중요)을 선택하고 결과에 약간만 기여하는 적은 샘플을 선택합니다 (중요하지 않음). 따라서 이름, 중요도 샘플링.
pdf가 의 모양과 매우 일치하는 샘플링 함수를 선택 하면 분산을 크게 줄일 수 있으므로 샘플 수를 줄일 수 있습니다. 그러나 값이 와 매우 다른 샘플링 함수를 선택 하면 분산을 증가시킬 수 있습니다 . 아래 그림을보십시오 :
Wojciech Jarosz의 논문 부록 Aff
경로 추적에서 중요도 샘플링의 한 예는 광선이 표면에 닿은 후 광선의 방향을 선택하는 방법입니다. 표면이 완벽하게 반사되지 않는 경우 (예 : 거울 또는 유리), 나가는 광선은 반구의 어느 곳에 나있을 수 있습니다.
우리는 할 수 균일 새로운 광선을 생성하는 반구 샘플. 그러나 렌더링 방정식에 코사인 요소가 있다는 사실을 활용할 수 있습니다.
Lo(p,ωo)=Le(p,ωo)+∫Ωf(p,ωi,ωo)Li(p,ωi)|cosθi|dωi
특히, 수평선의 광선은 크게 감쇠됩니다 (특히 ). 따라서 수평선 근처에서 생성 된 광선은 최종 값에 크게 기여하지 않습니다.cos(x)
이를 방지하기 위해 중요도 샘플링을 사용합니다. 코사인 가중치 반구에 따라 광선을 생성하면 수평선 위, 수평선 근처에서는 광선이 더 많이 생성됩니다. 이렇게하면 분산이 줄어들고 노이즈가 줄어 듭니다.
귀하의 경우, Cook-Torrance, 마이크로 패싯 기반 BRDF를 사용하도록 지정했습니다. 일반적인 형태는 다음과 같습니다.
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
블로그 "A Graphic 's Guy 's Note"에는 Cook-Torrance BRDF를 샘플링하는 방법에 대한 훌륭한 글이 있습니다. 나는 그의 블로그 포스트 를 참조 할 것 입니다. 즉, 아래에서 간단한 개요를 작성하려고합니다.
NDF는 일반적으로 Cook-Torrance BRDF의 지배적 인 부분이므로 중요도 샘플을 보려면 NDF를 기반으로 샘플링해야합니다.
Cook-Torrance는 사용할 특정 NDF를 지정하지 않습니다. 우리는 우리의 공상에 맞는 것을 자유롭게 선택할 수 있습니다. 그러나 몇 가지 인기있는 NDF가 있습니다.
각 NDF에는 고유 한 공식이 있으므로 각각 다르게 샘플링해야합니다. 각각에 대한 최종 샘플링 기능 만 보여줄 것입니다. 공식이 어떻게 파생되는지 보려면 블로그 게시물을 참조하십시오.
GGX 는 다음과 같이 정의됩니다.
DGGX(m)=α2π((α2−1)cos2(θ)+1)2
구형 좌표 각도 를 샘플링하기 위해 다음 공식을 사용할 수 있습니다.θ
θ=arccos(α2ξ1(α2−1)+1−−−−−−−−−−−−√)
여기서 는 균일 한 랜덤 변수입니다.ξ
NDF가 등방성이라고 가정하여 균일하게 샘플링 할 수 있습니다 .ϕ
ϕ=ξ2
Beckmann 은 다음과 같이 정의됩니다.
DBeckmann(m)=1πα2cos4(θ)e−tan2(θ)α2
다음으로 샘플링 할 수 있습니다 :
θ=arccos(11=α2ln(1−ξ1)−−−−−−−−−−−−−−√)ϕ=ξ2
마지막으로 Blinn 은 다음과 같이 정의됩니다.
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 값을 계산합니다. 그리고 우리는 Monte Carlo 공식에 따라 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);
}