### Surface Integrators

### Digital Image Synthesis g g y *Yung-Yu Chuang*

*with slides by Peter Shirley, Pat Hanrahan, Henrik Jensen, Mario Costa Sousa and Torsten Moller *

**Direct lighting via Monte Carlo integration**

*diff*
*diffuse *

**Direct lighting via Monte Carlo integration**

*parameterization over hemisphere *

*parameterization over surface*

*have to add visibility*
*have to add visibility*

**Direct lighting via Monte Carlo integration**

*take one sample according to a density function*

*let’s take*
*let s take*

**Direct lighting via Monte Carlo integration**

### 1 sample/pixel

### 100 samples/pixel *Lights’ sizes matter * *more than shapes.* *p* *Noisy because*

*•x’ could be on the * *b k*

*back*

*•cos varies*

**Noise reduction**

*choose better density function*

) 1

(

###

###

*It is equivalent to uniformly sampling over*
*the cone cap in the last lecture.*

max 1

1) cos

1 (

cos

###

###

### 2

2###

_{cos}

###

_{max}

**Direct lighting from many luminaries**

### • Given a pair , use it to select light and generate new pair for sampling that light generate new pair for sampling that light.

### • α could be constant for proportional to power

**Rendering**

**• Rendering is handled by Renderer class.**

**class Renderer {**
**class Renderer {**

**… **

**virtual void Render(Scene *scene) = 0;**

given a scene, render an image or a set of measurements

**virtual Spectrum Li(Scene *scn, RayDifferential &r,**
**Sample *sample, RNG &rng, **

computer radiance along a ray

for MC sampling **p** **p** **,** **g,**

**MemoryArena &arena, Intersection *isect, **
**Spectrum *T) const = 0;**

t t itt l

p g transmittance

**virtual Spectrum Transmittance(Scene *scene,**
**RayDifferential &ray, Sample *sample,**
return transmittance along a ray

**y** **y** **p** **p**

**RNG &rng, MemoryArena &arena) const = 0;**

**};** The later two are usually relayed to Integrator

**SamplerRenderer**

**class SamplerRenderer : public Renderer {**

**…**

**private:**

**// SamplerRenderer Private Data** **// SamplerRenderer Private Data**

**Sampler *sampler;**

choose samples on image plane
and for integration
**Camera *camera;**

determine lens parameters (position,
orientation, focus, field of view)
with a film
**SurfaceIntegrator *surfaceIntegrator;**

**V l** **I t** **t** ***** **l** **I t** **t**

with a film

**VolumeIntegrator *volumeIntegrator;**

**};**

calculate the rendering equation
**The main rendering loop**

**• After scene and Renderer are constructed, ** **Renderer:Render()** is invoked

**Renderer:Render() ** is invoked.

**Renderer:Render()**

**void SamplerRenderer::Render(const Scene *scene) {**

**…** scene-dependent initialization such photon map
**surfaceIntegrator->Preprocess(scene,camera,this);**

**volumeIntegrator->Preprocess(scene,camera,this);**

scene dependent initialization such photon map

**Sample *sample = new Sample(sampler, **

**surfaceIntegrator** **volumeIntegrator** **scene);**

sample structure depends on types of integrators
**surfaceIntegrator, volumeIntegrator, scene);**

We want many tasks to fill in the core (see histogram next page).

If there are too few, some core will be idle. But, threads have
**int nPixels = camera->film->xResolution **

If there are too few, some core will be idle. But, threads have overheads. So, we do not want too many either.

*** camera->film->yResolution;**

**int nTasks = max(32 * NumSystemCores(), **
**nPixels / (16*16));**

at least 32 tasks
for a core
a task is about 16x16
**nPixels / (16*16));**

**nTasks = RoundUpPow2(nTasks);**

a task is about 16x16 power2 easier to divide

**Renderer:Render()**

**vector<Task *> renderTasks;**

**for (int i = 0; i < nTasks; ++i)(** **;** **;** **)**
**renderTasks.push_back(new **

**SamplerRendererTask(scene,this,camera,reporter, **
all information about renderer
must be passed in

**sampler, sample, nTasks-1-i, nTasks));**

**EnqueueTasks(renderTasks);**

total tasks task id

**EnqueueTasks(renderTasks);**

**WaitForAllTasks();**

**for (int i = 0; i < renderTasks.size(); ++i)(** **()** **)**
**delete renderTasks[i];**

**delete sample;**

**camera->film->WriteImage();**

**}**
**}**

**SamplerRenderTask::Run**

### • When the task system decided to run a task on a **particular processor, SamplerRenderTask::Run()** **particular processor, SamplerRenderTask::Run() ** will be called.

