A Gentle Introduction to DirectX Raytracing 11

引言

本节我们将减少光线计数,将阴影射线拍摄到每个像素仅一个随机光。

In Tutorial 9, 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.

教程 9中,我们添加了一个 Lambertian 着色模型,该模型将阴影光线追踪到每个灯光。虽然准确,但有很多原因为每个灯光跟踪阴影光线可能是一个糟糕的选择。一个关键的考虑因素是成本会随着场景中的额外灯光而线性增加。

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 on a very simple quantity (the Lambertian term with direct illumination).

在本教程中,我们将随机选择一个灯光来测试每个像素。这是使用显式直接照明进行路径跟踪时的常用策略,但更重要的是在一个非常简单的量(具有直接照明的 Lambertian 项)上 引入了蒙特卡洛积分的思想。

Changes to our C++ Render Pass

There is a single difference between the C++ code for this tutorial and Tutorial 9. 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.

本教程的 C++ 代码与教程 9 之间只有一个区别。由于我们的着色器需要一个随机数生成器,我们传递一个不断变化的“帧计数”,以允许我们在每一帧重新植入我们的伪随机数生成器。

Our header DiffuseOneShadowRayPass.h declares:

我们的头文件 DiffuseOneShadowRayPass.h 声明:

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.

这是一个计数器,它递增每一帧并且只播种我们的每像素随机数。因为我们不想要与 ThinLensGBufferPass(或任何其他RenderPasses)相同的随机数生成器,所以我们在每次传递中初始化 mFrameCount 为不同的值。我选择了一些任意的十六进制数字,但是这些值没有什么特别之处,除了每个通道都是唯一初始化的。

We also pass this mFrameCount down to our shader so we can use it:

我们还将它传递 mFrameCount 给我们的着色器,以便我们可以使用它:

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:

打开 diffusePlus1Shadow.rt.hlsl。我们的第一个变化是我们使用输入帧计数初始化一个伪随机数生成器:

[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:

然后当我们着色时,我们不再循环我们的灯光:

// 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, the contribution of each random sample should be divided by the probability of selecting that sample.

最后一个考虑:因为我们只使用一个灯光进行着色,在有N个灯光的场景中,我们的随机采样会太暗(N倍)。在蒙特卡洛积分中,每个随机样本的贡献应该除以选择该样本的概率。

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:

我们的均匀随机光选择(上图)以概率 对每个光进行采样 1.0f / N,因此当使用随机光进行着色时,我们应该将该光的贡献乘以 N。如果使用更智能的光采样方案,则必须计算更复杂的概率。这意味着我们的着色计算发生了变化:

// 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:

这涵盖了本教程的重点。运行时,您会得到以下结果:

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. However, if you disable temporal accumulation, you will notice the shadows are significantly noisier.

本教程默认在启用时间累积的情况下运行,因此如果您转身离开片刻,您可能看不出与教程 9的区别。但是,如果您禁用时间累积,您会注意到阴影明显更加嘈杂。

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) 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, which adds one bounce diffuse global illumination.

准备好后,继续教程 12,它添加了一个反弹漫反射全局照明。