A Gentle Introduction to DirectX Raytracing 2

引言

本节是基础知识,使用 HLSL 像素着色器创建简单的光栅全屏通道

As discussed in our tutorial introduction, our goal is to provide a simple infrastructure for getting a DirectX Raytracing application up and running without digging around in low-level API specification documents. Tutorial 2 continues with our sequence covering some infrastructure basics before we get to the meat of implementing a path tracer. If you wish to move on to a tutorial with actual DirectX Raytracing programming, jump ahead to Tutorial 4.

正如我们的教程简介中所讨论的,我们的目标是提供一个简单的基础结构来启动和运行 DirectX Raytracing 应用程序,而无需在底层 API 规范文档中进行挖掘。教程 2 继续介绍一些基础结构和基本知识,然后再介绍如何实现路径跟踪器。如果您希望继续学习包含实际 DirectX 光线追踪编程的教程,请跳到教程 4.

Adding a More Complex RenderPass

Tutorial 1 showed you how to get a basic window open and inset a simplistic RenderPass to clear your screen to a user-controllable color. This tutorial shows how to setup and use a more complex rasterization pass with a programmable HLSL shader.

教程 1向您展示了如何打开一个基本窗口并加入一个简单的 RenderPass 让用户调整屏幕颜色。本教程介绍如何设置和使用具有可编程 HLSL 着色器的更复杂的栅格化通道。

The specific shader is not particularly important, rather the point of this tutorial is to demonstrate the wrappers we have for encapsulating programmable shaders of various kinds and how you interact and pass parameters to these shaders from your C++ based RenderPasses.

特定的着色器并不是特别重要,但本教程的重点是演示我们用于封装各种可编程着色器,以及如何从使用 C++ RenderPasses 交互并将参数传递到这些着色器。

If you open up Tutor02-SimpleRasterShader.cpp, you will find it looks remarkably similar to the main program from Tutorial 1. The key difference is in what RenderPass we add to our pipeline:

如果您打开 Tutor02-SimpleRasterShader.cpp,您会发现它看起来与教程 1 中的主程序非常相似。关键的区别在于我们添加到管道中的 RenderPass

// Create our rendering pipeline
RenderingPipeline *pipeline = new RenderingPipeline();
pipeline->setPass(0, SinusoidRasterPass::create());

In this tutorial, we simply swap out the ConstantColorPass for our more complex SinusoidRasterPass. The key files to look at are then:

  • Passes\SinusoidRasterPass.h
  • Passes\SinusoidRasterPass.cpp
  • Data\Tutorial02\sinusoid.ps.hlsl

在本教程中,我们只需将 ConstantColorPass 换成更复杂的 SinusoidRasterPass。然后,要查看的关键文件是:

  • Passes\SinusoidRasterPass.h
  • Passes\SinusoidRasterPass.cpp
  • Data\Tutorial02\sinusoid.ps.hlsl

Looking at the SinusoidRasterPass.h header, you will largely see the same class declaration boilerplate from the ConstantColorPass, and in fact the only differences are in the member variables:

查看 SinusoidRasterPass.h 头文件,您会在很大程度上看到 ConstantColorPass 的相同类声明样板,实际上唯一的区别在于成员变量:

// Internal pass state
FullscreenLaunch::SharedPtr mpSinusoidPass; ///< Our accumulation shader state
GraphicsState::SharedPtr mpGfxState; ///< Our graphics pipeline state
uint32_t mFrameCount = 0; ///< A frame counter to let our sinusoid animate
float mScaleValue = 0.1f; ///< A scale value for our sinusoid

For now ignore mFrameCount and mScaleValue, which are parameters to control the image displayed by our shader.

现在忽略 mFrameCountmScaleValue,它们是控制着色器显示的图像的参数。

The GraphicsState is a Falcor class that wraps up the pipeline state of a DirectX rasterization pipeline. This includes things like culling, depth testing, blending, rasterization parameters, etc. For our ray tracing tutorials, we only need a simple default graphics state, so the key to remember is we need some graphics state to setup a DirectX pipeline. Don’t worry too much about what settings it controls.

GraphicsState 是一个 Falcor 类,用于封装 DirectX 栅格化管道的管道状态。它包括剔除、深度测试、混合、栅格化参数等。对于光线追踪教程,我们只需要一个简单的默认图形状态,因此要记住的关键是我们需要一些图形状态来设置 DirectX 管道。不要太担心它控制什么设置。

The FullscreenLaunch class is an abstraction in the SharedUtils\ directory that easily allows you to launch very simple full-screen rasterization draw calls. Similar abstractions, RasterLaunch and RayLaunch, exist for drawing more complex raster and ray calls using loaded scene geometry. We’ll use those in future tutorials, and they behave quite similar to our FullscreenLaunch.

