**DirectX Raytracing, Tutorial 11**
**Lambertian surface with one random shadow ray per pixel**
In [Tutorial 9](../Tutor9/tutorial09.md.html), we added a Lambertian shading model that traced a shadow ray
to every light. While accurate, there are many reasons tracing shadows rays for each light maybe a poor choice.
A key consideration is costs increase linearly with additional lights in the scene.
In this tutorial, we will randomly select *_just one_* light to test for each pixel. This is a common
strategy when path tracing with explicit direct lighting, but more importantly introduces the idea
of [Monte Carlo integration](https://en.wikipedia.org/wiki/Monte_Carlo_integration) on a very simple
quantity (the Lambertian term with direct illumination).
Changes to our C++ Render Pass
=================================================================================
There is a single difference between the C++ code for this tutorial and [Tutorial 9](../Tutor9/tutorial09.md.html).
Since our shader requires a random number generator, we pass down a changing "frame count" to allow us to reseed our
pseudorandom number generator each frame.
Our header *`DiffuseOneShadowRayPass.h`* declares:
~~~~~~~~~~~~~~~~~~~~C
uint32_t mFrameCount = 0x1337u;
~~~~~~~~~~~~~~~~~~~~
This is a counter that increments each frame and only seeds our per-pixel random numbers. Because we
don't want the same random number generator as *`ThinLensGBufferPass`* (or any of our other *`RenderPasses`*), we
initialize *`mFrameCount`* to a different value in each pass. I picked somewhat arbitrary hexadecimal numbers, but
there's nothing special about these values *_except_* each pass is initialized uniquely.
We also pass this *`mFrameCount`* down to our shader so we can use it:
~~~~~~~~~~~~~~~~~~~~C
auto rayGenVars = mpRays->getRayGenVars();
rayGenVars["RayGenCB"]["gFrameCount"] = mFrameCount++;
~~~~~~~~~~~~~~~~~~~~
HLSL Changes for Random Light Selection
=================================================================================
Open up *`diffusePlus1Shadow.rt.hlsl`*. Our first change is we initialize a pseudorandom number
generator using our input frame count:
~~~~~~~~~~~~~~~~~~~~C
[shader("raygeneration")]
void LambertianShadowsRayGen()
{
...
uint randSeed = initRand( launchIndex.x + launchIndex.y * launchDim.x,
gFrameCount );
...
}
~~~~~~~~~~~~~~~~~~~~
Then when we are shading, we no longer loop over our lights:
~~~~~~~~~~~~~~~~~~~~C
// Old: loop over all lights
// for (int lightIndex = 0; lightIndex < gLightsCount; lightIndex++)
// New: pick a single random light in [0...gLightsCount-1]
int lightIndex = min( int(gLightsCount * nextRand(randSeed)), gLightsCount-1 );
~~~~~~~~~~~~~~~~~~~~
The last consideration: because we use just a single light for shading, in a
scene with *_N_* lights our random sampling will be too dark (by a factor
of *_N_*). In [Monte Carlo integration](https://en.wikipedia.org/wiki/Monte_Carlo_integration),
the contribution of each random sample should be divided by the probability of
selecting that sample.
Our uniform random light selection (above) samples each light with probability *`1.0f / N`*,
so when shading with a random light, we should multiply that light's contribution by *`N`*. If
using a more intelligent light sampling scheme, a more sophisticated probability must be computed.
This means our shading computation changes:
~~~~~~~~~~~~~~~~~~~~C
// Old, per-light contribution when looping through all lights
// shadeColor += NdotL * lightColor * isLit * matlDiffuse / M_PI;
// New contribution for a single, randomly selected light
shadeColor = gLightsCount * NdotL * lightColor * isLit * matlDiffuse / M_PI;
~~~~~~~~~~~~~~~~~~~~
What Does it Look Like?
===============================================================================
That covers the important points of this tutorial. When running, you get the following result:
![Result of running Tutorial 11, after loading the scene "pink_room.fscene"](Tutor11-Output.png)
The tutorial defaults to running with temporal accumulation enabled, so if you turn away for a
moment, you might not see the difference from [Tutorial 9](../Tutor9/tutorial09.md.html). However,
if you disable temporal accumulation, you will notice the shadows are significantly noisier.
Something we observed in our research: it is easier to denoise direct lighting than
indirect, global lighting. This means if you decide to use a filter (like our
[spatiotemporal variance-guded filter](http://research.nvidia.com/publication/2017-07_Spatiotemporal-Variance-Guided-Filtering%3A))
to reconstruct noisy results for your indirect lighting, you can probably save the cost of extra
shadow rays for direct lighting. By the time you have enough information to reconstruct indirect light,
you probably have more than enough to reconstruct your direct light, too.
When you are ready, continue on to [Tutorial 12](../Tutor12/tutorial12.md.html), which adds one bounce
diffuse global illumination.