A Gentle Introduction to DirectX Raytracing 1

引言

本节是基础知识,包括打开一个窗口,并将其替换为用户可控的颜色。

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. So before digging into the meat of these tutorials, we focus a little on just getting started.

正如我们的教程简介中所讨论的,我们的目标是提供一个简单的基础架构来启动和运行 DirectX Raytracing 应用程序,而无需在底层 API 规范文档中进行挖掘。因此,在深入研究这些教程的内容之前,我们先关注一下如何开始。

These tutorials build up, step-by-step, to demonstrate a quite complex path tracer with global illumination, depth-of-field, and physically-based materials. However, the first few tutorials are fairly simple and focus on understanding the infrastructure we will take for granted in the more advanced tutorials. If you wish to jump ahead to a tutorial with actual DirectX Raytracing programming, jump ahead to Tutorial 4.

这些教程将逐步构建具有全局照明、景深和基于物理材质的相当复杂的路径跟踪器。但是,前几个教程相当简单,重点是了解我们将在更高级的教程中认为理所当然的基础结构。如果您希望跳转到包含实际 DirectX 光线追踪编程的教程,请跳到教程 4

Our Infrastructure

These tutorials use Falcor, a prototypying framework I use every day in my job at NVIDIA Research. This is an open source, multi-platform wrapper around DirectX and Vulkan that is tested daily on NVIDIA, AMD, and Intel hardware, and it provides common tools needed for baseline graphics experimentation. This includes a GUI, window management, scene and asset loading, and common abstractions over graphics APIs suitable for developing graphics algorithms.

本系列教程使用 Falcor,这是我在 NVIDIA Research 工作中每天使用的原型框架。这是一个围绕 DirectX 和 Vulkan 的开源多平台封装渲染器,每天在 NVIDIA,AMD 和 Intel 硬件上进行测试,它提供了图形渲染管线基础实验所需的常用工具,包括 GUI、窗口管理、场景和资产加载以及适用于开发图形算法的图形 API 的常见抽象。

I added some additional wrappers around Falcor functionality to simplify creating a modular set of tutorials and reduce boilerplate code common in many complex graphics APIs. These wrappers are in the directory DXRTutors\SharedUtils, but you should not need to look there unless you need to modify or extend these tutorials in fairly complex ways.

我围绕 Falcor 功能添加了一些额外的封装,以简化创建一组模块化教程的过程,并减少许多复杂图形 API 中常见的样板代码。这些封装位于 DXRTutors\SharedUtils 目录中,但除非您需要以相当复杂的方式修改或扩展这些封装,否则您不需要查看该目录。

Building a Simple Tutorial Application

If you open the project DXRTutors\01-OpenWindow, the main file is Tutor01-OpenWindow.cpp, and is just about 20 lines long.

打开项目 DXRTutors\01-OpenWindow,主文件是 Tutor01-OpenWindow.cpp,长度约为 20 行:

#include "Falcor.h"
#include "../SharedUtils/RenderingPipeline.h"
#include "Passes/ConstantColorPass.h"

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
// Create our rendering pipeline
RenderingPipeline *pipeline = new RenderingPipeline();

// Add passes into our pipeline
pipeline->setPass(0, ConstantColorPass::create());

// Define a set of configuration for our program
SampleConfig config;
config.windowDesc.title =
"Tutorial 1: Open a window and set up a simple rendering pipeline";
config.windowDesc.resizableWindow = true;

// Start our program!
RenderingPipeline::run(pipeline, config);
}

This starts with some #includes for basic Falcor functionality, the required Falcor extensions for the DirectX Raytracing API, and the RenderingPipeline abstraction. Each of the tutorials is structured as a RenderingPipeline, which contains an arbitrarily-long sequence of RenderingPasses. A frame is rendered by executing each of the render passes in sequence.

这段代码首先包含了许多头文件,包括 Falcor 的基本功能、DirectX Raytracing API 所需的 Falcor 扩展以及 RenderingPipeline 抽象。每个教程的结构都是一个 RenderingPipeline 其中包含任意长的 RenderingPasses 序列。一个帧序列是通过按顺序执行每个渲染通道来渲染的。

#include "Falcor.h"
#include "../SharedUtils/RenderingPipeline.h"
#include "Passes/ConstantColorPass.h"

In this simple example, there is a single pass called ConstantColorPass, which displays a constant color across the entire screen.

在这个简单的示例中,我们有一个名为 ConstantColorPass 的单个通道,它在整个屏幕上显示恒定的颜色。

The SampleConfig structure contains various parameters that control the window created by Falcor. In this case, we specify a title and note that the created window should be resizable.

