2
$\begingroup$

I'm developing a webgpu pathtracer using multiple importance sampling with two similiar techniques, next event estimation and one-sample model. For some reason, next event estimation seems to gather less energy compared with one-sample-model.

There are currently two types of surfaces, diffuse and emissive, MIS is only applied to diffuse surfaces and emissive surfaces are exclusively operating with brdf sampling.

Next event estimation uses the brdf to sample the next direction, but it also accumulates direct light by picking a light source and sampling it directly, whereas the one-sample model randomly decides wether the next direction will be picked by sampling the brdf or the light source.

The scene in the screenshot has a ground plane and a single area light perpendicular to the plane, it's not easy to spot at first glance but the area computed with OSM is slightly brighter compared to NEE

enter image description here

I suspect that the reason why NEE gets less energy is because the light sample picks the radiance from the light source and it "stops" there, but if you let the light sample bounce again into the scene, it might get to the ground and bounce again back into the light source, thus contributing more to the final energy gathered at that pixel. This could be confirmed by the fact that limiting the bounces of the path tracer to 2 doesn't show differences between the two methods, whereas increasing the bounce count does show a difference. (however if this is the answer to the problem, then how can I get OSM results with NEE without recursion?)

Pseudocode for brevity:

fn shadeDiffuse(...) {
  let hitPoint = ...
  let material = ...
  let color = material.color;
  var N = ...hit point normal...

  (*ray).origin = hitPoint;

  var brdf = 1 / PI;

  if (MIS_TYPE == ONE_SAMPLE_MODEL) {
    var pdf; var misWeight;
    if (randomNumber < 0.5) {
      shadeDiffuseSampleBRDF(N, ray, &pdf, &misWeight);
    } else {
      shadeDiffuseSampleLight(N, ray, &pdf, &misWeight);
    }
    *reflectance *= brdf * (misWeight / pdf) * color * max(dot(N, (*ray).direction), 0.0);
  }

  if (MIS_TYPE == NEXT_EVENT_ESTIMATION) {
    var brdfSamplePdf; var brdfMisWeight; 
    var lightSamplePdf; var lightMisWeight; var lightSample;
    var rayBrdf = copy of *ray
    var rayLight = copy of *ray

    shadeDiffuseSampleBRDF(N, &rayBrdf, &brdfSamplePdf, &brdfMisWeight);
    shadeDiffuseSampleLight(N, &rayLight, &lightSamplePdf, &lightMisWeight, &lightSample);

    (*ray).origin = rayBrdf.origin;
    (*ray).direction = rayBrdf.direction;

    *reflectance *= brdf * color;
    // light contribution
    *rad += lightSample * (lightMisWeight / lightSamplePdf) * (*reflectance) * max(dot(N, rayLight.direction), 0.0);
    *reflectance *= (brdfMisWeight / brdfSamplePdf) * max(dot(N, rayBrdf.direction), 0.0);
  }
} 

fn shadeDiffuseSampleBRDF(...) {
  var brdfSamplePdf = 1 / (2 * PI);
  ...
  // get new ray direction
  (*ray).direction = normalize(Nt * nd.x + N * nd.y + Nb * nd.z);

  if (MIS_TYPE == BRDF_ONLY) {
    *pdf = brdfSamplePdf;
  } 

  if (MIS_TYPE == ONE_SAMPLE_MODEL || MIS_TYPE == NEXT_EVENT_ESTIMATION) {
    let hitPoint = ...intersect scene with ray...
    let materialType = ...
    let hitPointNormal = ...
    let triangleArea = ...
    var lightSamplePdf = 0.0;
    if (materialType == EMISSIVE) {
      let lD = (*ray).direction;
      let r2 = squaredLength(hitPoint - (*ray).origin);
      var lN = hitPointNormal;
      var lNolD = ...
      lightSamplePdf = r2 / (lNolD * triangleArea);
    }

    if (MIS_TYPE == ONE_SAMPLE_MODEL) {
      *pdf = brdfSamplePdf;
      *misWeight = brdfSamplePdf / ((brdfSamplePdf + lightSamplePdf) * 0.5);
    }

    if (MIS_TYPE == NEXT_EVENT_ESTIMATION) {
      *pdf = brdfSamplePdf;
      *misWeight = brdfSamplePdf / (brdfSamplePdf + lightSamplePdf);
    }
  }
}

fn shadeDiffuseSampleLight(...) {
  let samplePoint = ...sample a light source...
  let normal = ...
  let triangleArea = ...

  let lD = normalize(samplePoint - (*ray).origin);
  (*ray).direction = lD;

  let r2 = squaredLength(samplePoint - (*ray).origin);
  var lN = normal;
  var lNolD = ...
  var backSideHit = ...
  var lightSamplePdf = r2 / (lNolD * triangleArea);
  var brdfSamplePdf = 1 / (2 * PI);

  if (MIS_TYPE == ONE_SAMPLE_MODEL) {
    *pdf = (lightSamplePdf * probabilityOfPickingThisLight);
    *misWeight = *pdf / ((brdfSamplePdf + *pdf) * 0.5);

    if (backSideHit) {
      *misWeight = 0.0;
    }
  }

  if (MIS_TYPE == NEXT_EVENT_ESTIMATION) {
    *pdf = (lightSamplePdf * probabilityOfPickingThisLight);
    *misWeight = *pdf / (brdfSamplePdf + *pdf);

    let materialType = ...intersect scene with ray and get material type of the intersection...
    if (materialType == EMISSIVE && !backSideHit) {
      *lightSample = material.color * material.intensity;
    } else {
      *misWeight = 0;
    }
  }
}
$\endgroup$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.