**void SamplerRendererTask::Run() {**

**// decided which part it is responsible for **

**…**

**int sampleCount;**

**while ((sampleCount=sampler** **>**

**while ((sampleCount=sampler -> **

**GetMoreSamples(samples, rng)) > 0) {**

**// Generate camera rays and compute radiance y** **p**

**SamplerRenderTask::Run**

**for (int i = 0; i < sampleCount; ++i) {**
for vignetting

**float rayWeight = camera-> **

**GenerateRayDifferential(samples[i], &rays[i]); **

for vignetting ray differential
for antialiasing
**rays[i].ScaleDifferentials(**

**1.f / sqrtf(sampler->samplesPerPixel));**

**if (rayWeight > 0.f)**

**Ls[i] = rayWeight * renderer->Li(scene, rays[i], [ ]** **y** **g** **(** **,** **y [ ],**

**&samples[i], rng, arena, &isects[i], &Ts[i]); **

**else { Ls[i] = 0.f; Ts[i] = 1.f; } **
**for (int i = 0; i < sampleCount; ++i)**

**camera->film->AddSample(samples[i]** **Ls[i]);**

**camera->film->AddSample(samples[i], Ls[i]);**

**}**

**SamplerRender::Li**

**Spectrum SamplerRender::Li(Scene *scene,**
**RayDifferential &ray, Sample *sample, y** **y,** **p** **p** **,**

**…, Intersection *isect, Spectrum *T)**
**{ Spectrum Li = 0.f;**

**if (scene->Intersect(ray, isect))**

**Li = surfaceIntegrator->Li(scene,this, **
**ray** ***isect** **sample rng** **arena);**

**ray, *isect, sample,rng, arena);**

**else { // ray that doesn't hit any geometry**
**for (i=0; i<scene->lights.size(); ++i)(** **g** **()** **)**

**Li += scene->lights[i]->Le(ray);**

**}**

**Spectrum Lvi = volumeIntegrator->Li(scene, this,**
**ray, sample, rng, T, arena);**

**return *T * Li + Lvi;**

**return *T * Li + Lvi;**

**}**

**Surface integrator’s Li**

### ) ω p, (

_{o}

*L*

*o*

*L*

_{e}### ( p , ω

_{o}

### )

### **L**

^{i}

_{o}### ω

2

*d*

###

*s*

### *f* ( p, ω

_{o}

### , ω

_{i}

### ) *L*

_{i}### ( p, ω

_{i}

### ) cos θ

_{i}

**SamplerRender::Li**

**L i** **L**

_{i}**Lvi**

**T**

**Integrators**

**• core/integrator.* integrator/***

**Class Integrator {**

**i t** **l** **id P** **(S** *****

**virtual void Preprocess(Scene *scene, ** **Camera *camera, Renderer *renderer){}**

**(**

**virtual void RequestSamples(Sampler **

***sampler, Sample *sample, Scene *scene){}**

**};**

**Integrators**

**• void Preprocess(…)**

*Called after scene has been initialized; do scene* *Called after scene has been initialized; do scene-* *dependent computation such as photon shooting for * *photon mapping.*

*p* *pp g*

**• void RequestSamples(…)**

*Sample is allocated once in Render(). There, sample’s * *p* *()* *p* *constructor will call integrator’s RequestSamples to * *allocate appropriate space.*

**Sample::Sample(Sampler *sampler, SurfaceIntegrator**

***surf, VolumeIntegrator *vol, Scene *scene) {**
**if (surf) (** **)**

**surf>RequestSamples(sampler,this,scene);**

**if (vol) **

**vol->RequestSamples(sampler, this, scene);**

**…**

**Surface integrators**

### • Responsible for evaluating the integral equation

### Whitted directlighting path irradiancecache Whitted, directlighting, path, irradiancecache, photonmap, igi, exphotonmap

**class SurfaceIntegrator:public Integrator {** **public:** We could call Renderer’s

### Li or Transmittance **virtual Spectrum Li(Scene *scene, Renderer **

***renderer, RayDifferential &ray, ** Li or Transmittance

**Intersection &isect, Sample *sample, ** **RNG &rng, MemoryArena &arena) const = 0;**

**};**

**Direct lighting**

*d* *L*

*f* *L*

*L* ( ) ( ) ( ) ( ) | |

### Rendering equation

*i*
*i*
*i*

*i*
*i*
*o*
*o*

*e*
*o*

*o*

*p* *L* *p* *f* *p* *L* *p* *d*

*L* ( , ) ( , )

### ( , , ) ( , ) | cos | If we only consider direct lighting, we can replace

*i*
*i*
*i*

*d*
*i*
*o*
*o*

*e*
*o*

*o*

*p* *L* *p* *f* *p* *L* *p* *d*

*L* ( , ) ( , )

### ( , , ) ( , ) | cos |

### y g g, p

*L*

_{i}*by L*

_{d}### .

*i*
*i*
*i*

*d*
*i*
*o*
*o*

*e*
*o*

*o*

###

### • simplest form of equation

### • somewhat easy to solve (but a gross approximation)

### • major contribution to the final radiance

### • not too bad since most energy comes from direct lights

### • kind of what we do in Whitted ray tracing

**Direct lighting**

### • Monte Carlo sampling to solve

###

### • Sampling strategy A: sample only one light

*i*
*i*
*i*

*d*
*i*

*o*

*L* *p* *d*

*p*

*f* ( , , ) ( , ) | cos |

###

### • Sampling strategy A: sample only one light

### – pick up one light as the representative for all lights distribute N samples over that light

### – distribute N samples over that light

*– Use multiple importance sampling for f and L*

_{d}*N*

*f* ( ) *L* ( ) | |

###

*N*

*j* *j*

*j*
*j*

*d*
*j*
*o*

*p* *p* *L* *p*

*f*

*N*

_{1}

### ( )

### | cos

### | ) , ( ) , , 1 (

###

###

###

###

###

*– Scale the result by the number of lights N*

_{L}*Randomly pick f or g and then sample, *

### ] *[ f* *E*

*j*

*Randomly pick f or g and then sample, * multiply the result by 2

### ] [ *f* *g* *E*

**Direct lighting**

### • Sampling strategy B: sample all lights

### d A f h li ht – do A for each light – sum the results

### t ld b t l li ht di t – smarter way would be to sample lights according to

### their power

###

_{}

*N*

*L*

*j*

*i*
*i*
*i*

*j*
*d*
*i*

*o*

*L* *p* *d*

*p* *f*

1

)

(

### ( , ) | cos | )

### , ,

### (

*j 1*

*sample f or g separately and then sum *

### ] *[ f*

*E* *sample f or g separately and then sum * them together

### ] [ *f* *g* *E*

**DirectLighting**

**enum LightStrategy { **

**SAMPLE ALL UNIFORM, SAMPLE ONE UNIFORM_** **_** **,** **_** **_**

**};** two possible strategies; if there are many image samples for a pixel
(e.g. due to depth of field), we prefer only sampling one light at a

i O h h h d if h f i l f

**class DirectLighting : public SurfaceIntegrator {**
time. On the other hand, if there are few image samples, we often
prefer sampling all lights at once.

**class DirectLighting : public SurfaceIntegrator {**
**public:**

**DirectLighting(g** **g(**

**LightStrategy ls = SAMPLE_ALL_UNIFORM, **
**int md=5** maximal depth

**);**

**...**

**}**
**}**

**Data structure **

### •Different types of lights require different numbers of samples, usually 2D samples.

### samples, usually 2D samples.

### •Sampling BRDF requires 2D samples.

### •Selection of BRDF components requires 1D samples.

3 1 2

**D** **t** **D**

**n1D** **n2D** 2 2 1 1 2 2

### sample

allocate together to avoid cache miss filled in by integrators

**oneD** **twoD** allocate together to avoid cache miss

**mem**

**lightNumOffset**
1 2 3

**lightSampleOffset** **bsdfSampleOffset**

1 3 5 2 4 6

### PathIntegrator

**lightNumOffset** **lightSampleOffset** **bsdfSampleOffset**

**DirectLighting::RequestSamples**

**void DirectLightingIntegrator::RequestSamples(**

**Sampler *sampler, Sample *sample, Scene *scene) {**
**if ( t** **t** **SAMPLE ALL UNIFORM) {**

**if (strategy == SAMPLE_ALL_UNIFORM) {**
**uint32_t nLights = scene->lights.size();**

**lightSampleOffsets=new LightSampleOffsets[nLights];**

**b dfS** **l Off** **t** **BSDFS** **l Off** **t [ Li ht ]**
**bsdfSampleOffsets = new BSDFSampleOffsets[nLights];**

**for (uint32_t i = 0; i < nLights; ++i) {**
**const Light *light = scene->lights[i];**

**i** **S** **l** **li h** **S** **l**

**int nSamples = light->nSamples;**

**if (sampler) nSamples=sampler->RoundSize(nSamples);**

gives sampler a chance to adjust to an appropriate value
**lightSampleOffsets[i]**

**= LightSampleOffsets(nSamples, sample);**

**bsdfSampleOffsets[i] **

**= BSDFSampleOffsets(nSamples, sample);**

**}**

**lightNumOffset = -1;**

**}**

**DirectLighting::RequestSamples**

**else {**

**lightSampleOffsets = new LightSampleOffsets[1];**

**li htS** **l Off** **t [0]**

**lightSampleOffsets[0] **

**= LightSampleOffsets(1, sample);**

**li htN**which light to sample**Off** **t** **l** **>Add1D(1)**
**lightNumOffset = sample->Add1D(1);**

**bsdfSampleOffsets = new BSDFSampleOffsets[1];**

**bsdfSampleOffsets[0] = BSDFSampleOffsets(1, sample);**

**}**
**}**
**}**

lightSampleOffsets records where the samples are in the sample structure.g p p p With this information, we can drive the required random numbers for generating light samples and store all random numbers required for one sample in LightSample. Similar for bsdfSample.

**DirectLighting::Li**

**Spectrum DirectLighting::Li(…)**
**{**

**Spectrum L(0.f);**

**BSDF *bsdf = isect.GetBSDF(ray, arena);**

**Vector wo = -ray.d;**

**const Point &p = bsdf->dgShading.p;**

**const Normal &n = bsdf->dgShading.nn;**

**L += isect.Le(wo);(** **);**

**if (scene->lights.size() > 0) {**
**switch (strategy) {**

**switch (strategy) {**

**case SAMPLE_ALL_UNIFORM:**

**L += UniformSampleAllLights(scene, renderer, **
**arena** **p** **n** **wo** **isect rayEpsilon**

**arena, p, n, wo, isect.rayEpsilon, **
**ray.time, bsdf, sample, rng,**

**lightSampleOffsets, bsdfSampleOffsets);**

**b** **k**

**break;**

**DirectLighting::Li**

**case SAMPLE_ONE_UNIFORM:**

**L += UniformSampleOneLight(scene, renderer, **
**arena, p, n, wo, isect.rayEpsilon, **
**ray.time, bsdf, sample, rng, **

**lightNumOffset, lightSampleOffsets, **
**bsdfSampleOffsets);**

**break;**

**}**
**}**
**}**

**if (ray.depth + 1 < maxDepth) {**
**Vector wi;**

**Vector wi;**

**L += SpecularReflect(…);**

**L += SpecularTransmit(…);**

**}**
**}**

**return L;**

**}***This part is essentially the same as Whitted integrator. The main difference *
*i th * * th * *l li ht Whitt d * *l L t t k * *l f *
*is the way they sample lights. Whitted uses sample_L to take one sample for *
*each light. DirectLighting uses multiple Importance sampling to sample both *
*lights and BRDFs.*

**Whitted::Li**

**...**

**// Add contribution of each light source**

**for (int i = 0; i < scene->lights.size(); ++i) {**
**Vector wi;**

**float pdf;**

**float pdf;**

**VisibilityTester visibility;**

**Spectrum Li = scene->lights[i]->Sample_L(…);**

**if (Li.IsBlack() || pdf == 0.f) continue;**

**Spectrum f = bsdf->f(wo, wi);**

**if (!f IsBlack() && visibility Unoccluded(scene))**
**if (!f.IsBlack() && visibility.Unoccluded(scene))**

**L += f * Li * AbsDot(wi, n) ***

**visibility.Transmittance(…) / pdf;**

**}**

**UniformSampleAllLights**

**Spectrum UniformSampleAllLights(...) **
**{**

**Spectrum L(0.);**

**for (u_int i=0;i<scene->lights.size();++i) {**
**Light *light = scene->lights[i];**

**int nSamples = lightSampleOffsets ?**

**lightSampleOffsets[i].nSamples : 1;**

**Spectrum Ld(0.);p** **(** **);**

**for (int j = 0; j < nSamples; ++j) {**

**<Find light and BSDF sample values>**

**[[lightSample=LightSample(sample,lightSampleOffsets[i],j);]]**

**Ld += EstimateDirect(...); }**
**L += Ld / nSamples;**

**}**

*compute contribution for one *
*sample for one light*

**g** **p** **g** **p** **p** **g** **p** **j**

**}**

**return L;**

**}**

*sample for one light*

### ) (

### | cos

### | ) , ( ) , , (

*j*

*j*
*j*

*d*
*j*
*o*

*p* *p* *L* *p*

*f*

###

###

###

###

###

### ) (

_{j}*p*

*i*
*i*
*i*

*d*
*i*
*o*
*o*

*e*
*o*

*o*

*p* *L* *p* *f* *p* *L* *p* *d*

*L* ( , ) ( , )

### ( , , ) ( , ) | cos |

**UniformSampleOneLight**

**Spectrum UniformSampleOneLight (...) **
**{**

**int nLights = int(scene->lights.size());**

**if (nLights == 0) return Spectrum(0.);**

**int lightNum;**

**if (lightNumOffset != -1)**
**lightNum = **

**Floor2Int(sample->oneD[lightNumOffset][0]*nLights);(** **p** **[** **g** **][ ]** **g** **);**

**else**

**lightNum = Floor2Int(RandomFloat() * nLights);**

**lightNum = min(lightNum** **nLights-1);**

**lightNum = min(lightNum, nLights 1);**

**Light *light = scene->lights[lightNum];**

**<Find light and BSDF sample values>**

**return (float)nLights *** **EstimateDirect(** **);**

**return (float)nLights * EstimateDirect(...);**

**}**

**EstimateDirect**

**Spectrum EstimateDirect(Scene *scene, Renderer *renderer, **
**Light *light, Point &p, Normal &n, Vector &wo, **

**float rayEpsilon** **float time** **BSDF *bsdf** **RNG &rng**
**float rayEpsilon, float time, BSDF *bsdf, RNG &rng, **
**LightSample &lightSample, BSDFSample &bsdfSample, **
**BxDFType flags) **

**{**

*f* ( *p* , ,

_{j}### ) *L*

_{d}### ( *p* ,

_{j}### ) | cos

_{j}### |

**{**

**Spectrum Ld(0.);**

**Vector wi;**

**float lightPdf, bsdfPdf;**

### ) (

### | cos

### | ) , ( ) , , (

*j*

*j*
*j*

*d*
*j*
*o*

*p* *p* *L* *p*

*f*

###

###

###

###

###

**float lightPdf, bsdfPdf;**

**VisibilityTester visibility;**

Here, we use multiple importance sampling to estimate the above Here, we use multiple importance sampling to estimate the above term by taking one sample according to the light and the other according to BSDF.

**Multiple importance sampling**

*f*

*f* ( ) ( ) ( ) ( ) ( ) ( )

###

###

###

^{g}*f* *n*

*j* *g* *j*

*i*
*g*
*j*
*j*

*g*
*n*

*i* *f* *i*

*i*
*f*
*i*
*i*

*f*

*p* *Y*

*Y* *w* *Y* *g* *Y* *f* *n* *X*

*p*

*X* *w* *X* *g* *X* *f*

*n*

_{1}

_{1}

### ( )

### ) ( ) ( ) 1 (

### ) (

### ) ( ) ( ) 1 (

*j* *g* *j*

*g*
*i*

*f*
*f*

###

###

###

^{s}

^{s}*s*

*x*
*p*
*x* *n*

*w* _{}

) (

) ) (

( _{Here, n}_{f}_{=n}_{g}_{=1}

###

###

*i*

*n*

*i*

*p*

*i*(

*x*)

^{}

^{f}

^{g}

**Sample light with MIS**

**Spectrum Li = light->Sample_L(p, rayEpsilon, lightSample, **
**time, &wi, &lightPdf, &visibility);**

**if (lightPdf > 0. && !Li.IsBlack()) {**
**Spectrum f = bsdf->f(wo, wi, flags);**

**if (!f IsBlack() && visibility Unoccluded(scene)) {**
**if (!f.IsBlack() && visibility.Unoccluded(scene)) {**

**Li *= visibility.Transmittance(…);**

**if (light->IsDeltaLight())**

**Ld += f * Li * (AbsDot(wi, n) / lightPdf);**

**else {**

**bsdfPdf = bsdf->Pdf(wo, wi, flags);(** **,** **,** **g );**

**float weight = **

**PowerHeuristic(1, lightPdf, 1, bsdfPdf);**

**Ld +** **f * Li * (Ab D t( i** **) *** **i ht / li htPdf)**
**Ld += f * Li * (AbsDot(wi, n) * weight / lightPdf);**

**}**

**}**

*f* ( *p* ,

_{o}### ,

_{j}### ) *L*

_{d}### ( *p* ,

_{j}### ) | cos

_{j}### | *w*

_{L}### (

_{j}### )

**}**

*p* (

_{j}### )

**Sample BRDF with MIS**

**if (!light->IsDeltaLight()) {**
**BxDFType sampledType;**

*If it is delta light, no need *
*to sample BSDF*

**Spectrum f = bsdf->Sample_f(wo, &wi, bsdfSample, **

**&bsdfPdf, flags, &sampledType);**

**if (!f IsBlack() && bsdfPdf > 0 ) {**
**if (!f.IsBlack() && bsdfPdf > 0.) {**

**float weight = 1.f;**

**if (!(sampledType & BSDF_SPECULAR)) {**
*weight=1 is for specular lights*

**lightPdf = light->Pdf(p, wi);**

**if (lightPdf == 0.) return Ld;**

**weight = PowerHeuristic(1, bsdfPdf, 1, lightPdf);g** **( ,** **,** **,** **g** **);**

**}**

**I t** **ti** **li htI** **t**

*We need to test whether we can see the light along the sampled direction *
**Intersection lightIsect;**

**Spectrum Li(0.f);**

**RayDifferential ray(p, wi, rayEpsilon, INFINITY, time);**

**Sample BRDF with MIS**

**if (scene->Intersect(ray, &lightIsect)) {**
*If we can see it, record its Li*

**if (lightIsect.primitive->GetAreaLight() == light)**
**Li = lightIsect.Le(-wi);**

**} else** *No intersection but it could be an infinite *
**} else**

**Li = light->Le(ray);**

*No intersection, but it could be an infinite *
*area light. For non-infinite-area lights,*
*Le return 0.*

**if (!Li.IsBlack()) {**

**Li *= renderer->Transmittance(…);**

**Ld += f * Li * AbsDot(wi, n) * weight / bsdfPdf;(** **,** **)** **g** **/** **;**
**}**

**}**

**t** **Ld**

**return Ld;**

**}**

**Direct lighting**

**The light transport equation**

### • The goal of integrator is to numerically solve the light transport equation governing the the light transport equation, governing the equilibrium distribution of radiance in a scene.

**The light transport equation**

**Analytic solution to the LTE**

### • In general, it is impossible to find an analytic solution to the LTE because of complex BRDF solution to the LTE because of complex BRDF, arbitrary scene geometry and intricate visibility.

### F t l i l i id

### • For an extremely simple scene, e.g. inside a uniformly emitting Lambertian sphere, it is h ibl Thi i f l f d b i however possible. This is useful for debugging.

### • Radiance should be the same for all points

### • Radiance should be the same for all points

*L* *c* *L*

*L*

_{e}###

**Analytic solution to the LTE** *L*

*c* *L*

*L*

_{e}###

*hh*
*e*

*L* *L*

*L*

*L* *L*

*L*

###

###

###

###

###

###

###

### ) (

*e*
*hh*
*e*

*hh*
*e*

*hh*
*e*

*hh*
*e*

*L* *L*

*L*

*L* *L*

*L*

###

###

###

###

###

###

###

###

###

###

###

### ...

### ( (

### ) (

*i*
*hh*

*e*
*hh*
*e*

*hh*
*e*

*L*

###

###

##

^{}

###

### ( (

*hh*
*i*

*L*

*e*

###

##

0*L*

*hh*

*L*

*e*

*L*

### 1

_{hh}### 1

**Surface form of the LTE** **Surface form of the LTE**

### These two forms are equivalent, but they represent two

### different ways of approaching light transport.

**Surface form of the LTE** **Surface form of the LTE**

**Surface form of the LTE** **Delta distribution**

**Partition the integrand** **Partition the integrand**

**Partition the integrand** **Rendering operators**

**Solving the rendering equation** **Successive approximation**

**Successive approximation** **Light transport notation** **(Hekbert 1990**

**)**

### • Regular expression denoting sequence of events **along a light path alphabet: {L E S D G}**

**along a light path alphabet: {L,E,S,D,G}**

**– L a light source (emitter)** **E h **

**– E the eye**

**– S specular reflection/transmission** **– D diffuse reflection/transmission** **– G glossy reflection/transmission** **G glossy reflection/transmission**

### • operators:

**(k)** f k **– (k)**

^{+}### one or more of k

**– (k)**

^{*}### zero or more of k (iteration)

**– (k|k’) a k or a k’ event**

**Light transport notation: examples**

### • LSD

### th t ti t li ht h i l – a path starting at a light, having one specular

### reflection and ending at a diffuse reflection

### D L S

**Light transport notation: examples**

### • L(S|D)

^{+}

### DE

### th t ti t li ht h i diff – a path starting at a light, having one or more diffuse

### or specular reflections, then a final diffuse reflection toward the eye

### reflection toward the eye

### E

### D L

### D

### S

**Light transport notation: examples**

### • L(S|D)

^{+}

### DE

### th t ti t li ht h i diff – a path starting at a light, having one or more diffuse

### or specular reflections, then a final diffuse reflection toward the eye

### reflection toward the eye

### E

### L D E

### S

### D S

**Rendering algorithms**

### • Ray casting: E(D|G)L Whi d E[S*](D|G)L

### • Whitted: E[S*](D|G)L

### • Kajiya: E[(D|G|S)

^{+}

### (D|G)]L

### • Goral: ED*L

**The rendering equation** **The rendering equation**

**The radiosity equation** **Radiosity**

### • formulate the basic radiosity equation:

*N*

*B*

_{m}* E*

_{m}###

_{m}*B*

_{n}*F*

_{mn}*n1*

###

*N*

### • B

_{m}

### = radiosity = total energy leaving surface m (energy/unit area/unit time)

*n 1*

### (energy/unit area/unit time)

### • E

_{m}

### = energy emitted from surface m (energy/unit area/unit time)

### area/unit time)

### •

_{m}

### = reflectivity, fraction of incident light reflected back into environment

### • F

_{mn}

### = form factor, fraction of energy leaving surface n that lands on surface m

### • (A

_{m}

### = area of surface m)

**Radiosity**

### • Bring all the B’s on one side of the equation

*E*

_{m}* B*

_{m}###

_{m}*B*

_{n}*F*

_{mn}*m*

##

### • this leads to this equation system:

^{m}###

###

###

###

###

### 1 *F* *F* *F* *B* *E*

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

*N*
*N*

*E* *E* *B*

*B* *F*

*F* *F*

*F* *F*

*F*

2 1 2

1 2

2 22

2 21

2

1 1 12

1 11

1

### ...

### 1

### ...

### 1

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

###

### *F* *F* *F* *B* *E*

###

###

###

###

###

###

### 1

###

### _{} _{} _{} _{} _{}

###

_{N}*F*

_{N}_{1}

###

_{N}*F*

_{N}_{2}

### ... 1

_{N}*F*

_{NN}*B*

_{N}*E*

_{N}*E* *B* *S* *B* *E* *S*

**Path tracing**

### • Proposed by Kajiya in his classic SIGGRAPH 1986 paper rendering equation as the solution for paper, rendering equation, as the solution for

### • Incrementally generates path of scattering y g p g events starting from the camera and ending at light sources in the scene. g

### • Two questions to answer

### – How to do it in finite time? How to do it in finite time?

### – How to generate one or more paths to compute

**Infinite sum**

### • In general, the longer the path, the less the impact

### impact.

### • Use Russian Roulette after a finite number of bbounces

### – Always compute the first few terms *– Stop after that with probability q*

**Infinite sum**

### • Take this idea further and instead randomly consider terminating evaluation of the sum at consider terminating evaluation of the sum at *each term with probability q*

_{i}**Path generation (first trial)**

*• First, pick up surface i in the scene randomly * and uniformly

*A*

### and uniformly

###

*j* *j*

*i*

*i* *A*

*p* *A*

### • Then, pick up a point on this surface randomly and uniformly with probability

*A**i*

1

### • Overall probability of picking a random surface point in the scene:

*A**i*

### p

###

###

^{}

^{}

^{i}

*i*

*A* *A* *A* *A*

*p* *A*

*p* 1 1

)

(

###

*j*

*j*

*i*

###

*j*

*j*

*i*

*A* *p* *A* *A* *A*

*p* ( )

**Path generation (first trial)**

### • This is repeated for each point on the path.

### L i h ld b l d li h

### • Last point should be sampled on light sources only.

### • If we know characteristics about the scene (such as which objects are contributing most indirect lighting to the scene), we can sample more smartly.

### • Problems:

### – High variance: only few points are mutually visible, g y p y , i.e. many of the paths yield zero.

### – Incorrect integral: for delta distributions, we rarely g y

### find the right path direction

**Incremental path generation**

### • For path

### A h fi d di BSDF (i hi

*i*
*j*
*j*

*i*

*p* *p* *p* *p* *p*

*p*

_{0}

_{1}

### ...

_{}

_{1}

### ...

*– At each p*

_{j}*, find p*

_{j+1}### according to BSDF (in this way, they are guaranteed to be mutually

### i ibl ) visible)

*– At p*

_{i-1}*, find p*

_{i }### by multiple importance sampling of BSDF and L

### • This algorithm distributes samples according to g p g solid angle instead of area. So, the distribution *p*

_{A}### needs to be adjusted

*p*

_{A}### j

### | cos ) |

### (

2
*i* 1
*i*
*i*

*A*

*p* *p* *p*

*p*

*p*

^{}

###

###

### | cos

_{i}### |

**Incremental path generation**

### • Monte Carlo estimator

### • Implementation re-uses path for new path *p*

*i*1

*p*

*i*

### MIS sampled by BSDF

### • Implementation re uses path for new path This introduces correlation, but speed makes up for it

*p*

*i*

1

*p*

*i*

### for it.

**Path tracing** **Direct lighting**

**Path tracing**

### 8 samples per pixel

**Path tracing**

### 1024 samples per pixel

**Bidirectional path tracing**

### • Compose one path from two paths

### t t d t th d

*p*

*–p*

_{1}*p*

_{2}*…p*

_{i}### started at the camera p

_{0}

### and *–q*

_{j}*q*

_{j-1}*…q*

_{1}### started at the light source q

_{0}

### • Modification for efficiency:

1 1 2

1

*p* ... *p* , *q* *q* ... *q* *p*

*p*

_{i}###

_{i}

_{j}

_{j}_{}

### y

### –Use all paths whose lengths ranging from lengths ranging from 2 to i+j

*H l f l f th it ti* * i hi h li ht * * diffi lt * *Helpful for the situations in which lights are difficult * *to reach and caustics*

**Bidirectional path tracing**

**PathIntegrator**

**class PathIntegrator : public SurfaceIntegrator {**
**public:**

**Spectrum Li(…) const;**

**void RequestSamples(…);**

**PathIntegrator(int md) { maxDepth = md; }**
**PathIntegrator(int md) { maxDepth = md; }**
**private:**

**int maxDepth;**

*Use samples from Sampler for the first SAMPLE_DEPTH vertices of the path.*

*After that, the advantage of well-distributed samples are greatly reduced,*
*And it switches to using uniform random numbers. *

**#define SAMPLE_DEPTH 3**

**LightSampleOffsets lightSampleOffsets[SAMPLE_DEPTH];**

*g* *f*

**int lightNumOffset[SAMPLE_DEPTH];**

**BSDFSampleOffsets bsdfSampleOffsets[SAMPLE_DEPTH];**

**BSDFSampleOffsets pathSampleOffsets[SAMPLE DEPTH];p** **p** **p** **[** **_** **];**

**};**

**RequestSamples**

**class PathIntegrator::RequestSamples(…) **
**{**

**{**

**for (int i = 0; i < SAMPLE_DEPTH; ++i) {**

*Path is reused. Thus, for each vertex, we need to perform MIS as it *
*serves as the terminated point for some path. Therefore, we need *
**lightSampleOffsets[i]=LightSampleOffsets(1,sample);**

**lightNumOffset[i] = sample >Add1D(1);**

*serves as the terminated point for some path. Therefore, we need *
*both light and brdf samples*

**lightNumOffset[i] = sample->Add1D(1);**

**bsdfSampleOffsets[i] = BSDFSampleOffsets(1,sample);**

**pathSampleOffsets[i] = BSDFSampleOffsets(1,sample);**

**p** **p** **[ ]** **p** **( ,** **p** **)**

**}**
**}**

*Another bsdf sample is used for extending the path*

**PathIntegrator::Li**

**class PathIntegrator::Li(…) const **
**{**

**{**

**Spectrum pathThroughput = 1., L = 0.;**

**RayDifferential ray(r);**

**bool specularBounce = false;**

**Intersection localIsect;**

**const Intersection *isectp = &isect;**

**const Intersection *isectp = &isect;**

**for (int bounces = 0; ; ++bounces) {**

**<possibly add emitted light at vertex>p** **y** **g**

**<sample from lights to find path contributions>**

**<sample BSDF to get new path direction>**

**<possibly terminate the path>**

**<find next vertex of path>**

**}**
**}**

**return L;**

**}**

**PathIntegrator::Li**

**<possibly add emitted light at vertex>**

**if (bounces == 0 || specularBounce)(** **||** **p** **)**

**L += pathThroughput * isectp->Le(-ray.d);**

**PathIntegrator::Li**

**<sample from lights to find path contributions>**

**BSDF *bsdf = isectp->GetBSDF(ray, arena);**

**const Point &p = bsdf->dgShading.p;**

**const Normal &n = bsdf->dgShading.nn;**

**Vector wo = -ray.d;**

**Vector wo** ** ray.d;**

**if (bounces < SAMPLE_DEPTH)**
**L += pathThroughput ***

**UniformSampleOneLight(scene** **renderer** **arena**
**UniformSampleOneLight(scene, renderer, arena, **

**p, n, wo, isectp->rayEpsilon, ray.time, **
**bsdf, sample, rng, lightNumOffset[bounces], **

**&lightSampleOffsets[bounces], **

**&bsdfSampleOffsets[bounces]);**

**else**

**L += pathThroughput ***

**UniformSampleOneLight(scene, renderer, arena, **
**p** **n** **wo** **isectp->rayEpsilon** **ray time**

**p, n, wo, isectp >rayEpsilon, ray.time, **
**bsdf, sample, rng);**

**PathIntegrator::Li**

**<sample BSDF to get new path direction>**

**BSDFSample outgoingBSDFSample;**

**if (bounces < SAMPLE_DEPTH)**

**outgoingBSDFSample = BSDFSample(sample,**

**pathSampleOffsets[bounces], 0);**

**pathSampleOffsets[bounces], 0);**

**else**

**outgoingBSDFSample = BSDFSample(rng);**

**Vector wi;**

**Vector wi;**

**float pdf;**

**BxDFType flags;**

**Spectrum f = bsdf->Sample_f(wo, &wi,**

**outgoingBSDFSample, &pdf, BSDF_ALL, &flags);**

**if (f.IsBlack() || pdf == 0.)(** **() || p** **)**
**break;**

**specularBounce = (flags & BSDF_SPECULAR) != 0;**

**pathThroughput *= f * AbsDot(wi** **n) / pdf;**

**pathThroughput *= f * AbsDot(wi, n) / pdf;**

**ray = RayDifferential(p, wi, ray, isectp->rayEpsilon);**

**PathIntegrator::Li**

**<possibly terminate the path> **

**if (bounces > 3) {(** **) {**

**float continueProbability = **

**min(.5f, pathThroughput.y());**

**if (rng.RandomFloat() > continueProbability)**
**break;**

**pathThroughput /= continueProbability;**

**pathThroughput /= continueProbability;**

**}**

**if (bounces == maxDepth)(** **p** **)**
**break;**

**PathIntegrator::Li**

**<find next vertex of path> **

**if (!scene->Intersect(ray, &localIsect)) {(** **(** **y,** **)) {**
**if (specularBounce)**

**for (int i = 0; i < scene->lights.size(); ++i)**
**L += pathThroughput*scene->lights[i]->Le(ray);**

**break;**

**}**
**}**

**if (bounces > 1)**

**pathThroughput *= renderer->Transmittance(scene,**

**p** **g p** **(** **,**

**ray, NULL, rng, arena);**

**isectp = &localIsect;**

**Noise reduction/removal**

### • Path tracing is unbiased and often taken as a reference The problem is that it has high reference. The problem is that it has high variances.

### • More samples (slow convergence)

### • Better sampling (stratified, importance etc.)

### • Filtering

### • Filtering

### • Caching and interpolation (reuse samples)

**Biased approaches**

### • By introducing bias (making smoothness assumptions), biased methods produce images without high-frequency biased methods produce images without high frequency noise

### • Unlike unbiased methods, errors may not be reduced by , y y adding samples in biased methods

### • On contrast, when there is little error in the result of an unbiased method, we are confident that it is close to the right answer

### • Biased approaches – Filtering

### – Instant global illumination – Irradiance caching

### – Photon mapping

**The world is more diffuse!** **Filtering**

### • Noise is high frequency M h d

### • Methods:

### – Simple filters – Anisotropic filters

### – Energy preserving filters

### • Problems with filtering: everything is filtered

### (blurred)

**Path tracing (10 paths/pixel)** **3x3 lowpass filter**

**3x3 median filter** **Instant global illumination**

### • Preprocess: follows some light-carrying paths from the light sources to create virtual light from the light sources to create virtual light sources.

### R d i l th i t l li ht t

### • Rendering: use only the virtual lights to compute the indirect contributions.

### • Since only a set of virtual lights are used, there

### will be systemic error due to correlation rather

### than noise due to variance. Similar artifacts for

### your project #3.

**Instant global illumination**

light source light source

virtual

light direct virtual

light indirect

virtual light

**Instant global illumination**

light source light source

*p*

2
*p*

*n*

*p*

0 What should we store
in the virtual lights?

*p*

1
*p*

3
**Instant global illumination**

### ) (

### ) (

### ) (

### )

### ( *p* *f* *p*

_{3}

*p*

_{2}

*p*

_{1}

*G* *p*

_{2}

*p*

_{1}

*f* *p*

_{2}

*p*

_{1}

*p*

_{0}

*P* ( *p*

_{n}

_{n}### ) *f* ( *p*

_{3}

### *p*

_{2}

### *p*

_{1}

### ) ( *p*

_{2}

### *p*

_{1}

### ) *f* ( *p*

_{2}

### *p*

_{1}

### *p*

_{0}

### )

###

###

###

_{1}

### ) (

_{1}

_{2}

### ) | cos

_{1}

### |

### ( *p* *p* *f* *p* *p* *p*

*L*

###

###

###

###

###

^{}

^{}

^{}

^{}

2 - n

1 2

1 1

### |

### | ) (

### ) (

### | cos

### | ) (

### ) (

*n*
*A*

*n*
*n*

*n*
*n*
*n*

*n*
*e*

*f*

*p* *P*

*p* *p*

*p* *f* *p* *p* *L*

###

###

###

###

###

###

###

###

###

2

- n

3

i 1

1 1

### ) (

### | cos

### | ) (

*i*
*i*

*i*
*i*

*i*
*i*

*p* *p*

*P*

*p* *p* *p*

*f*

###

It is independent to the camera and the first visible point p_{1}.
It is what we should pre-compute and store at the virtual lights.

During rendering, for each shading point, we need to evaluate the two remaining BRDFs and the geometric term.

**Caching techniques**

### • Irradiance caching: compute irradiance at selected points and interpolate

### selected points and interpolate

### • Photon mapping: trace photons from the lights

### d t th i h t th t b

### and store them in a photon map, that can be

### used during rendering

**Direct illumination** **Global illumination**

**Indirect irradiance**

**Indirect illumination tends to be low frequency**

**Indirect illumination tends to be low frequency**

**Irradiance caching**

### • Introduced by Greg Ward 1988

### I l d i R di d

### • Implemented in Radiance renderer

### • Contributions from indirect lighting often vary

### smoothly →cache and interpolate results

**Irradiance caching**

### • Compute indirect lighting at sparse set of samples

### samples

### • Interpolate neighboring values from this set of l

### samples

### • Issues

### – How is the indirect lighting represented

### – How to come up with such a sparse set of samples?

### – How to store these samples?

### – When and how to interpolate? p

**Set of samples**

### • Indirect lighting is computed on demand, store *irradiance in a spatial data structure If there is * *irradiance in a spatial data structure. If there is * no good nearby samples, then compute a new irradiance sample

### irradiance sample

### • Irradiance (radiance is direction dependent, i t t )

### expensive to store)

*H*

*L*

*i*

*p*

*i*

*i*

*d*

*i*

*p*

*E* ^{(} ^{)} ^{}

^{2}

^{(} ^{,} ^{)} ^{|} ^{cos} ^{|}

### • If the surface is Lambertian,

^{H}### | cos

### | ) ( ) (

### )

### ( *p* *f* *p* *L* *p* *d*

*L*

### | cos

### | ) , (

### | cos

### | ) , ( ) , , ( )

### , (

2 2

*d* *p*

*L*

*d* *p*

*L* *p*

*f* *p*

*L*

*H* *i* *i* *i* *i*

*H* *o* *i* *i* *i* *i* *i*

*o*
*o*

###

###

###

###

###

###

###

###

###

###

###

###

###

###

### ) (

### *E* *p*

*H*

###

###

**Set of samples**

### • For diffuse scenes, irradiance alone is enough information for accurate computation

### information for accurate computation

### • For nearly diffuse surfaces (such as Oren-Nayar

### l f ith id l

### or a glossy surface with a very wide specular lobe), we can view irradiance caching makes th f ll i i ti

### the following approximation

### ^{(} ^{,} ^{,} ^{)} ^{(} ^{,} ^{)} ^{|} ^{cos} ^{|}

### ) ,

### ( *p* *f* *p* *d* *L* *p* *d*

*L*

_{i}###

_{i}###

_{i}###

_{i}###

_{i}###

_{i}###

### ( ) ( )

### | cos

### | ) , ( )

### , , ( )

### , (

2 1

2 2

*p* *E*

*d* *p*

*L* *d*

*p* *f* *p*

*L*

*o*
*hd*

*H* *i* *i* *i* *i*

*H* *o* *i* *i*

*o*
*o*

###

###

###

###

###

###

###

###

###

###

###

###

### directional reflectance

**Set of samples**

### | cos

### |

### | cos

### | ) (

### ) , ( )

### ,

### ( *p* *n*

_{2}

*L*

_{i}*p*

_{i}

_{i}

_{avg}

_{i}*d*

_{i}*L*

_{avg}

_{avg}*E* ^{(} *p* ^{,} ^{)}

*H*

^{2}

*i*

^{(} *p* ^{,}

*i*

^{)} ^{(}

*i*

###

*avg*

^{)} ^{|}

*i*

^{|}

^{i}###

^{avg}^{|}

^{avg}^{|}

*L* *E*

### | cos

### |

_{avg}*L*

*avg*

###

### | cos

### | | cos ) | (

### ) , , ( )

### ,

### (

_{2}

*E* *d*

*p* *f* *p*

*L*

_{i}*H* *o* *i* *i* *avg* *i*

*o*

*o*

###

###

###

###

###

###

###

### ) , ( ) , , (

### | cos

### | *n*

*p* *E* *p*

*f*

_{o}

_{avg}*avg*

###

###

###

###

### makes it directional

**Set of samples**

### • Not a good approximation for specular surfaces l Whi d i

### • specular → Whitted integrator

### • Diffuse/glossy → irradiance caching

### – Interpolate from known points – Cosine-weighted

### – Path tracing sample points

*i*
*i*
*i*

*i*

*p* *d*

*L* *p*

*E* ^{(} *p* ^{)} ^{}

*H*

*L*

*i*

^{(} *p* ^{,}

*i*

^{)} ^{|} ^{cos}

*i*

^{|} *d*

^{i}*E* ^{(} ^{)}

^{2}

^{(} ^{,} ^{)} ^{|} ^{cos} ^{|}

###

### *L*

^{i}*p*

^{j}

^{j}*p*

*E* ( )

### | cos

### | ) , 1 (

### )

### (

###

*j*

*p*

*j*

*p* *N*

### ) ) (

### (

###

### *L*

_{i}*p*

_{j}*p* *N*

*E* ( ) ( , ) *p* ( ) cos

###

*j*

*j*

*N*

*i*

**Storing samples**

### • Samples are stored in an octree.

### E h l h f ll i i f i

### • Each sample stores the following information {E,p,n,w

_{avg}

### ,d

_{max}

### }

### M i l di t i k t d i th t i f

### • Maximal distance is kept during path tracing for computing the sample. d

_{i}

### is the distance that th ith hit i t ti

### the ith ray hit an intersection.

*d*

_{i}**Storing samples**

### {E,p,n,d}

*d* *d*

_{i}**Interpolating from neighbors**

### • Weights depend on

### A l b t l

### – Angle between normals – Distance between points

### h d h

### • Weight (ad hoc)

###

### *d* 1 *N* *N* '

###

###

###

###

###

###

###

###

max

max

### 1 cos

### , 1 max

### 1

*N* *N* *d*

*w*

_{i}*d*

### • Final irradiance estimate is simply the weighted sum

###

###

^{i}*w*

^{i}*E*

^{i}*E*

*i*

*w*

*i*

**IrradianceCacheIntegrator**

**class IrradianceCacheIntegrator : public **
**SurfaceIntegrator {**

**...**

**float minSamplePixelSpacing, maxSamplePixelSpacing; **

**fl** **i** **i h** **S** **l** **l** **iff**

**float minWeight, cosMaxSampleAngleDifference;**

**int nSamples; ***how many rays for computing irradiance samples*
**int maxSpecularDepth, maxIndirectDepth;**

**int maxSpecularDepth, maxIndirectDepth;**

**}**

**Preprocess() allocates the octree for storing irradiance samples**

**Li**

**L += isect.Le(wo);**

**L += UniformSampleAllLights(...);**

**if (ray.depth+1 < maxSpecularDepth) {**

**<Trace rays for specular reflection and refraction>**

**}** Current implemetation uses Whitted style for specular; irradiance cache for

*// Estimate indirect lighting with irradiance cache*

Both diffuse and glossy. It could lead to errors for glossy.

*the project area of a pixel *
**...**

**float pixelSpacing = **

**sqrtf(Cross(isect.dg.dpdx, isect.dg.dpdy).Length());**

**BxDFType flags =**

*the project area of a pixel *
*in the world space*

**BxDFType flags = **

**BxDFType(BSDF_REFLECTION|BSDF_DIFFUSE|BSDF_GLOSSY);**

**L += indirectLo(...);**

**l**

**Flags = **

**BxDFType(BSDF_TRANSMISSION|BSDF_DIFFUSE|BSDF_GLOSSY);**

**L += indirectLo(...);**

**IndirectLo**

**if (!InterpolateE(scene, p, n, &E, &wi)) {**
*... // Compute irradiance at current point*

**for (int i = 0; i < nSamples; ++i) {**

**<Path tracing to compute radiances along ray **
**for irradiance sample>**

**for irradiance sample>**

**LiSum += L;**

**wAvg += r.d * L.y();**

**i HitDi t** **i ( i HitDi t** **t)**

**minHitDistance = min(minHitDistance, r.maxt); **

**}**

**E = (M_PI / float(nSamples)) * LiSum;_**
*... // Add computed irradiance value to cache*
**IrradianceSample *sample = **

**new IrradianceSample(E** **p** **ng** **wAvg** **contribExtent);**

**new IrradianceSample(E, p, ng, wAvg, contribExtent);**

**octree->Add(sample, sampleExtent);**

**}**

**return bsdf->f(wo, Normalize(wi), flags) * E;**

**Octree**

**void IrradianceCache::Preprocess(const Scene *scene)**
**{**

**BBox wb = scene->WorldBound();**

**Vector delta = .01f * (wb.pMax - wb.pMin);**

**wb pMin -= delta;**

**wb.pMin -= delta;**

**wb.pMax += delta;**

**octree=new Octree<IrradianceSample *>(wb);**

**<prefill the irradiacne cache>**

**}**

**struct IrradianceSample {**
**struct IrradianceSample {**

**Spectrum E;**

**Normal n;**

**Point p;**

**Vector wAvg;**

**float maxDist;;**
**};**