A Gentle Introduction to DirectX Raytracing 9


本节的主题是 Lambertian 着色,向场景中的每盏灯拍摄一条阴影射线。

In Tutorial 8, we added random camera jitter over a camera lens to introduce dynamic depth of field, which is typically hard to achieve using rasterization.


This tutorial finally gets rid of the ambient occlusion shading introduced in Tutorial 5 and replaces it with a simple diffuse shading model (also known as a Lambertian 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 artifacts for decades).

本教程最终摆脱了 教程 5 中引入的环境光遮蔽着色,并将其替换为简单的漫反射着色模型(也称为Lambertian材质模型)。除了使用 Lambertian 着色之外,我们还将阴影光线追踪到场景中的每一种光线。这为我们提供了像素级精度的硬阴影,这在光栅化中仍然是一个悬而未决的问题(几十年来一直受到 阴影贴图 伪影的困扰)。

Our Lambertian Rendering Pipeline

If you open up Tutor09-LambertianPlusShadows.cpp, you will find our new pipeline combines a the ThinLensGBufferPass from Tutorial 8 and our new LambertianPlusShadowPass.

如果您打开 Tutor09-LambertianPlusShadows.cpp,您会发现我们的新管道结合了教程8 中的 ThinLensGBufferPass 和我们新的 LambertianPlusShadowPass

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

就像我们新的 LambertianPlusShadowPass 取代的环境光遮蔽一样,我们将从G缓冲区读取,计算阴影和光线追踪阴影,并将结果输出到显示的图像 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.

继续查看 LambertianPlusShadowPass.h。此头文件完全由您在之前的教程中看到的标准样板和 RayLaunch 包装器组成。

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.

同样,查看 LambertianPlusShadowPass::initialize() 显示了一组熟悉的功能:从我们的 G 缓冲区传递请求适当的输入,并适当地设置漫反射 RayLaunch 包装器的光线生成、未命中、最接近命中和任意命中着色器。

Looking at LambertianPlusShadowPass::execute() is also straightforward, though it might be worth pointing out that in:

看看 LambertianPlusShadowPass::execute() 也很简单,尽管可能值得指出这一点:

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.

getMinTDist() 从 GUI 返回一个可选参数,该参数指定阴影光线应偏移多远以避免由于数值精度而产生的自交。

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():

在我们的 HLSL 着色器文件 lambertianPlusShadows.rt.hlsl 中,让我们从射线生成着色器 LambertShadowsRayGen() 开始。:

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.

此着色器的大部分开头都与我们的环境光遮蔽着色器相同。但是,我们确实从 G 缓冲区加载了漫反射材料的颜色。在我们的实现中,G 缓冲区的漫反射颜色有两个用途:对于表面,它存储其 Lambertian 颜色;对于背景像素,它存储背景色。

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.

因此,如果像素落在背景上,我们将最终输出的颜色 shadeColor 初始化为漫反射色,否则我们将着色颜色初始化为黑色。

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

Lambertian 着色器的下一个重要部分是我们在场景灯光上的循环。此处 gLightsCount是一个自动设置的 Falcor 全局变量,用于指定当前加载的场景有多少灯光。我们需要循环所有这些光,并累积光对当前像素的贡献(可能被阴影)。

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.)

为此,我们需要更多关于光属性的数据。为了避免深入研究Falcor场景表示的规格,我写了一个简单的助手 getLightData() 来提取这些光数据。(作为参考,此函数在标头中可用,并在教程的 Visual Studio 项目中提供 HLSL 帮助程序函数。

Once we have the direction to the light, we can compute the Lambertian color as light color times diffuse BRDF 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

一旦我们有了光的方向,我们就可以计算出朗伯色作为光色乘以漫反射 BRDF 乘以朗伯子项:

  • 我们的漫反射 BRDF 是 difMatlColor.rgb / M_PI
  • 我们的 Lambertian term 是点积 NdotL
  • 我们的光色是 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.

其中 vis0.0f1.0f 的可见性因子,具体取决于我们的阴影光线是否决定了光线照亮像素。

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.)

我们用我们的效用例程 shootShadowRay() 来计算我们的可见性因子。事实证明,阴影和环境光遮蔽射线都在问同样的问题(即,“A 点和 B 点之间有什么东西吗?”)。这意味着 shootShadowRay() 与之前教程中使用的 shootAoRay() 相同。(我们的未命中、最接近命中和任意命中着色器也是相同的。)

What Does it Look Like?

That covers the important points of this tutorial. When running, you get the following result:


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.

希望本教程演示了如何使用稍微有趣和逼真的着色模型,以及如何对 Falcor 场景数据进行简单查询,以将阴影光线射向场景指定的灯光。

When you are ready, continue on to Tutorial 10, shows how to load an high dynamic range environment map and use a DirectX miss shader to return a background color that varies across the screen.

准备就绪后,请继续学习 教程10,了解如何加载 高动态范围环境贴图,并使用 DirectX 未命中着色器返回随屏幕变化的背景色。