**DirectX Raytracing, Tutorial 4**
**Creating a ray traced G-buffer**
Our first three [tutorials](../../dxr_tutors.md.html) focused on getting up to speed with
our simple tutorial infrastructure and making simple DirectX rasterization calls (to create
a basic [G-Buffer](https://en.wikipedia.org/wiki/Deferred_shading)). This tutorial finally
moves on to actually spawning some ray tracing with DirectX. To focus on how ray tracing and
rasterization are different, this tutorial builds a G-Buffer identical to the one created
in [Tutorial 3](../Tutor3/tutorial03.md.html).
Our Basic Ray Tracing Render Pipeline
=================================================================================
If you open up *`Tutor04-RayTracedGBuffer.cpp`*, you will find the only change is we swaped out
*`SimpleGBufferPass`* for the new *`RayTracedGBufferPass`* we'll build below. We reuse the
same *`CopyToOutputPass`* we created in [Tutorial 3](../Tutor3/tutorial03.md.html).
~~~~~~~~~~~~~~~~~~~~ C
// Create our rendering pipeline
RenderingPipeline *pipeline = new RenderingPipeline();
pipeline->setPass(0, RayTracedGBufferPass::create());
pipeline->setPass(1, CopyToOutputPass::create());
~~~~~~~~~~~~~~~~~~~~
For those
who skipped the first three tutorials, this code can be read as "create a rendering pipeline
with two components: a pass that generates a ray-traced G-buffer followed by a pass that copies a selectable
buffer onscreen."
Creating and Launching DirectX Raytracing Work
=================================================================================
Start by looking in *`RayTracedGBufferPass.h`*. This should look familiar, as the boilerplate is
nearly identical to that from the *`RenderPasses`* from prior tutorials.
The major difference is in our pass' member variables:
~~~~~~~~~~~~~~~~~~~~ C
RtScene::SharedPtr mpScene; // Falcor scene abstraction
RayLaunch::SharedPtr mpRays; // Encapsulates DirectX Raytracing pass
~~~~~~~~~~~~~~~~~~~~
The *`RtScene`* class encapsultes Falcor's scene representation, with additions for ray tracing. In the rest
of our tutorials, we will use the *`RtScene`* class (which derives from the *`Scene`* class from
[Tutorial 3](../Tutor3/tutorial03.md.html) but adds functionality required for ray tracing).
The *`RayLaunch`* is similar to the *`RasterLaunch`* class from [Tutorial 3](../Tutor3/tutorial03.md.html), except it
exposes methods for setting up all the new HLSL ray tracing shader types and setting their variables.
Initializing our Ray Traced G-Buffer Pass
------------------------------
Our *`RayTracedGBufferPass::initialize()`* looks quite similar to the one for *`SimpleGBufferPass`*:
~~~~~~~~~~~~~~~~~~~~ C
bool RayTracedGBufferPass::initialize(RenderContext::SharedPtr pRenderContext,
ResourceManager::SharedPtr pResManager)
{
// Stash a copy of our resource manager; tell it what shared texture
// resources we need for this pass.
mpResManager = pResManager;
mpResManager->requestTextureResources({"WorldPosition", "WorldNormal",
"MaterialDiffuse", "MaterialSpecRough",
"MaterialExtraParams"});
// Create our wrapper for our DirectX Raytracing launch.
mpRays = RayLaunch::create("rtGBuffer.hlsl", "GBufferRayGen");
mpRays->addMissShader("rtGBuffer.hlsl", "PrimaryMiss");
mpRays->addHitShader("rtGBuffer.hlsl", "PrimaryClosestHit", "PrimaryAnyHit");
// Compile our shaders and pass in our scene
mpRays->compileRayProgram();
mpRays->setScene(mpScene);
return true;
}
~~~~~~~~~~~~~~~~~~~~
A couple important notes:
* The method *`requestTextureResources`* is identical to calling *`requiresTextureResource`* multiple times,
with each of the specified names, simplifying the code somewhat.
* We don't request a Z-buffer, because ray tracing does not generate one by default. (Of course, if desired
you can output a Z-buffer manually.)
We then create our ray tracing wrapper *`mpRays`* by pointing it to our shader file *`rtGBuffer.hlsl`* and
specifying the function where our _ray generation shader_ starts, in this case the function *`GBufferRayGen()`*
in our HLSL file. Because we plan to launch rays from this shader, we also have to specify the _miss shader_
to use, named *`PrimaryMiss()`*, and our _closest-hit_ and _any hit shaders_, named *`PrimaryClosestHit()`* and
*`PrimaryAnyHit()`*.
When ray tracing, we can have multiple ray types. This means we can call *`addMissShader`* multiple times.
The first time it is called, we specify miss shader #0. The second time, we specify miss shader #1. The next,
miss shader #2. Et cetera. Similarly, when called multiple times, *`addHitShader`* specifies hit group #0, 1, 2, etc.
This integer identifier is important, as calling *`TraceRay()`* in HLSL requires you specify the correct IDs.
Launching Ray Tracing and Sending Data to HLSL
------------------------------
Now that we initialized our rendering resources, we can create our ray traced G-buffer. The
*`RayTracedGBufferPass::execute`* pass is somewhat complex, so let's split it into multiple pieces:
~~~~~~~~~~~~~~~~~~~~ C
void RayTracedGBufferPass::execute(RenderContext::SharedPtr pRenderContext)
{
// Color used to clear our G-buffer
vec4 black = vec4(0, 0, 0, 0);
// Load our textures; ask the resource manager to clear them first
// Note: 'auto' type used for brevity; actually Texture::SharedPtr
auto wsPos = mpResManager->getClearedTexture("WorldPosition", black);
auto wsNorm = mpResManager->getClearedTexture("WorldNormal", black);
auto matDif = mpResManager->getClearedTexture("MaterialDiffuse", black);
auto matSpec = mpResManager->getClearedTexture("MaterialSpecRough", black);
auto matExtra = mpResManager->getClearedTexture("MaterialExtraParams", black);
auto matEmit = mpResManager->getClearedTexture("Emissive", black);
~~~~~~~~~~~~~~~~~~~~
First, we need access the textures we'll use to write out our G-buffer. Unlike
in [Tutorials 2](../Tutor2/tutorial02.md.html) [and 3](../Tutor3/tutorial03.md.html), we
do not need to create a framebuffer object. Instead for ray tracing, we'll write
directly into them ([using UAVs](https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/sm5-object-rwtexture2d)).
We also ask our resource manager to clear them to black prior to returning the textures.
~~~~~~~~~~~~~~~~~~~~C
// Pass our background color down to miss shader #0
auto missVars = mpRays->getMissVars(0);
missVars["MissShaderCB"]["gBgColor"] = mBgColor; // Color for background
missVars["gMatDif"] = matDif; // Where to store bg color
~~~~~~~~~~~~~~~~~~~~
Next, we set the HLSL variables for our _miss shader #0_. These variables are scoped
so they are _only_ usable while executing this specific miss shader. This uses the same
syntax we used for raster shaders, except we need to specifically ask (*`getMissVars(0)`*)
for variables for miss shader #0. In this case, if our rays miss we'll store the specified
background color in the *`"MaterialDiffuse"`* texture.
~~~~~~~~~~~~~~~~~~~~C
// Pass down variables for our hit group #0
for (auto pVars : mpRays->getHitVars(0))
{
pVars["gWsPos"] = wsPos;
pVars["gWsNorm"] = wsNorm;
pVars["gMatDif"] = matDif;
pVars["gMatSpec"] = matSpec;
pVars["gMatExtra"] = matExtra;
}
~~~~~~~~~~~~~~~~~~~~
In this snippet, we are setting the HLSL variables for hit group #0 (i.e, the _closest-hit_ and _any-hit_ shaders).
This code is more complex than for miss shaders, since your variables may change for each geometry instance
in your scene. This is the cause of the *`for`* loop, as we loop over all the instances. In this
case, the variables specifed are scoped so they are only visibile by the closest-hit and any-hit shaders executed
while intersecting the specified geometry instance.
Above, we are specifying the output textures *`gWsPos`*, *`gWsNorm`*, *`gMatDif`*, *`gMatSpec`*, and *`gMatExtra`*.
We will store our G=Buffer output in our _closest-hit_ shader, which can thought of as similar to a pixel shader.
~~~~~~~~~~~~~~~~~~~~C
// Launch our ray tracing
mpRays->execute( pRenderContext, mpResManager->getScreenSize() );
}
~~~~~~~~~~~~~~~~~~~~
Finally, we launch our ray tracing pass. *`execute()`* requres the
DirectX context and the number of rays to launch (often dependant on screen resolution).
The DirectX Raytracing HLSL for Our G-Buffer
===============================================================================
Just like when starting to read a C++ program it often makes sense to start
reading at *`main()`*, you should probably start reading HLSL ray tracing code
at the ray generation shader. As specified above during our pass initialization,
our shader's ray generation shader is named *`GBufferRayGen()`*:
~~~~~~~~~~~~~~~~~~~~~C
[shader("raygeneration")]
void GBufferRayGen()
{
// Convert our ray index into a ray direction in world space.
float2 currenPixelLocation = DispatchRaysIndex() + float2(0.5f, 0.5f);
float2 pixelCenter = currenPixelLocation / DispatchRaysDimensions();
float2 ndc = float2(2, -2) * pixelCenter + float2(-1, 1);
float3 rayDir = normalize( ndc.x * gCamera.cameraU +
ndc.y * gCamera.cameraV +
gCamera.cameraW );
// Initialize a ray structure for our ray tracer
RayDesc ray = { gCamera.posW, 0.0f, rayDir, 1e+38f };
// Initialize our ray payload (a per-ray, user-definable structure).
SimplePayload payload = { false };
// Trace our ray
TraceRay(gRtScene, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload );
}
~~~~~~~~~~~~~~~~~~~~~
The first few lines lookup the current pixel location. The number of rays launched (i.e., the parmaeter
passed to *`mpRays->execute`*) is accessible via HLSL's *`DispatchRayDimensions()`* intrinsic, and
the current pixel being processed is given by *`DispatchRaysIndex()`*. After converting the pixel location
into a value in [-1...1], we use the current camera parameters to generate a world-space vector that
describes the ray from the camera through this pixel. The camera variable *`gCamera`* is automatically
passed down by our framework.
We then create a ray using the DirectX data type *`RayDesc`*. The first entry in the structure (named *`Origin`*)
specifies where the ray starts. The second entry (named *`TMin`*) specifies the minimum distance we need to travel
along our ray before reporting a valid hit. The third entry (named *`Direction`*) specifies the ray directions.
The fourth entry (named *`TMax`*) specifies the maximum distance along our ray to return a valid hit.
When tracing rays in DirectX, there is a user-definable _payload_ structure that accompanies each ray and allows
you to stash temporary data during ray tracing. In this example shader this payload is unused, so is not particularly
important.
Finally, we call *`TraceRay()`* to launch our ray with the following parameters:
* The first parameter, *`gRtScene`*, is the ray acceleration structure. Our framework populates this HLSL
variable for you automatically.
* Second, we specify flags to control ray behavior. Here, we use no special flags.
* Third, we specify an instance mask. This allows you to skip some instances during traversal. A value *`0xFF`*
tests all geometry in the scene (skipping none).
* Fourth, we specify which hit group to use. In the DXR spec, this is a somewhat complex computation. For most
simple usage, this corresponds directly to the ID of your hit group, as determined in *`RayTracedGBufferPass::initialize`*.
(In our case, there is only one hit group so this takes a value of 0).
* Fifth, we specify how many hit groups there are. In our case this is 1. (Our framework computes this
and stores it in the varaible *`hitProgramCount`*, if you wish to avoid hard coding this value.)
* Sixth, we specify which miss shader to use. This directly corresponds to the miss shader ID determined in
*`RayTracedGBufferPass::initialize`*.
* Seventh, we specify the ray to trace.
* Finally, we specify the payload data structure to use while tracing this ray.
After calling *`TraceRay()`*, some processing happens internally. The next shader
executed depends on the geometry, ray direction, and internal data structures. _Any-hit shaders_ get launched
for some potential hits, _closest-hit shaders_ get launched when you have identified the single closest hit
along the ray, and _miss shaders_ get launched if no valid hit occurs.
~~~~~~~~~~~~~~~~~~~~~C
// A dummy payload for this simple ray; never used
struct SimplePayload {
bool dummyValue;
};
// Our miss shader's variables
cbuffer MissShaderCB {
float3 gBgColor;
};
// The output textures. See bindings in C++ code.
// -> gMatDif is visible in the miss shader
// -> All textures are visible in the hit shaders
RWTexture2D