SampleConfig 结构包含控制 Falcor 创建的窗口的各种参数。在这种情况下,我们指定一个标题 “Tutorial 1: Open a window and set up a simple rendering pipeline”,并注意创建的窗口应该可调整大小。

// Define a set of configuration for our program
SampleConfig config;
config.windowDesc.title =
"Tutorial 1: Open a window and set up a simple rendering pipeline";

Finally, we start execution by calling the static method RenderingPipeline::run with our tutorial’s rendering pipeline and the window configuration.

最后,我们通过调用静态方法 RenderingPipeline::run 来开始执行,并使用我们教程的渲染管道和窗口配置。

// Start our program!
RenderingPipeline::run(pipeline, config);

Defining a New Render Pass

Before running the program, let’s look into what it takes to build a simple RenderPass. Our ConstantColorPass is located in the Passes\ directory. First let’s examine the boilerplate:

在运行程序之前,让我们看看构建一个简单的 RenderPass 需要什么。我们的 ConstantColorPass 位于 Passes\ 目录中:

#include "../SharedUtils/RenderPass.h"

class ConstantColorPass : public RenderPass,
inherit_shared_from_this< RenderPass, ConstantColorPass >
{
public:
using SharedPtr = std::shared_ptr< ConstantColorPass >;

static SharedPtr create() { return SharedPtr(new ConstantColorPass()); }
virtual ~ConstantColorPass() = default;

protected:
ConstantColorPass()
: SimpleRenderPass("Constant Color Pass", "Constant Color Options") {}

// Actual functional methods declared below here
...
};

Here, RenderPass is the abstract class defining the interface a render pass needs to implement, and this abstract class resides in my directory of wrapper utilities.

在这里,RenderPass 是定义渲染传递需要实现的接口的抽象类,这个抽象类驻留在我的封装实用程序目录中。

Falcor makes heavy use of smart pointers (e.g., based on std::shared_ptr), and the Falcor utility class inherit_shared_from_this ensures derived classes don’t end up inheriting multiple copies of the internal pointers. Most Falcor classes define a SharedPtr type that simplifies the template notation when using smart pointers, and our wrappers keep up this tradition.

Falcor 大量使用智能指针(例如,基于 std::shared_ptr),而 Falcor 的 utility 类 inherit_shared_from_this 确保派生类最终不会继承内部指针的多个副本。大多数 Falcor 类都定义了一个 SharedPtr 类型,该类型在使用智能指针时简化了模板表示,我们的封装保持了这一传统。

We want all RenderPasses to be dynamically created. We enforce this with a create constructor. The inputs to the SimpleRenderPass constructor are strings that will be used by the GUI display. The first is the name to appear in the list of passes. The second is the name of this pass’ GUI window that contains any user-controllable options it exposes.

我们希望所有 RenderPasses 动态创建的。为此我们使用 create 构造函数强制执行此操作。 SimpleRenderPass 构造函数的输入是 GUI 将使用的字符串。第一个是出现在凭证列表中的名称。第二个是此传递的 GUI 窗口的名称,其中包含它公开的任何用户可控选项。

The important rendering methods and data in ConstantColorPass are as follows:

ConstantColorPass 中重要的渲染方法和数据如下:

bool initialize(RenderContext::SharedPtr pRenderContext, 
ResourceManager::SharedPtr pResManager) override;

This method is called when the program and render pass are initialized. As part of initialization, a render pass receives a Falcor RenderContext, which allows you to create and access DirectX state and resources. Additionally, the ResourceManager is a utility class that enables sharing of resources between RenderPasses.

初始化程序和渲染管道时调用此方法。作为初始化的一部分,渲染管道会收到一个 Falcor RenderContext,允许您创建和访问 DirectX 状态和资源。此外 ResourceManager 是一个实用程序类,它支持 RenderPasses .

void renderGui(Gui* pGui) override;

The renderGui method is called when drawing the application’s GUI and allows you to explicitly control placement of widgets in the GUI window for this pass. Falcor currently uses Dear Imgui for GUI rendering, and the Falcor::Gui class is a light wrapper around Imgui’s functionality.

renderGui 方法在绘制应用程序的 GUI 时调用,并允许您显式控制此过程中小部件在 GUI 窗口中的位置。Falcor 目前使用 Dear Imgui 进行 GUI 渲染,Falcor::Gui 类是围绕 Imgui 功能的轻量级封装。

void execute(RenderContext::SharedPtr pRenderContext) override;

The execute method is invoked when rendering a frame. If your pipeline contains multiple passes, their execute methods are called in the order of inclusion in the pipeline.

在渲染帧时调用 execute 方法。如果管道包含多个通道,则将按管道中的顺序调用其 execute 方法。