FullscreenLaunch 类是 SharedUtils\ 目录中的一个抽象,它允许您轻松启动非常简单的全屏光栅化绘制调用。类似的抽象如 RasterLaunchRayLaunch,存在使用加载的场景几何图形绘制更复杂的栅格和光线调用。我们将在以后的教程中使用它们,它们的行为与我们的 FullscreenLaunch 非常相似。

Defining the SinusoidRasterPass

As in the case of our simpler ConstantColorPass, we need to define the initialize, renderGui, and execute methods. Looking in SinusoidRasterPass.cpp, the renderGui should be self explanatory:

就像我们简单的 ConstantColorPass 一样,我们需要定义 initializerenderGuiexecute 方法。在 SinusoidRasterPass.cpprenderGui 应该是不言自明的:

pGui->addFloatVar("Sin multiplier", mScaleValue, 0.0f, 1.0f, 0.00001f);

It simply adds a GUI widget that allows control over a single floating point value, allowing variations in the range [0…1] in increments of 0.00001.

它只是添加了一个 GUI 小部件,允许控制单个浮点值,允许从 0 到 1 以 0.00001 为增量的变化。

Our initialize pass adds two new lines that were not part of ConstantColorPass:

我们的 initialize 传递添加了两行不属于 ConstantColorPass 的新行:

mpGfxState = GraphicsState::create();
mpSinusoidPass = FullscreenLaunch::create( "Tutorial02\\sinusoid.ps.hlsl" );

The first creates a default DirectX raster pipeline state we’ll use when rendering. The second line says we’ll be doing a full-screen draw call using the specified HLSL shader. Falcor’s full-screen pass uses a default vertex shader, so you only need to specify a pixel shader.

第一行创建我们将在渲染时使用的默认 DirectX 栅格管道状态。第二行表示我们将使用指定的 HLSL 着色器执行全屏绘制调用。Falcor 的全屏通道使用默认顶点着色器,因此您只需指定像素着色器即可。

The execute method is more complex, so let’s step through the entire function:

execute 方法更复杂,因此让我们逐步执行整个函数:

void SinusoidRasterPass::execute(RenderContext::SharedPtr pRenderContext)
{
// Create a framebuffer object to render into.
Fbo::SharedPtr outputFbo =
mpResManager->createManagedFbo({ ResourceManager::kOutputChannel });
mpGfxState->setFbo(outputFbo);

// Set shader parameters for our shader
auto shaderVars = mpSinusoidPass->getVars();
shaderVars["PerFrameCB"]["gFrameCount"] = mFrameCount++;
shaderVars["PerFrameCB"]["gMultValue"] = mScaleValue;

// Execute our shader
mpSinusoidPass->execute(pRenderContext, mpGfxState);
}

In this case, we’re executing a raster shader. This means we need a framebuffer to draw into, and we can’t simply clear our texture (as we did in ConstantColorPass).

在本例中,我们将执行栅格着色器。这意味着我们需要一个帧缓冲器来绘制,并且我们不能简单地清除纹理(就像我们在 ConstantColorPass 中所做的那样)。

Our first line asks our ResourceManager to create a framebuffer for us, and to bind the kOutputChannel as the framebuffer object’s color channel. Ideally, you would not create a new framebuffer each frame, but this is left as an exercise for more advanced readers. We then update our DirectX pipeline state to send results to this new FBO when we run our shader.

我们的第一行要求我们的 ResourceManager 为我们创建一个帧缓冲器,并将 kOutputChannel 绑定为帧缓冲器对象的颜色通道。理想情况下,您无需为每个帧都创建一个新的帧缓冲器,但这留给更高级的读者作为练习。然后,我们更新 DirectX 管道状态,以便在运行着色器时将结果发送到此新 FBO。

// Create a framebuffer object to render to.  Done here once per frame for simplicity, not performance.
// This function allows us provide a list of managed texture names, which get combined into an FBO
Fbo::SharedPtr outputFbo = mpResManager->createManagedFbo({ ResourceManager::kOutputChannel });

The next three lines set shader parameters in the sinusoid.ps.hlsl shader. The first line gets an object that can access and transfer data to the variables in your shader. The next two lines have very simple syntax: on the left of the assignment operator you specify the variable in your shader, on the right is the value you want to transfer to HLSL. The assignment operator is overloaded and (in a Debug build) will do some basic type checking (at runtime) to ensure the value is the right type to assign to the specified HLSL variable.

接下来的三行在 sinusoid.ps.hlsl 着色器中设置着色器参数。第一行获取一个对象,该对象可以访问着色器中的变量并将其传输到着色器中的变量。接下来的两行则是非常简单的语法:在赋值运算符的左侧,在着色器中指定变量,右侧是要传输到 HLSL 的值。赋值运算符已重载,并且(在调试版本中)将执行一些基本类型检查(在运行时),以确保该值是分配给指定 HLSL 变量的正确类型。

(译注:这段不是很懂,我的理解大概就是说这里重载了等于符号可以直接赋值。)

