Light Loops - Arnold Developer Guide
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
lightloop.cpp
#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
if (AI_BSDF_LOBE_MASK_NONE == methods->Eval(bsdf, light_sample.Ld, lobe_mask, false, lobe_sample))
continue; // Skip accumulation of samples that didn't fall within the domains of any lobe
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;
}
And here's an example scene and render
options
{
AA_samples 9
outputs "RGBA RGBA /out/arnold1:gaussian_filter /out/arnold1:jpeg"
camera "camera"
GI_diffuse_depth 1
GI_specular_depth 1
GI_diffuse_samples 3
}
driver_jpeg
{
name /out/arnold1:jpeg
filename "simple.jpg"
}
gaussian_filter
{
name /out/arnold1:gaussian_filter
}
persp_camera
{
name camera
position 0 30 30
look_at 0 10 0
}
sphere
{
name pSphereShape2
center 0 10 0
radius 10
shader "ballShader"
}
plane
{
name myplane
normal 0 1 0
point 0 0 0
shader "groundShader"
}
diffuse
{
name ballShader
color 0.5 0.8 0.8
}
diffuse
{
name groundShader
color 1 1 1
}
skydome_light
{
name sky
camera 0
}