Implementing a New Render Pass

Now that we’ve looked at the header for our ConstantColorPass, let’s look at how we implement each of the three important methods:

现在我们已经看了 ConstantColorPass 的标头,让我们来看看我们如何实现这三个重要方法:

bool ConstantColorPass::initialize(RenderContext::SharedPtr pRenderContext, 
ResourceManager::SharedPtr pResManager)
{
// Remember our resource manager, so we can ask for resources later
mpResManager = pResManager;

// Tell our resource manager that we need access to the output channel
mpResManager->requestTextureResource(ResourceManager::kOutputChannel);

// If we return false, this pass will be removed from our pipeline.
return true;
}

For our simple ConstantColorPass, we do two important initialization tasks:

  1. Keep a copy of our resource manager, so we can access shared textures and buffers later. mpResManager is a member variable declared in the base class RenderPass.
  2. Tell the resource manager that this pass requires access to the kOutputChannel texture. This texture is a required output from any pipeline, as it gets displayed onscreen after all RenderPasses execute.

对于简单的 ConstantColorPass 我们执行两个重要的初始化任务:

  1. 保留资源管理器的副本,以便之后可以访问共享纹理和缓冲区。 mpResManager 是在基类 RenderPass 中声明的成员变量
  2. 告诉资源管理器此传递需要访问 kOutputChannel 纹理。此纹理是任何管道的必需输出,因为它在所有 RenderPasses 执行后都会显示在屏幕上
void ConstantColorPass::renderGui(Gui* pGui)
{
// Add a GUI widget allowing us to dynamically change the displayed color
pGui->addFloat3Var("Color", mConstColor, 0.0f, 1.0f);
}

Our renderGui defines what widgets are available to interact with when we open the GUI window for the ConstantColorPass. In this case, we’ll have an RGB color control (i.e., a float3) where all components can freely be changed between the values of 0.0f and 1.0f.

我们的 renderGui 定义了当我们打开 ConstantColorPass 的 GUI 窗口时,哪些小部件可以与之交互。在这种情况下,我们将有一个 RGB 颜色控件(即 float3 其中所有组件都可以在 0.0f1.0f 的值之间自由更改。

void ConstantColorPass::execute(RenderContext::SharedPtr pRenderContext)
{
// Get a pointer to a Falcor texture resource of our output channel
Texture::SharedPtr outTex =
mpResManager->getTexture(ResourceManager::kOutputChannel);

// Clear the texture to the appropriate color
mpResManager->clearTexture(outTex, vec4(mConstColor, 1.0f));
}

The execute method is invoked when the pipeline wants us to perform our rendering tasks. For a simple pass like ConstantColorPass, we have exactly two goals:

  1. Get a handle to the buffer that will be displayed on screen, and
  2. Clear this buffer to the user-specified constant color.

当管道希望我们执行渲染任务时,将调用 execute 方法。对于像 ConstantColorPass 这样的简单管线,我们正好有两个目标:

  1. 获取将显示在屏幕上的缓冲区的句柄
  2. 然后将此缓冲区清除为用户指定的常量颜色

To do the first task, we simply ask our resource manager for the requested texture. This only works if we called requestTextureResource with the corresponding texture during pass initialization. The resource manager handles creation, destruction, and sharing of all textures that any pass has requested. It also handles resizing of screen-sized buffers (all textures default to screen size unless a different size is requested).

We then call the clearTexture() utility method that is part of the ResourceManager class. This hides some DirectX details (i.e., how to clear a resource varies depending on what resource views it be bound with).

然后,我们调用 clearTexture() 方法,该方法属于 ResourceManager 类。这隐藏了一些 DirectX 细节(即如何清除资源取决于绑定的资源视图)。

What Does it Look Like?

现在,如果您运行它将获得类似于以下内容的结果:

To open the GUI window for the ConstantColorPass, click the little box to the left of the text “Constant Color Pass” in the main window GUI.

如需打开 ConstantColorPass 的 GUI 窗口,请单击主窗口 GUI 中文本”Constant Color Pass”左侧的小框。

Hopefully, this tutorial clarified how to set up a simple application with our framework and define a basic render pass to control what appears on screen. We will skip many of these basics and ignore the boilerpate in future tutorials.

希望本节教程阐明了如何使用我们的框架设置一个简单的应用程序,并定义一个基本的渲染通道来控制屏幕上显示的内容。我们将在以后的教程中跳过其中的许多基础知识并忽略该样板。

When you are ready, continue on to Tutorial 2, where we build a slightly more complex raster pass with a basic HLSL shader.

准备就绪后,请继续学习教程2,我们将使用基本的 HLSL 着色器构建稍微复杂的栅格管道。