Typically, it is best to avoid doing any manual light integration and leave it up to Arnold, to take advantage of an optimized implementation that can continue improving without changes to shaders and BSDFs, and to support light path expressions. However, a lower level direct light sampling API is also provided.

The BSDF API can be used to implement a wide range of BSDFs, which can then be output as a closure or integrated using AiBSDFIntegrate.

#include <ai.h>
 
AI_SHADER_NODE_EXPORT_METHODS(DiffuseMtd)
 
enum DiffuseParams {
   p_color,
};
 
node_parameters
{
   AiParameterRGB("color", 0.8f, 0.8f, 0.8f);
}
 
node_initialize
{
   // create 3D sampler for BSDF sampling
   const int seed = 3158863998;
   const int nsamples = 4;
   const int ndim = 3;
   AtSampler *sampler = AiSampler(seed, nsamples, ndim);
   AiNodeSetLocalData(node, sampler);
}
 
node_update
{
}
 
node_finish
{
   AtSampler *sampler = (AtSampler*)AiNodeGetLocalData(node);
   AiSamplerDestroy(sampler);
}
 
static float PowerHeuristic(float pdf_a, float pdf_b)
{
   return AiSqr(pdf_a) / (AiSqr(pdf_a) + AiSqr(pdf_b));
}
 
static AtRGB DirectLightMIS(AtShaderGlobals *sg, AtSampler *sampler,
                            AtBSDF *bsdf)
{
   const AtRGB weight = AiBSDFGetWeight(bsdf);
   const AtBSDFMethods *methods = AiBSDFGetMethods(bsdf);
   AtRGB result = AI_RGB_BLACK;
 
   // initialize BSDF
   methods->Init(sg, bsdf);
 
   // prepare light integration
   AiLightsPrepare(sg);
 
   // integrate each BSDF lobe separately
   const int num_lobes = AiBSDFGetNumLobes(bsdf);
   AtBSDFLobeSample *lobe_sample = new AtBSDFLobeSample[num_lobes];
 
   for (int lobe = 0; lobe < num_lobes; lobe++)
   {
      const AtBSDFLobeMask lobe_mask = 1 << lobe;
      uint16_t ray_type = AiBSDFGetLobes(bsdf)[lobe].ray_type;
      float nsamples_bsdf = 0;
 
      // integrate MIS BSDF samples, if there are lights that can benefit from them
      if (AiLightsTraceRayTypes(sg) & ray_type)
      {
         AtSamplerIterator *iterator = AiSamplerIterator(sampler, sg);
         nsamples_bsdf = AiSamplerGetSampleCount(iterator);
         AtVector rnd;
 
         while (AiSamplerGetSample(iterator, &rnd.x))
         {
            AtVectorDv wi;
            int lobe_index;
 
            if (methods->Sample(bsdf, rnd, 0.0f, lobe_mask, true, wi, lobe_index, lobe_sample))
            {
               AtRGB bsdf_weight = weight * lobe_sample[0].weight;
               float bsdf_pdf = lobe_sample[0].pdf * nsamples_bsdf;
 
               AtLightSample *hits;
               int num_hits = AiLightsTrace(sg, wi.val, ray_type, hits);
 
               for (int i = 0; i < num_hits; i++)
               {
                  AtLightSample& light_sample = hits[i];
 
                  float light_influence = AiLightGetInfluence(sg, light_sample.Lp, ray_type);
                  if (light_influence == 0.0f)
                     continue;
 
                  float mis_weight = PowerHeuristic(bsdf_pdf, light_sample.pdf);
                  result += (light_influence * mis_weight) * light_sample.Li
                            * bsdf_weight / nsamples_bsdf;
               }
            }
         }
      }
 
      // integrate MIS light samples
      AtLightSample light_sample;
      while (AiLightsGetSample(sg, light_sample))
      {
         float light_influence = AiLightGetInfluence(sg, light_sample.Lp, ray_type);
         if (light_influence == 0.0f)
            continue;
 
         if (light_sample.trace_ray_types & ray_type)
         {
            // Use MIS
            methods->Eval(bsdf, light_sample.Ld, lobe_mask, true, lobe_sample);
            AtRGB bsdf_weight = weight * lobe_sample[0].weight * lobe_sample[0].pdf;
            float bsdf_pdf = lobe_sample[0].pdf * nsamples_bsdf;
            float mis_weight = PowerHeuristic(light_sample.pdf, bsdf_pdf);
            result += (light_influence * mis_weight) * bsdf_weight
                      * light_sample.Li / light_sample.pdf;
         }
         else
         {
            // No MIS
            methods->Eval(bsdf, light_sample.Ld, lobe_mask, false, lobe_sample);
            AtRGB bsdf_weight = weight * lobe_sample[0].weight * lobe_sample[0].pdf;
            result += light_influence * bsdf_weight
                      * light_sample.Li / light_sample.pdf;
         }
      }
   }
   delete[] lobe_sample;
   return result;
}
 
shader_evaluate
{
   // early out for shadow rays and black
   if (sg->Rt & AI_RAY_SHADOW)
      return;
 
   AtRGB weight = AiShaderEvalParamRGB(p_color);
   if (AiColorIsSmall(weight))
      return;
 
   // create and integrate BSDF
   AtBSDF *bsdf = AiOrenNayarBSDF(sg, weight, sg->Nf);
   AtSampler *sampler = (AtSampler*)AiNodeGetLocalData(node);
   sg->out.RGB() = DirectLightMIS(sg, sampler, bsdf);
}
 
node_loader
{
   if (i>0)
      return false;
 
   node->methods      = DiffuseMtd;
   node->output_type  = AI_TYPE_RGB;
   node->name         = "diffuse";
   node->node_type    = AI_NODE_SHADER;
   strcpy(node->version, AI_VERSION);
   return true;
}
  • No labels