**DirectX Raytracing, Tutorial 12** **Extending our Lambertian shading to add one-bounce global illumination** In [Tutorial 11](../Tutor11/tutorial11.md.html), we changed our Lambertian shading pass to stochastically shoot exactly one shadow ray per pixel, rather than shooting one ray per scene light. This demonstrates a very simplistic [Monte Carlo integration](https://en.wikipedia.org/wiki/Monte_Carlo_integration) process, which we will extend in this tutorial to compute one-bounce [diffuse](https://en.wikipedia.org/wiki/Diffuse_reflection) [global illumination](https://en.wikipedia.org/wiki/Global_illumination). Changes to our C++ Render Pass ================================================================================= As in most of the later tutorials, differences in the C++ *`RenderPass`* are fairly minor. In our new *`SimpleDiffuseGIPass.h`*, we now add the following methods and variables: ~~~~~~~~~~~~~~~~~~~~C // So we can use an environment map to illuminate the scene bool usesEnvironmentMap() override { return true; } // Some user controls allowing the UI to switch on/off certain rays bool mDoIndirectGI = true; bool mDoDirectShadows = true; ~~~~~~~~~~~~~~~~~~~~ The major change in *`SimpleDiffuseGIPass::initialize()`* occurs because we now plan to shoot *_two_* types of rays: *_shadow rays_* and *_indirect bounce rays_*. This means, we need to define both types when we initialize our *`RayLaunce`* wrapper class: ~~~~~~~~~~~~~~~~~~~~C // Create our ray tracing wrapper and define the ray generation shader std::string shaderFile = "simpleDiffuseGI.rt.hlsl"; mpRays = RayLaunch::create(shaderFile, "DiffuseRayGen"); // Define our miss shaders mpRays->addMissShader(shaderFile, "ShadowMiss"); // Miss shader #0 mpRays->addMissShader(shaderFile, "IndirectMiss"); // Miss shader #1 // Define our hit groups (first is #0, second is #1) mpRays->addHitShader(shaderFile, "ShadowClosest", "ShadowAny"); mpRays->addHitShader(shaderFile, "IndirectClosest", "IndirectAny"); // Finalize pass; compile and attach our scene mpRays->compileRayProgram(); if (mpScene) mpRays->setScene(mpScene); ~~~~~~~~~~~~~~~~~~~~ The only other changes our C++ code includes sending additional variables down to our DirectX shaders, see *`SimpleDiffuseGIPass::execute()`*: ~~~~~~~~~~~~~~~~~~~~C // Send down our optional UI parameters auto rayGenVars = mpRays->getRayGenVars(); rayGenVars["RayGenCB"]["gDoIndirectGI"] = mDoIndirectGI; rayGenVars["RayGenCB"]["gDirectShadow"] = mDoDirectShadows; // Set an environment map for indirect rays that miss geometry auto missVars = mpRays->getMissVars(1); // Indirect rays use miss shader #1 missVars["gEnvMap"] = mpResManager->getTexture(ResourceManager::kEnvironmentMap); ~~~~~~~~~~~~~~~~~~~~ HLSL Changes for Diffuse Global Illumination ================================================================================= Open up *`simpleDiffuseGI.rt.hlsl`*. Our first change is we define a new set of shaders for our indirect ray. The miss shader is essentially copied from our environment map lookup in [Tutorial 10](../Tutor10/tutorial10.md.html) and the any-hit shader is our standard any-hit shader that performs [alpha testing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing#Alpha_testing): ~~~~~~~~~~~~~~~~~~~~C // Identical to our environment map code in Tutoral 10 [shader("miss")] void IndirectMiss(inout IndirectPayload rayData) { float2 dims; gEnvMap.GetDimensions(dims.x, dims.y); float2 uv = wsVectorToLatLong(WorldRayDirection()); rayData.color = gEnvMap[uint2(uv * dims)].rgb; } // Identical to any hit shaders for most other rays we've defined [shader("anyhit")] void IndirectAny(inout IndirectPayload rayData, BuiltinIntersectionAttribs attribs) { if (alphaTestFails(attribs)) IgnoreHit(); } ~~~~~~~~~~~~~~~~~~~~ Our new indirect ray's closest hit shader is somewhat more complex, but essentially reproduces the direct illumination code from [Tutorial 11](../Tutor11/tutorial11.md.html). Basically: when we our bounce rays hit another surface, we do simple Lambertian shading at the hit points, using the same math we previously defined for primary hits. To save cost, we only shoot one shadow ray from each hit (rather than looping through all scene lights): ~~~~~~~~~~~~~~~~~~~~C [shader("closesthit")] void IndirectClosest(inout IndirectPayload rayData, BuiltinIntersectionAttribs attribs) { // Extract data from our scene description to allow shading this point ShadingData shadeData = getHitShadingData( attribs ); // Pick a random light from our scene to shoot a shadow ray towards int lightToSample = min( int(gLightsCount * nextRand(rayData.rndSeed)), gLightsCount - 1 ); // 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, shadeData.posW, toLight, lightColor, distToLight); // Compute our Lambertion term (NL dot L) float NdotL = saturate(dot(shadeData.N, toLight)); // Shoot our ray. Return 1.0 for lit, 0.0 for shadowed float vis = shootShadowRay(shadeData.posW, toLight, RayTMin(), distToLight); // Return the shaded Lambertian shading at this indirect hit point rayData.color = vis * NdotL * lightColor * shadeData.diffuse / M_PI; } ~~~~~~~~~~~~~~~~~~~~ We also add a utility function, similar to *`shootShadowRay`* that encapsulates the process of shooting an indirect ray and returning the incident color in the selected direction: ~~~~~~~~~~~~~~~~~~~~C struct IndirectPayload { float3 color; // The color in the ray's direction uint rndSeed; // Our current random seed }; float3 shootIndirectRay(float3 orig, float3 dir, float minT, uint seed) { // Setup shadow ray and the default ray payload RayDesc rayColor = { orig, minT, dir, 1.0e+38f }; IndirectPayload payload = { float3(0,0,0), seed }; // Trace our indirect ray. Use hit group #1 and miss shader #1 (of 2) TraceRay(gRtScene, 0, 0xFF, 1, 2, 1, rayColor, payload); return payload.color; } ~~~~~~~~~~~~~~~~~~~~ Changes to our Ray Generation Shader to Add Indirect Lighting ------------------------------------------------------------------------------ In our ray generation shader from [Tutorial 11](../Tutor11/tutorial11.md.html), we essentially have the following code: ~~~~~~~~~~~~~~~~~~~~C [shader("raygeneration")] void SimpleDiffuseGIRayGen() { ... // Shoot a shadow ray for our direct lighting float vis = shootShadowRay(worldPos.xyz, toLight, gMinT, distToLight); shadeColor += vis * NdotL * lightColor * difMatlColor.rgb / M_PI; ... } ~~~~~~~~~~~~~~~~~~~~ After adding our color for *_direct illumination_* we need to add our color computations for *`indirect illumination`*: ~~~~~~~~~~~~~~~~~~~~C ... // Pick a random direction for our indirect ray (in a cosine distribution) float3 bounceDir = getCosHemisphereSample(randSeed, worldNorm.xyz); // Get NdotL for our selected ray direction float NdotL = saturate(dot(worldNorm.xyz, bounceDir)); // Shoot our indirect global illumination ray float3 bounceColor = shootIndirectRay(worldPos.xyz, bounceDir, gMinT, randSeed); // Probability of sampling a cosine lobe float sampleProb = NdotL / M_PI; // Do Monte Carlo integration of indirect light (i.e., rendering equation) // -> This is: (NdotL * incoming light * BRDF) / probability-of-sample shadeColor += (NdotL * bounceColor * difMatlColor.rgb / M_PI) / sampleProb; ... ~~~~~~~~~~~~~~~~~~~~ This is doing [Monte Carlo integration](https://en.wikipedia.org/wiki/Monte_Carlo_integration) of the [rendering equation](https://en.wikipedia.org/wiki/Rendering_equation) for just the indirect component of the lighting. In this tutorial, I explicitly define the sampling probability for our random ray. In more efficient code, you would obviously cancel the duplicate terms. But for novices (and even experts), cancelling terms is an easy way to forget the underlying math. Once you forget the math, you might decide to select your ray differently and forget to update the accumulation term with a new sampling probability. Almost every rendering researcher and engineer has stories of "magic coefficients" used to get a good results in some code base--forgetting the underlying mathematics is usually the cause of these spurious magic numbers. What Does it Look Like? =============================================================================== That covers the important points of this tutorial. When running, you get the following result: ![Result of running Tutorial 12, after loading the scene "pink_room.fscene"](Tutor12-Output.png) With this tutorial, you now have one-bounce diffuse global illumination (and the UI allows you to turn on and off direct and indirect light for comparison). We're getting close to an interesting renderer. However, before continuing on to add more complex materials and multi-bounce global illumination, [Tutorial 13](../Tutor13/tutorial13.md.html) adds a detour to handle [high dynamic range](https://en.wikipedia.org/wiki/High-dynamic-range_imaging) outputs. When rendering simple materials and lighting, displaying colors in a monitor's arbitrary [0...1] range is typically sufficient. However, adding multiple bounces and HDR environment maps starts giving output colors that no longer fit in that range. This can give very dark or blown out images. In order to address this, [Tutorial 13](../Tutor13/tutorial13.md.html) allows you to turn on [tone mapping](https://en.wikipedia.org/wiki/Tone_mapping) to map a wider range of colors onto your monitor.