পাথ ট্রেসিংয়ের একাধিক ক্ষেত্র রয়েছে যা নমুনাযুক্ত হয়ে উঠতে পারে। এছাড়াও, সেই ক্ষেত্রগুলির প্রত্যেকটি একাধিক গুরুত্ব স্যাম্পলিং ব্যবহার করতে পারে, যা প্রথম ভ্যাচ এবং গুইবার 1995 এর কাগজে প্রস্তাবিত হয়েছিল । আরও ভাল ব্যাখ্যা করতে, আসুন পিছনের পথের ট্রেসারটি দেখুন:
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);
SurfaceInteraction interaction;
// Bounce the ray around the scene
const uint maxBounces = 15;
for (uint bounces = 0; bounces < maxBounces; ++bounces) {
m_scene->Intersect(ray);
// The ray missed. Return the background color
if (ray.GeomID == INVALID_GEOMETRY_ID) {
color += throughput * m_scene->BackgroundColor;
break;
}
// 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 emission
if (light != nullptr) {
color += throughput * light->Le();
}
interaction.Position = ray.Origin + ray.Direction * ray.TFar;
interaction.Normal = normalize(m_scene->InterpolateNormal(ray.GeomID, ray.PrimID, ray.U, ray.V));
interaction.OutputDirection = normalize(-ray.Direction);
// Get the new ray direction
// Choose the direction based on the bsdf
material->bsdf->Sample(interaction, sampler);
float pdf = material->bsdf->Pdf(interaction);
// Accumulate the weight
throughput = throughput * material->bsdf->Eval(interaction) / pdf;
// Shoot a new ray
// Set the origin at the intersection point
ray.Origin = interaction.Position;
// Reset the other ray properties
ray.Direction = interaction.InputDirection;
ray.TNear = 0.001f;
ray.TFar = infinity;
// Russian Roulette
if (bounces > 3) {
float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
if (sampler->NextFloat() > p) {
break;
}
throughput *= 1 / p;
}
}
m_scene->Camera->FrameBufferData.SplatPixel(x, y, color);
}
ইংরেজীতে:
- দৃশ্যের মাধ্যমে একটি রশ্মি অঙ্কুর
- আমরা কিছু আঘাত করে কিনা তা পরীক্ষা করে দেখুন। যদি না হয় আমরা স্কাইবক্সের রঙ এবং বিরতি ফিরিয়ে দেব।
- আমরা কোন আলো মারি কিনা তা পরীক্ষা করে দেখুন। যদি তা হয় তবে আমরা আমাদের রঙ জমে হালকা নির্গমন যুক্ত করি
- পরবর্তী রশ্মির জন্য একটি নতুন দিক চয়ন করুন। আমরা বিআরডিএফ এর উপর ভিত্তি করে এটি অভিন্ন বা গুরুত্বের নমুনাটি করতে পারি
- বিআরডিএফকে মূল্যায়ন করুন এবং এটি জমা করুন। মন্টি কার্লো অ্যালগরিদম অনুসরণ করতে এখানে আমাদের আমাদের নির্বাচিত দিকের পিডিএফ দ্বারা ভাগ করতে হবে।
- আমাদের নির্বাচিত দিকের উপর ভিত্তি করে একটি নতুন রশ্মি তৈরি করুন এবং আমরা সবেমাত্র এসেছি
- [Ptionচ্ছিক] আমাদের যদি রশ্মি বন্ধ করা উচিত কিনা তা চয়ন করতে রাশিয়ান রুলেট ব্যবহার করুন
- গোটো ঘ
এই কোডটি সহ, কেবলমাত্র রঙটি পাই যদি শেষ পর্যন্ত রশ্মি একটি আলোতে আঘাত করে। এগুলির পাশাপাশি, এটি নিয়মিত আলোর উত্সগুলিকে সমর্থন করে না, কারণ তাদের কোনও অঞ্চল নেই।
এটি ঠিক করতে, আমরা প্রতিটি বাউন্সে সরাসরি লাইট নমুনা করি। আমাদের কয়েকটি ছোট পরিবর্তন করতে হবে:
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);
SurfaceInteraction interaction;
// Bounce the ray around the scene
const uint maxBounces = 15;
for (uint bounces = 0; bounces < maxBounces; ++bounces) {
m_scene->Intersect(ray);
// The ray missed. Return the background color
if (ray.GeomID == INVALID_GEOMETRY_ID) {
color += throughput * m_scene->BackgroundColor;
break;
}
// 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 this is the first bounce or if we just had a specular bounce,
// we need to add the emmisive light
if ((bounces == 0 || (interaction.SampledLobe & BSDFLobe::Specular) != 0) && light != nullptr) {
color += throughput * light->Le();
}
interaction.Position = ray.Origin + ray.Direction * ray.TFar;
interaction.Normal = normalize(m_scene->InterpolateNormal(ray.GeomID, ray.PrimID, ray.U, ray.V));
interaction.OutputDirection = normalize(-ray.Direction);
// Calculate the direct lighting
color += throughput * SampleLights(sampler, interaction, material->bsdf, light);
// Get the new ray direction
// Choose the direction based on the bsdf
material->bsdf->Sample(interaction, sampler);
float pdf = material->bsdf->Pdf(interaction);
// Accumulate the weight
throughput = throughput * material->bsdf->Eval(interaction) / pdf;
// Shoot a new ray
// Set the origin at the intersection point
ray.Origin = interaction.Position;
// Reset the other ray properties
ray.Direction = interaction.InputDirection;
ray.TNear = 0.001f;
ray.TFar = infinity;
// Russian Roulette
if (bounces > 3) {
float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
if (sampler->NextFloat() > p) {
break;
}
throughput *= 1 / p;
}
}
m_scene->Camera->FrameBufferData.SplatPixel(x, y, color);
}
প্রথমে আমরা "রঙ + = থ্রুপুট * স্যাম্পললাইট (...)" যুক্ত করি। আমি স্যাম্পললাইট () সম্পর্কে কিছুটা বিশদে যাব। তবে, মূলত, এটি সমস্ত লাইটের মধ্যে ছড়িয়ে পড়ে এবং বিএসডিএফ দ্বারা তত্পর হয়ে রঙে তাদের অবদানটি ফিরিয়ে দেয়।
এটি দুর্দান্ত, তবে এটি সঠিক করতে আমাদের আরও একটি পরিবর্তন করা দরকার; বিশেষত, যখন আমরা একটি আলো আঘাত করি তখন কী হয়। পুরানো কোডে, আমরা রঙের জমে আলোর নিঃসরণ যুক্ত করেছি। তবে এখন আমরা প্রতিটি বাউন্সে সরাসরি আলোকে নমুনা করি, তাই আমরা যদি আলোর নিঃসরণ যুক্ত করি তবে আমরা "ডাবল ডিপ" করব। সুতরাং, সঠিক জিনিসটি হ'ল ... কিছুই নয়; আমরা আলোর নিঃসরণ জমে এড়িয়ে চলি।
তবে দুটি কর্নার কেস রয়েছে:
- প্রথম রশ্মি
- পুরোপুরি স্পেকুলার বাউন্স (ওরফে মিরর)
যদি প্রথম রশ্মি আলোকে আঘাত করে তবে আপনার উচিত আলোর নির্গমন সরাসরি দেখা উচিত। সুতরাং আমরা যদি এটি এড়িয়ে চলে যাই তবে সমস্ত আলোকসজ্জা কালো হিসাবে প্রদর্শিত হবে যদিও তাদের চারপাশের পৃষ্ঠতলগুলি আলোকিত হয়।
আপনি যখন পুরোপুরি স্পেকুলার পৃষ্ঠগুলিতে আঘাত করেন আপনি সরাসরি কোনও আলোর নমুনা করতে পারবেন না, কারণ একটি ইনপুট রশ্মির কেবল একটি আউটপুট থাকে। আচ্ছা, প্রযুক্তিগতভাবে, আমরা ইনপুট রশ্মিটি হালকা আঘাত করতে চলেছে কিনা তা পরীক্ষা করে দেখতে পারি , তবে এর কোনও মানে নেই; মূল পাথ ট্রেসিং লুপ যেভাবেই করতে চলেছে। অতএব, আমরা যদি একটি স্পেসুলার পৃষ্ঠকে আঘাত করার ঠিক পরে একটি হালকা আঘাত করি তবে আমাদের রঙটি সংগ্রহ করতে হবে। যদি আমরা না করি তবে লাইটগুলি আয়নাতে কালো হবে।
এখন, আসুন স্যাম্পললাইটগুলি () সন্ধান করুন:
float3 SampleLights(UniformSampler *sampler, SurfaceInteraction interaction, BSDF *bsdf, Light *hitLight) const {
std::size_t numLights = m_scene->NumLights();
float3 L(0.0f);
for (uint i = 0; i < numLights; ++i) {
Light *light = &m_scene->Lights[i];
// Don't let a light contribute light to itself
if (light == hitLight) {
continue;
}
L = L + EstimateDirect(light, sampler, interaction, bsdf);
}
return L;
}
ইংরেজীতে:
- সমস্ত লাইট মাধ্যমে লুপ
- আমরা যদি এটি আঘাত করি তবে আলোটি এড়িয়ে যান
- সমস্ত আলো থেকে সরাসরি আলো একত্রিত করুন
- সরাসরি আলো ফিরিয়ে দিন
পরিশেষে, () কেবল ) মূল্যায়নবি এসডি এফ( পি , ω)আমি, ωণ) এলআমি( পি , ω)আমি)
নিয়মিত আলোর উত্সগুলির জন্য, এটি এত সহজ:
float3 EstimateDirect(Light *light, UniformSampler *sampler, SurfaceInteraction &interaction, BSDF *bsdf) const {
// Only sample if the BRDF is non-specular
if ((bsdf->SupportedLobes & ~BSDFLobe::Specular) != 0) {
return float3(0.0f);
}
interaction.InputDirection = normalize(light->Origin - interaction.Position);
return bsdf->Eval(interaction) * light->Li;
}
তবে, আমরা যদি আলোর ক্ষেত্রফল পেতে চাই তবে আমাদের প্রথমে আলোর একটি বিন্দুর নমুনা নেওয়া দরকার। সুতরাং, সম্পূর্ণ সংজ্ঞাটি হ'ল:
float3 EstimateDirect(Light *light, UniformSampler *sampler, SurfaceInteraction &interaction, BSDF *bsdf) const {
float3 directLighting = float3(0.0f);
// Only sample if the BRDF is non-specular
if ((bsdf->SupportedLobes & ~BSDFLobe::Specular) != 0) {
float pdf;
float3 Li = light->SampleLi(sampler, m_scene, interaction, &pdf);
// Make sure the pdf isn't zero and the radiance isn't black
if (pdf != 0.0f && !all(Li)) {
directLighting += bsdf->Eval(interaction) * Li / pdf;
}
}
return directLighting;
}
আমরা হালকা-> নমুনা প্রয়োগ করতে পারি তবে আমরা চাই; আমরা বিন্দুটি অভিন্ন বা গুরুত্বের নমুনাটি চয়ন করতে পারি। উভয় ক্ষেত্রেই, আমরা বিন্দুটি বেছে নেওয়ার পিডিএফ দ্বারা রেডিজিটিটি ভাগ করি। আবার মন্টি কার্লোর প্রয়োজনীয়তা পূরণ করতে fy
বিআরডিএফ যদি সর্বোচ্চ নির্ভরশীল হয় তবে আলোর উপর এলোমেলো পয়েন্টের পরিবর্তে বিআরডিএফ-এর ভিত্তিতে একটি পয়েন্ট বেছে নেওয়া আরও ভাল। তবে আমরা কীভাবে নির্বাচন করব? নমুনা আলোর উপর ভিত্তি করে, বা বিআরডিএফ ভিত্তিক?
কেন না উভয়? একাধিক গুরুত্ব স্যাম্পলিং প্রবেশ করান। সংক্ষেপে, আমরা একাধিকবার, বিভিন্ন নমুনা কৌশল ব্যবহার করে, তারপরে পিডিএফ-এর উপর ভিত্তি করে ওজন ব্যবহার করে তাদের একসাথে গড় করুন। কোডটি হ'ল:বি এসডি এফ( পি , ω)আমি, ωণ) এলআমি( পি , ω)আমি)
float3 EstimateDirect(Light *light, UniformSampler *sampler, SurfaceInteraction &interaction, BSDF *bsdf) const {
float3 directLighting = float3(0.0f);
float3 f;
float lightPdf, scatteringPdf;
// Sample lighting with multiple importance sampling
// Only sample if the BRDF is non-specular
if ((bsdf->SupportedLobes & ~BSDFLobe::Specular) != 0) {
float3 Li = light->SampleLi(sampler, m_scene, interaction, &lightPdf);
// Make sure the pdf isn't zero and the radiance isn't black
if (lightPdf != 0.0f && !all(Li)) {
// Calculate the brdf value
f = bsdf->Eval(interaction);
scatteringPdf = bsdf->Pdf(interaction);
if (scatteringPdf != 0.0f && !all(f)) {
float weight = PowerHeuristic(1, lightPdf, 1, scatteringPdf);
directLighting += f * Li * weight / lightPdf;
}
}
}
// Sample brdf with multiple importance sampling
bsdf->Sample(interaction, sampler);
f = bsdf->Eval(interaction);
scatteringPdf = bsdf->Pdf(interaction);
if (scatteringPdf != 0.0f && !all(f)) {
lightPdf = light->PdfLi(m_scene, interaction);
if (lightPdf == 0.0f) {
// We didn't hit anything, so ignore the brdf sample
return directLighting;
}
float weight = PowerHeuristic(1, scatteringPdf, 1, lightPdf);
float3 Li = light->Le();
directLighting += f * Li * weight / scatteringPdf;
}
return directLighting;
}
ইংরেজীতে:
- প্রথমত, আমরা আলোর নমুনা করি
- এটি ইন্টারঅ্যাকশন আপডেট করে I
- আলোর জন্য আমাদের লি দেয়
- এবং আলোর উপর পয়েন্ট চয়ন করার পিডিএফ
- দেখুন যে পিডিএফটি বৈধ এবং তেজস্বী শূন্য নয় is
- নমুনাযুক্ত ইনপুট দিকনির্দেশ ব্যবহার করে BSDF মূল্যায়ন করুন
- নমুনাযুক্ত ইনপুট দিকনির্দেশ দেওয়া বিএসডিএফের জন্য পিডিএফ গণনা করুন
- মূলত, এই নমুনাটি কতটা সম্ভব, যদি আমরা আলোর পরিবর্তে বিএসডিএফ ব্যবহার করে নমুনা বোধ করি
- লাইট পিডিএফ এবং বিএসডিএফ পিডিএফ ব্যবহার করে ওজন গণনা করুন
- ভিচ এবং গুইবাস ওজন গণনা করার জন্য কয়েকটি পৃথক পদ্ধতি নির্ধারণ করে। পরীক্ষামূলকভাবে, তারা বেশিরভাগ ক্ষেত্রে সর্বাধিক ক্ষেত্রে সবচেয়ে ভাল কাজ করার জন্য 2 এর শক্তি দিয়ে পাওয়ার হিউরিস্টিককে খুঁজে পেয়েছিল। আমি আপনাকে আরও বিশদ জন্য কাগজ রেফারেন্স। বাস্তবায়ন নীচে
- সরাসরি আলোক গণনার সাথে ওজনকে গুণিত করুন এবং হালকা পিডিএফ দ্বারা ভাগ করুন। (মন্টি কার্লোর জন্য) এবং সরাসরি আলো জমে যুক্ত করুন।
- তারপরে, আমরা বিআরডিএফ নমুনা করি
- এটি ইন্টারঅ্যাকশন আপডেট করে I
- বিআরডিএফকে মূল্যায়ন করুন
- বিআরডিএফের উপর ভিত্তি করে এই দিকটি বেছে নেওয়ার জন্য পিডিএফ পান
- স্যাম্পলড ইনপুট দিকনির্দেশ দিয়ে হালকা পিডিএফ গণনা করুন
- এটিই আগের আয়না। এই দিকটি কতটা সম্ভব, যদি আমরা আলোকে নমুনা দিতাম
- যদি আলোকপিডিএফ == 0.0f হয়, তবে রশ্মি আলোটি মিস করেছে, তাই কেবলমাত্র আলোকের নমুনা থেকে সরাসরি আলো ফিরিয়ে দিন।
- অন্যথায়, ওজন গণনা করুন, এবং জমে BSDF সরাসরি আলো যোগ করুন
- শেষ অবধি, জমে থাকা সরাসরি আলো ফিরিয়ে দিন
।
inline float PowerHeuristic(uint numf, float fPdf, uint numg, float gPdf) {
float f = numf * fPdf;
float g = numg * gPdf;
return (f * f) / (f * f + g * g);
}
এই ক্রিয়াকলাপগুলিতে আপনি করতে পারেন এমন অনেকগুলি অপ্টিমাইজেশন / উন্নতি রয়েছে, তবে সেগুলি বোঝার সহজ করার জন্য আমি তাদের এড়িয়ে গেছি। আপনি যদি চান, আমি এই উন্নতি কিছু ভাগ করতে পারেন।
স্যাম্পলিং ওয়ান লাইট
স্যাম্পললাইটগুলিতে () আমরা সমস্ত লাইট লুপ করি এবং তাদের অবদান পাই। অল্প সংখ্যক লাইটের জন্য, এটি ঠিক আছে, তবে কয়েকশ বা হাজার হাজার আলোতে এটি ব্যয়বহুল হয়। সৌভাগ্যক্রমে, আমরা মন্টি কার্লো ইন্টিগ্রেশন একটি দৈত্য গড়ের বিষয়টি কাজে লাগাতে পারি। উদাহরণ:
আসুন সংজ্ঞা দিন
h ( x ) = f( এক্স ) + জি( এক্স )
বর্তমানে, আমরা দ্বারা অনুমান করছি :এইচ ( এক্স )
h(x)=1N∑i=1Nf(xi)+g(xi)
তবে, এবং উভয়ই গণনা ব্যয়বহুল, সুতরাং পরিবর্তে আমরা এটি করি:f(x)g(x)
h(x)=1N∑i=1Nr(ζ,x)pdf
যেখানে একটি অভিন্ন র্যান্ডম ভেরিয়েবল এবং হিসাবে :ζr(ζ,x)
r(ζ,x)={f(x),g(x),0.0≤ζ<0.50.5≤ζ<1.0
এই ক্ষেত্রে কারণ পিডিএফ 1 সংহত করতে হবে, এবং সেখান থেকে পছন্দ করে নিন 2 ফাংশন আছে।pdf=12
ইংরেজীতে:
- মূল্যায়নের জন্য এলোমেলোভাবে বা বেছে নিন ।g ( x )f(x)g(x)
- ফলাফলটি দ্বারা ভাগ করুন (যেহেতু দুটি আইটেম রয়েছে)12
- গড়
এন বড় হওয়ার সাথে সাথে অনুমানটি সঠিক সমাধানে রূপান্তরিত হবে।
আমরা হালকা স্যাম্পলিংয়ে একই নীতিটি প্রয়োগ করতে পারি। প্রতিটি আলোকে নমুনা দেওয়ার পরিবর্তে, আমরা এলোমেলোভাবে একটি বাছাই করি এবং লাইটের সংখ্যা দ্বারা ফলাফলকে গুণ করি (এটি ভগ্নাংশ পিডিএফ দ্বারা বিভাজন হিসাবে সমান):
float3 SampleOneLight(UniformSampler *sampler, SurfaceInteraction interaction, BSDF *bsdf, Light *hitLight) const {
std::size_t numLights = m_scene->NumLights();
// Return black if there are no lights
// And don't let a light contribute light to itself
// Aka, if we hit a light
// This is the special case where there is only 1 light
if (numLights == 0 || numLights == 1 && hitLight != nullptr) {
return float3(0.0f);
}
// Don't let a light contribute light to itself
// Choose another one
Light *light;
do {
light = m_scene->RandomOneLight(sampler);
} while (light == hitLight);
return numLights * EstimateDirect(light, sampler, interaction, bsdf);
}
এই কোডটিতে, সমস্ত লাইটের বাছাইয়ের সমান সুযোগ রয়েছে। তবে, আমরা চাইলে নমুনাকে গুরুত্ব দিতে পারি। উদাহরণস্বরূপ, আমরা বড় লাইটগুলি বাছাই করার একটি উচ্চতর সুযোগ বা হিট পৃষ্ঠের কাছাকাছি আলো দিতে পারি। আপনি শুধু PDF, যা আর হবে দ্বারা ফলাফলের ভাগ আছে।1numLights
একাধিক তাত্পর্য "নতুন রে" দিকনির্দেশনা নমুনা
বর্তমান কোডটি কেবল BSDF এর উপর ভিত্তি করে "নতুন রে" দিকের নমুনাকে গুরুত্ব দেয়। আমরা যদি আলোর অবস্থানের ভিত্তিতে নমুনাকেও গুরুত্ব দিতে চাই?
আমরা উপরে যা শিখেছি তা গ্রহণ করে একটি পদ্ধতি হ'ল দুটি "নতুন" রশ্মি অঙ্কন করা এবং তাদের পিডিএফ-এর উপর ভিত্তি করে প্রতিটি ওজন করা। তবে এটি উভয়ই গণনামূলকভাবে ব্যয়বহুল এবং পুনরাবৃত্তি ছাড়াই কার্যকর করা শক্ত।
এটি কাটিয়ে উঠতে, আমরা কেবলমাত্র একটি আলো স্যাম্পলিং করে শিখেছি একই নীতিগুলি প্রয়োগ করতে পারি। এটি, এলোমেলোভাবে নমুনার জন্য একটি চয়ন করুন, এবং এটি বেছে নেওয়ার পিডিএফ দ্বারা ভাগ করুন।
// Get the new ray direction
// Randomly (uniform) choose whether to sample based on the BSDF or the Lights
float p = sampler->NextFloat();
Light *light = m_scene->RandomLight();
if (p < 0.5f) {
// Choose the direction based on the bsdf
material->bsdf->Sample(interaction, sampler);
float bsdfPdf = material->bsdf->Pdf(interaction);
float lightPdf = light->PdfLi(m_scene, interaction);
float weight = PowerHeuristic(1, bsdfPdf, 1, lightPdf);
// Accumulate the throughput
throughput = throughput * weight * material->bsdf->Eval(interaction) / bsdfPdf;
} else {
// Choose the direction based on a light
float lightPdf;
light->SampleLi(sampler, m_scene, interaction, &lightPdf);
float bsdfPdf = material->bsdf->Pdf(interaction);
float weight = PowerHeuristic(1, lightPdf, 1, bsdfPdf);
// Accumulate the throughput
throughput = throughput * weight * material->bsdf->Eval(interaction) / lightPdf;
}
এগুলি সবই বলেছিল, আমরা কি আলোর উপর ভিত্তি করে "নিউ রে" দিকের নমুনাকে গুরুত্ব দিতে চাই ? জন্য সরাসরি আলো, radiosity উভয় পৃষ্ঠের BSDF, এবং হালকা দিক দ্বারা প্রভাবিত হয়। কিন্তু পরোক্ষ আলোকসজ্জার জন্য, রেডিয়োসিটিটি প্রায় একচেটিয়াভাবে পূর্বের পৃষ্ঠের হিট বিএসডিএফ দ্বারা সংজ্ঞায়িত করা হয়। সুতরাং, হালকা গুরুত্বের নমুনা যুক্ত করা আমাদের কিছু দেয় না।
সুতরাং, বিএসডিএফের সাথে "নতুন দিকনির্দেশ" কেবলমাত্র নমুনাকে গুরুত্ব দেওয়া সাধারণ তবে সরাসরি আলোকে একাধিক গুরুত্ব স্যাম্পলিং প্রয়োগ করুন।