Afin de comprendre la roulette russe, regardons un traceur de chemin vers l'arrière très basique:
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);
}
C'EST À DIRE. nous rebondissons autour de la scène, accumulant des atténuations de couleur et de lumière au fur et à mesure. Pour être complètement mathématiquement impartiaux, les rebonds doivent aller à l'infini. Mais ce n'est pas réaliste et, comme vous l'avez noté, pas visuellement nécessaire; pour la plupart des scènes, après un certain nombre de rebonds, disons 10, le montant de la contribution à la couleur finale est très très minime.
Ainsi, afin d'économiser les ressources informatiques, de nombreux traceurs de chemin ont une limite stricte au nombre de rebonds. Cela ajoute du parti pris.
Cela dit, il est difficile de choisir quelle devrait être cette limite. Certaines scènes ont fière allure après 2 rebonds; d'autres (disons avec transmission ou SSS) peuvent prendre jusqu'à 10 ou 20.
Si nous choisissons trop bas, l'image sera visiblement biaisée. Mais si nous choisissons trop haut, nous perdons de l'énergie et du temps de calcul.
Une façon de résoudre ce problème, comme vous l'avez noté, est de terminer le chemin après avoir atteint un certain seuil d'atténuation. Cela ajoute également des biais.
Le serrage après un seuil fonctionnera , mais encore une fois, comment choisissons-nous le seuil? Si nous choisissons trop grand, l'image sera visiblement biaisée, trop petite et nous gaspillons des ressources.
La roulette russe tente de résoudre ces problèmes de manière impartiale. Tout d'abord, voici le code:
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;
// Russian Roulette
// Randomly terminate a path with a probability inversely equal to the throughput
float p = std::max(throughput.x, std::max(throughput.y, throughput.z));
if (sampler->NextFloat() > p) {
break;
}
// Add the energy we 'lose' by randomly terminating paths
throughput *= 1 / p;
// 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);
}
La roulette russe termine aléatoirement un chemin avec une probabilité inversement égale au débit. Ainsi, les chemins à faible débit qui ne contribueront pas beaucoup à la scène sont plus susceptibles d'être interrompus.
Si nous nous arrêtons là, nous sommes toujours biaisés. Nous «perdons» l'énergie du chemin que nous terminons au hasard. Pour le rendre non biaisé, nous augmentons l'énergie des chemins non terminés par leur probabilité d'être terminée. Ceci, en plus d'être aléatoire, rend la roulette russe impartiale.
Pour répondre à vos dernières questions:
- La roulette russe donne-t-elle un résultat impartial?
- La roulette russe est-elle nécessaire pour un résultat impartial?
- Cela dépend de ce que vous entendez par impartial. Si vous voulez dire mathématiquement, alors oui. Cependant, si vous voulez dire visuellement, alors non. Il vous suffit de choisir très soigneusement la profondeur de trajectoire maximale et le seuil de coupure. Cela peut être très fastidieux car il peut changer d'une scène à l'autre.
- Pouvez-vous utiliser une probabilité fixe (coupure), puis redistribuer l'énergie «perdue». Est-ce impartial?
- Si vous utilisez une probabilité fixe, vous ajoutez un biais. En redistribuant l'énergie «perdue», vous réduisez le biais, mais il est toujours biaisé mathématiquement. Pour être totalement impartial, il doit être aléatoire.
- Si l'énergie qui serait perdue en terminant un rayon sans redistribuer son énergie est finalement perdue de toute façon (comme les rayons auxquels elle est redistribuée sont également finalement arrêtés), comment cela améliore-t-il la situation?
- La roulette russe arrête seulement le rebond. Il ne supprime pas complètement l'échantillon. De plus, l'énergie «perdue» est comptabilisée dans les rebonds jusqu'à la terminaison. Donc, la seule façon pour que l'énergie soit «finalement perdue de toute façon» serait d'avoir une pièce complètement noire.
En fin de compte, la roulette russe est un algorithme très simple qui utilise une très petite quantité de ressources informatiques supplémentaires. En échange, il peut économiser une grande quantité de ressources de calcul. Par conséquent, je ne vois pas vraiment de raison de ne pas l'utiliser.
to be completely unbiased it must be random
. Je pense que vous pouvez toujours obtenir des résultats mathématiques corrects en utilisant le poids fractionné des échantillons, plutôt que le passage / baisse binaire que la roulette russe impose, c'est juste que la roulette convergera plus rapidement car elle opère un échantillonnage d'importance parfaite.La technique de la roulette russe elle-même est un moyen de terminer les chemins sans introduire de biais systémique. Le principe est assez simple: si à un sommet particulier vous avez 10% de chances de remplacer arbitrairement l'énergie par 0, et si vous le faites un nombre infini de fois, vous verrez 10% d'énergie en moins. L'augmentation d'énergie compense simplement cela. Si vous n'avez pas compensé l'énergie perdue en raison de la terminaison du chemin, la roulette russe serait biaisée, mais toute la technique est une méthode utile pour éviter les biais.
Si j'étais un adversaire cherchant à prouver que la technique des "terminaisons de chemins dont la contribution est inférieure à une petite valeur fixe" est biaisée, je construirais une scène avec des lumières si faibles que les chemins contributifs sont toujours inférieurs à cette valeur. Peut-être que je simule un appareil photo à faible luminosité.
Mais bien sûr, vous pouvez toujours exposer la valeur fixe en tant que paramètre modifiable à l'utilisateur, afin qu'il puisse la réduire encore plus si sa scène se trouve être en basse lumière. Ignorons donc cet exemple pendant une minute.
Que se passe-t-il si je considère un objet qui est éclairé par un grand nombre de chemins à très basse énergie qui sont collectés par un réflecteur parabolique ? Les chemins à faible énergie ne rebondissent pas nécessairement sans discernement d'une manière que vous pouvez complètement négliger. De même, le raisonnement s'applique, par exemple, pour couper des chemins après un nombre fixe de rebonds: vous pouvez construire une scène avec un chemin qui rebondit sur une série de 20 miroirs avant de toucher un objet.
Une autre façon de voir les choses: si vous définissez la contribution d'un chemin à 0 après qu'il tombe en dessous d'un epsilon fixe, comment corrigez-vous cette perte d'énergie? Vous ne réduisez pas simplement l'énergie totale d'une fraction. Vous ne savez rien sur la quantité d'énergie que vous négligez, car vous coupez à un certain seuil de contribution avant de connaître l'autre facteur: l'énergie incidente.
la source
Juste pour développer certaines des autres réponses, la preuve que la roulette russe ne donne pas un résultat biaisé est très simple.
Supposons que vous ayez une variable aléatoireF qui est la somme de plusieurs termes:
Remplacez chaque terme par:
Alors:
Notez que peu importe les probabilités que vous choisissezpje . La valeur attendue des termes, et donc la valeur attendue deF , est le même.
la source