**DirectX Raytracing, Tutorial 9** **A simple Lambertian material model with ray traced shadows** In [Tutorial 8](../Tutor8/tutorial08.md.html), we added random camera jitter over a camera lens to introduce dynamic [depth of field](https://en.wikipedia.org/wiki/Depth_of_field), which is typically hard to achieve using rasterization. This tutorial finally gets rid of the [ambient occlusion](https://en.wikipedia.org/wiki/Ambient_occlusion) shading introduced in [Tutorial 5](../Tutor5/tutorial05.md.html) and replaces it with a simple diffuse shading model (also known as a [Lambertian](https://en.wikipedia.org/wiki/Lambertian_reflectance) material model). In addition to using Lambertian shading, we also trace shadow rays to each light in the scene. This gives us pixel-accurate hard shadows, which is still something of an open problem in rasterization (which has been plagued by [shadow map](https://en.wikipedia.org/wiki/Shadow_mapping) [artifacts](http://roar11.com/2015/05/dealing-with-shadow-map-artifacts/) for decades). Our Lambertian Rendering Pipeline ================================================================================= If you open up *`Tutor09-LambertianPlusShadows.cpp`*, you will find our new pipeline combines a the *`ThinLensGBufferPass`* from [Tutorial 8](../Tutor8/tutorial08.md.html) and our new *`LambertianPlusShadowPass`*. ~~~~~~~~~~~~~~~~~~~~ C // Create our rendering pipeline RenderingPipeline *pipeline = new RenderingPipeline(); pipeline->setPass(0, ThinLensGBufferPass::create()); pipeline->setPass(1, LambertianPlusShadowPass::create()); ~~~~~~~~~~~~~~~~~~~~ Just like the ambient occlusion pass our new *`LambertianPlusShadowPass`* replaces, we will read from a G-buffer, compute shading and ray traced shadows, and output the result to the displayed image *`kOutputChannel`*. Setting up Our Lambertian Rendering Pass ================================================================================= Continue by looking in *`LambertianPlusShadowPass.h`*. This header consists entirely of standard boilerplate and *`RayLaunch`* wrappers you have seen in prior tutorials. Similarly, looking at *`LambertianPlusShadowPass::initialize()`* shows a set of familar functionality: requesting appropriate inputs from our G-buffer pass and setting up our diffuse *`RayLaunch`* wrapper's ray generation, miss, closest-hit, and any-hit shaders appropriately. Looking at *`LambertianPlusShadowPass::execute()`* is also straightforward, though it might be worth pointing out that in: ~~~~~~~~~~~~~~~~~~~~C rayGenVars["RayGenCB"]["gMinT"] = mpResManager->getMinTDist(); ~~~~~~~~~~~~~~~~~~~~ *`getMinTDist()`* returns a selectable parameter from the GUI that specifies how far shadow rays should be offset to avoid self-intersections due to numerical precision. Lambertian Shading and Shooting Shadow Rays ================================================================================= In our HLSL shader file *`lambertianPlusShadows.rt.hlsl`*, let's start by looking at out ray generation shader *`LambertShadowsRayGen()`*: ~~~~~~~~~~~~~~~~~~~~C [shader("raygeneration")] void LambertShadowsRayGen() { ... float4 difMatlColor = gDiffuseMatl[launchIndex]; // If we don't hit any geometry, our difuse material is our background color. float3 shadeColor = (worldPos.w != 0.0f) ? float3(0,0,0) : difMatlColor.rgb; ... ~~~~~~~~~~~~~~~~~~~~ Most of the the beginning of this shader is identical to our ambient occlusion shader. However, we do load our diffuse material color from the G-buffer. The G-buffer's diffuse color serves two purposes in our implementation: for surfaces, it stores their Lambertian color; for background pixels, it stores the background color. So, we initialize our final output color *`shadeColor`* to the diffuse color if our pixel falls on the background, otherwise we initialize our shaded color to black. ~~~~~~~~~~~~~~~~~~~~ C ... // Loop over all the lights in the scene for (int lightIdx = 0; lightIdx < gLightsCount; lightIdx++) { // Query our scene to get this info about the current light float distToLight; // How far away is it? float3 lightColor; // What color is it? float3 toLight; // What direction is it from our current hitpoint? // A helper to query the Falcor scene to get light data getLightData(lightIdx, worldPos.xyz, toLight, lightColor, distToLight); // Compute our Lambertion term (NL dot L) float NdotL = saturate(dot(worldNorm.xyz, toLight)); // Shoot our ray. Return 1.0 for lit, 0.0 for shadowed float vis = shootShadowRay(worldPos.xyz, toLight, gMinT, distToLight); // Accumulate our Lambertian shading color shadeColor += vis * NdotL * lightColor; } // Modulate based on the physically based Lambertian term (albedo/pi) shadeColor *= difMatlColor.rgb / M_PI; ... } ~~~~~~~~~~~~~~~~~~~~ The next important part of our Lambertian shader is our loop over the scene's lights. Here *`gLightsCount`* is an automatically set Falcor global variable that specifies how many lights your currently loaded scene has. We need to loop over all these lights and accumulate the light's (potentially shadowed) contribution to the current pixel. To do this we need a little more data about the light's properties. To avoid digging into specifcs of Falcor's scene representation, I wrote a simple helper *`getLightData()`* to extract this light data. (For reference, this function is available in header with HLSL helper functions in the tutorial's Visual Studio project.) Once we have the direction to the light, we can compute the Lambertian color as light color times diffuse [BRDF](https://en.wikipedia.org/wiki/Bidirectional_reflectance_distribution_function) times Lambertian term: * Our diffuse BRDF is *`difMatlColor.rgb / M_PI`* * Our Lambertian term is the dot product *`NdotL`* * Our light color is *`lightColor * vis`* Where *`vis`* is a visibility factor of either *`1.0f`* or *`0.0f`* depending on if our shadow ray determined the light illuminates the pixel. We compute our visibility factor with our utility routine *`shootShadowRay()`*. It turns out that both shadow and ambient occlusion rays are asking the same question (i.e., "is anything between point A and point B?"). That means *`shootShadowRay()`* is identical to the *`shootAoRay()`* used in prior tutorials. (And our miss, closest-hit, and any-hit shaders are also the same.) What Does it Look Like? =============================================================================== That covers the important points of this tutorial. When running, you get the following result: ![Result of running Tutorial 9, after loading the scene "pink_room.fscene"](Tutor09-Output.png) Hopefully, this tutorial demonstrated how to use a slightly more interesting and realistic shading model and how to do simplistic queries to Falcor scene data to shoot shadow rays towards scene-specified lights. When you are ready, continue on to [Tutorial 10](../Tutor10/tutorial10.md.html), shows how to load an [high dynamic range](https://en.wikipedia.org/wiki/High-dynamic-range_imaging) [environment map](https://en.wikipedia.org/wiki/Reflection_mapping) and use a DirectX miss shader to return a background color that varies across the screen.