// Set shader parameters.  PerFrameCB is a named constant buffer in our HLSL shader
auto shaderVars = mpSinusoidPass->getVars();

The syntax shaderVars["PerFrameCB"]["gMultValue"] = mScaleValue reads as: in the specified shader, find the variable gMultValue in the constant buffer PerFrameCB and set the value to mScaleValue. This is a very simple and clear assignment between CPU and GPU variables. It is not the most efficient way to update shader values, but for the purposes of these tutorials (and most of my research prototypes), this overhead is not significant enough to favor more efficient, but cryptic, data transfers.

语法 shaderVars["PerFrameCB"]["gMultValue"] = mScaleValue 读作:在指定的着色器中,在常量缓冲区 PerFrameCB 中找到变量 gMultValue 并将该值设置为 mScaleValue。这是 CPU 和 GPU 变量之间非常简单明了的分配。这不是更新着色器值的最有效方法,但对于这些教程(以及我的大多数研究原型)的目的,这种开销还不足以支持更有效但更隐晦的数据传输。

shaderVars["PerFrameCB"]["gFrameCount"] = mFrameCount++;
shaderVars["PerFrameCB"]["gMultValue"] = mScaleValue;

Finally, we execute our full-screen shader. This requires two parameters: the current DirectX render context and the graphics state to use for rendering.

最后,我们执行全屏着色器。这需要两个参数:当前 DirectX 呈现上下文和用于呈现的图形状态。

// Execute our shader
mpSinusoidPass->execute(pRenderContext, mpGfxState);

Interacting with the Sinusoidal HLSL Shader

The final step to understanding this tutorial is to look at our GPU shader in sinusoid.ps.hlsl:

本教程的最后一步是查看 sinusoid.ps.hlsl 中的 GPU 着色器:

// A constant buffer of shader parameters populated from our C++ code
cbuffer PerFrameCB
{
uint gFrameCount;
float gMultValue;
}

float4 main(float2 texC : TEXCOORD, float4 pos : SV_Position) : SV_Target0
{
// Get integer screen coordinates (e.g., in [0..1920] x [0..1080])
uint2 pixelPos = (uint2)pos.xy;

// Compute a per-pixel sinusoidal value
float sinusoid = 0.5 * (1.0f + sin( 0.001f * gMultValue *
dot(pixelPos, pixelPos) +
gFrameCount ));

// Save our color out to our framebuffer
return float4(sinusoid, sinusoid, sinusoid, 1.0f);
}

The first couple lines define our constant buffer with the parameters we set from the execute method of our SinusoidRenderPass.

前几行使用我们从 SinusoidRenderPassexecute 方法中设置的参数来定义我们的常量缓冲区。

// A constant buffer of shader parameters populated from our C++ code
cbuffer PerFrameCB
{
uint gFrameCount;
float gMultValue;
}

We then define our shader main that takes two variables (texC and pos) from Falcor’s default vertex shader and writes out to our framebuffer’s color buffer 0 (i.e., SV_Target0).

然后,我们定义着色器 main,从 Falcor 的默认顶点着色器中获取两个变量 texCpos 并写出到帧缓冲器的颜色缓冲区 0(即 SV_Target0 ).

// Our main pixel shader.  It writes a float4 to the output color buffer. 
float4 main(float2 texC : TEXCOORD, float4 pos : SV_Position) : SV_Target0
{
// Compute a per-pixel sinusoidal value
float sinusoid = 0.5 * (1.0f + sin(0.001f * gMultValue * (dot(pos.xy, pos.xy) + gFrameCount / gMultValue) ));

// Save our color out to our framebuffer
return float4(sinusoid, sinusoid, sinusoid, 1.0f);
}

The math is fairly unimportant, but generates a grayscale sinuosoid that slowly changes over time (since gFrameCount gets incremented each frame) and whose scale is controllable by the gMultValue variable.

数学运算相当不重要,但会生成一个灰度 sinuosoid,该灰度会随时间缓慢变化(因为 gFrameCount 每帧都会递增),并且其比例可由 gMultValue 变量控制。

What Does it Look Like?

That covers the important points of this tutorial. Now if you run it, you get a result similar to this:

这涵盖了本教程的要点。现在,如果您运行它,您将获得类似于以下内容的结果:

Hopefully, this tutorial demonstrated how to use a very basic raster shader and send it parameters from our simple RenderPass architecture.

希望本教程演示了如何使用非常基本的栅格着色器,以及从简单的 RenderPass 架构向其传递参数。

When you are ready, continue on to Tutorial 3, where we use a more traditional rasterization pass to generate a G-Buffer and allow the user to selectively display different G-Buffer parameters.

准备就绪后,请继续学习教程3,其中我们使用更传统的栅格化通道来生成 G缓冲区,并允许用户有选择地显示不同的 G缓冲区参数。