9.2 Diffuse Reflection

One of the simplest BRDFs is the Lambertian model, which describes a perfect diffuse surface that scatters incident illumination equally in all directions. It is a reasonable approximation to many real-world surfaces such as paper or matte paint. The Lambertian model captures the behavior of such diffuse materials relatively well, though the approximation tends to perform worse for light arriving at a grazing angle, where specular reflection causes a noticeable deviation from uniformity. (Microfacet models such as those presented in Section 9.6 can account for such effects.)

It is interesting to note that surfaces created from polytetrafluoroethylene (PTFE) powder are known to be particularly good Lambertian reflectors. They are commonly used to calibrate laboratory equipment for this reason.

<<DiffuseBxDF Definition>>= 
class DiffuseBxDF { public: <<DiffuseBxDF Public Methods>> 
DiffuseBxDF(SampledSpectrum R) : R(R) {} SampledSpectrum f(Vector3f wo, Vector3f wi, TransportMode mode) const { if (!SameHemisphere(wo, wi)) return SampledSpectrum(0.f); return R * InvPi; } pstd::optional<BSDFSample> Sample_f( Vector3f wo, Float uc, Point2f u, TransportMode mode, BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const { if (!(sampleFlags & BxDFReflTransFlags::Reflection)) return {}; <<Sample cosine-weighted hemisphere to compute wi and pdf>> 
Vector3f wi = SampleCosineHemisphere(u); if (wo.z < 0) wi.z *= -1; Float pdf = CosineHemispherePDF(AbsCosTheta(wi));
return BSDFSample(R * InvPi, wi, pdf, BxDFFlags::DiffuseReflection); } Float PDF(Vector3f wo, Vector3f wi, TransportMode mode, BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const { if (!(sampleFlags & BxDFReflTransFlags::Reflection) || !SameHemisphere(wo, wi)) return 0; return CosineHemispherePDF(AbsCosTheta(wi)); } PBRT_CPU_GPU static constexpr const char *Name() { return "DiffuseBxDF"; } std::string ToString() const; PBRT_CPU_GPU void Regularize() {} PBRT_CPU_GPU BxDFFlags Flags() const { return R ? BxDFFlags::DiffuseReflection : BxDFFlags::Unset; }
private: SampledSpectrum R; };

The constructor takes a reflectance spectrum R with values in the range left-bracket 0 comma 1 right-bracket that specify the fraction of incident light that is scattered.

<<DiffuseBxDF Public Methods>>= 
DiffuseBxDF(SampledSpectrum R) : R(R) {}

The reflection distribution function is just a constant, though it requires a normalization factor equal to StartFraction 1 Over pi EndFraction so that the total integrated reflectance equals upper R .

integral Underscript script upper H squared left-parenthesis bold n Subscript Baseline right-parenthesis Endscripts f Subscript normal r Baseline left-parenthesis normal p Subscript Baseline comma omega Subscript normal o Baseline comma omega prime Subscript Baseline right-parenthesis cosine theta prime normal d omega Subscript Baseline Superscript prime Baseline equals integral Subscript 0 Superscript 2 pi Baseline integral Subscript 0 Superscript StartFraction pi Over 2 EndFraction Baseline StartFraction upper R Over pi EndFraction cosine theta Superscript prime Baseline sine theta prime normal d theta prime normal d phi Superscript prime Baseline equals upper R period

With this correction, the f() implementation is given by

<<DiffuseBxDF Public Methods>>+=  
SampledSpectrum f(Vector3f wo, Vector3f wi, TransportMode mode) const { if (!SameHemisphere(wo, wi)) return SampledSpectrum(0.f); return R * InvPi; }

The sampling function returns an invalid sample if the caller specified that reflection components of the BSDF should not be sampled. Otherwise, it draws a direction from a suitable distribution and returns all the sample-related information via a BSDFSample instance.

<<DiffuseBxDF Public Methods>>+=  
pstd::optional<BSDFSample> Sample_f( Vector3f wo, Float uc, Point2f u, TransportMode mode, BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const { if (!(sampleFlags & BxDFReflTransFlags::Reflection)) return {}; <<Sample cosine-weighted hemisphere to compute wi and pdf>> 
Vector3f wi = SampleCosineHemisphere(u); if (wo.z < 0) wi.z *= -1; Float pdf = CosineHemispherePDF(AbsCosTheta(wi));
return BSDFSample(R * InvPi, wi, pdf, BxDFFlags::DiffuseReflection); }

Working in a canonical reflection coordinate system greatly simplifies the central sampling step: in particular, a direction generated by SampleCosineHemisphere() can be directly used, and we must only pay attention that wo and wi lie in the same hemisphere, as indicated by wo.z and wi.z.

Although the Lambertian BRDF is uniform over the hemisphere, BSDFs are sampled in the context of the light transport equation, (1.1), where the BSDF is multiplied by the incident radiance and a cosine factor. It is worthwhile for BxDFs to include the cosine factor in their sampling distribution if possible; see Figure 9.4, which compares uniform and cosine-weighted hemisphere sampling for the DiffuseBxDF.

Figure 9.4: Comparison of Sampling Methods for a Lambertian BSDF. Both images are rendered using 4 samples per pixel. (a) Uniform hemisphere sampling. (b) Cosine-weighted hemisphere sampling. By incorporating the cosine factor in the light transport equation’s integrand, cosine-weighted hemisphere sampling improves mean squared error (MSE) by a factor of 2.34 for this test scene, without additional computational cost.

<<Sample cosine-weighted hemisphere to compute wi and pdf>>= 
Vector3f wi = SampleCosineHemisphere(u); if (wo.z < 0) wi.z *= -1; Float pdf = CosineHemispherePDF(AbsCosTheta(wi));

The PDF() method just needs to ensure that the caller has included reflection in the types of scattering that it is interested in and that the two directions both lie in the same hemisphere.

<<DiffuseBxDF Public Methods>>+= 
Float PDF(Vector3f wo, Vector3f wi, TransportMode mode, BxDFReflTransFlags sampleFlags = BxDFReflTransFlags::All) const { if (!(sampleFlags & BxDFReflTransFlags::Reflection) || !SameHemisphere(wo, wi)) return 0; return CosineHemispherePDF(AbsCosTheta(wi)); }