**DirectX Raytracing, Tutorial 10** **Using a miss shader to index into a high dynamic range light probe** In [Tutorial 9](../Tutor9/tutorial09.md.html), we added a Lambertian shading model with ray traced shadows. Before moving on to global illumination, we want to load and render with [environment map](https://en.wikipedia.org/wiki/Reflection_mapping) light probes, which easily allow much more complex lighting. For this tutorial, we assume our environment maps are parameterized using a [lat-long projection](https://en.wikipedia.org/wiki/Equirectangular_projection), for instance the free environment maps [available here](http://www.hdrlabs.com/sibl/archive.html). I encourage you to use [high dynamic range](https://en.wikipedia.org/wiki/High-dynamic-range_imaging) environments (e.g., with a .hdr filename), since they provide more realistic lighting with larger variations in light intensity. Environment Maps in our Tutorials ================================================================================= Unlike most of the functionality we have added in our tutorials, using an environment map does not necessarily add a new *`RenderPass`*. Instead, it simply modifies existing ones, changing the behavior of our miss shaders as rays hit a more complex environment. In more complex pipelines, *_every_* *`RenderPass`* may need to use the environment map. To simplify this tutorial to focus on exactly how environment maps are used, we add an environment to our *`ThinLensGBufferPass`* from [Tutorial 8](../Tutor8/tutorial08.md.html). Instead of storing a constant background color to our G-buffer when we hit the background, we will now store a color from our environment map. To do this, in *`Tutor10-LightProbeEnvironmentMap.cpp`*, we swap out the *`ThinLensGBufferPass`* for our new *`LightProbeGBufferPass`*, which handles jittered antialiasing, a thin lens camera model, and rendering an environment mapped background all in a single, more complex *`RenderPass`*. Setting up a Render Pass For Environment Mapping ================================================================================= Look in *`LightProbeGBufferPass.h`*. This header looks almost identical to the *`ThinLensGBufferPass.h`* except for the following addition: ~~~~~~~~~~~~~~~~~~~~C bool usesEnvironmentMap() override { return true; } ~~~~~~~~~~~~~~~~~~~~ The method *`usesEnvironmentMap()`* overrides an inherited method from *`RenderPass`* and tells the pipeline that our passes plan to use an environment light probe. Our tutorial framework does a couple things with this information. First, it adds a control to the GUI allowing you to dynamically load new environment maps. Second, it tells our *`ResourceManager`* to allocate a default environment map texture. Loading an Environment Map ================================================================================= Falcor and our tutorial framework have various built-in texture loading utilites. After adding the *`usesEnvironmentMap()`* override above, you can dynamically load new light probes using the GUI. However, to specify the environment map loaded on initialization, we add the following line in *`LightProbeGBufferPass::initialize()`*: ~~~~~~~~~~~~~~~~~~~~C mpResManager->updateEnvironmentMap( "MonValley_G_DirtRoad_3k.hdr" ); ~~~~~~~~~~~~~~~~~~~~ This tells the resource manager to creates an environment map and initialize it with the texture in *`"MonValley_G_DirtRoad_3k.hdr"`*. Note that if *`updateEnvironmentMap()`* is called multiple times (e.g., in different *`RenderPass::initialize()`* methods), the last one called wins. Falcor uses [FreeImage](http://freeimage.sourceforge.net), so you can load any [image formats it supports](http://freeimage.sourceforge.net/features.html), including .hdr, .exr, .dds, .png, and .jpg (among many others). Sending an Evironment Map to a Miss Shader ================================================================================= Look down in *`LightProbeGBufferPass::execute()`* and you will see the following new lines related to environment mapping: ~~~~~~~~~~~~~~~~~~~~C // Pass our environment map down to our miss shader auto missVars = mpRays->getMissVars(0); missVars["gEnvMap"] = mpResManager->getTexture(ResourceManager::kEnvironmentMap); missVars["gMatDif"] = mpResManager->getTexture("MaterialDiffuse"); ~~~~~~~~~~~~~~~~~~~~ This sends down two variables (*`gEnvMap`* and *`gMatDif`*) for access in miss shader #0. Note: these variables will *_only_* be accessible in miss shader #0. (However, since the code *_also_* sets *`"gMatDif"`* as a hit shader variable later on, if is also accessible to hit group #0). Using an Evironment Map in our Miss Shader ================================================================================= Now that we loaded an environment map and sent it to our miss shader, we can index into it as follows (see *`lightProbeGBuffer.rt.hlsl`*): ~~~~~~~~~~~~~~~~~~~~C // The texture containing our environment map Texture2D gEnvMap; [shader("miss")] void PrimaryMiss() { float2 texDims; gEnvMap.GetDimensions( texDims.x, texDims.y ); float2 uv = wsVectorToLatLong( WorldRayDirection() ); gMatDif[ DispatchRaysIndex() ] = gEnvMap[ uint2(uv * texDims) ]; } ~~~~~~~~~~~~~~~~~~~~ To start, we need to declare out HLSL texture *`gEnvMap`* to load our texture. The first two lines of *`PrimaryMiss()`* query the DirectX texture object for its size. The third line converts our ray direction into a (u,v) coordinate to lookup a color in light probe. Finally, we grab the color from our environment map and store it into our output buffer in the appropriate location for the current pixel. Converting from a vector to a lat-long projection is fairly straightforward, using the following code ~~~~~~~~~~~~~~~~~~~~C float2 wsVectorToLatLong(float3 dir) { float3 p = normalize(dir); float u = (1.f + atan2(p.x, -p.z) * M_1_PI) * 0.5f; float v = acos(p.y) * M_1_PI; return float2(u, v); } ~~~~~~~~~~~~~~~~~~~~ Note: The texture lookup code in *`PrimaryMiss()`* uses nearest-neighbor indexing into the environment map. Instead of calling *`gEnvMap[...]`*, other DirectX texture lookups, like *`gEnvMap.Sample(gTexSampler, uv)`*, can be used if you prefer to use linear texture interpolation. Of course, this requires defining a *`TextureSampler`* *`gTexSampler;`* and passing it in from your C++ code. It is fairly straightforward to use a more complex texture sampler, but this is not in this tutorial: ~~~~~~~~~~~~~~~~~~~~C // Need to do stuff to configure mySampler as desired. Sampler::SharedPtr mySampler = Sampler::create( ... ); missVars["gTexSampler"] = mySampler; ~~~~~~~~~~~~~~~~~~~~ What Does it Look Like? =============================================================================== That covers the important points of this tutorial. When running, you get the following result, though you need to rotate the camera a bit to see the background: ![Result of running Tutorial 10, after loading the scene "pink_room.fscene"](Tutor10-Output.png) Hopefully, this tutorial demonstrated how to use load an environment map, send it to your HLSL shader, and index into it in your miss shader. When you are ready, continue on to [Tutorial 11](../Tutor11/tutorial11.md.html), which takes the Lambertian material shading pass from [Tutorial 9](../Tutor9/tutorial09.md.html) and shoots only a simple, random shadow ray for each pixel. This reduces rendering cost in scenes with many lights, but adds some noise in exchange.