TOP > PROGRAM

DirectX Raytracing 入門

1 はじめに…

そろそろDXR触りたいなぁと思ったので,触ってみることにします。まずは,単純な三角形の描画から始めることにします。

図 1: DXRはじめました

2 開発環境の整備

まずは,DXRを触れる環境が必要です。Windows Updateして最新版にしておきましょう。自分は以下の環境でやっています。

3 DirectX RayTracingとは?

DirectX RayTracing(DXR)とは,かなり雑にいうと,DirectXでレイトレーシングできるAPIを用意しましたというものです。DXRでは,以下の新しい3つのコンセプトが導入されています。

3.1 Acceleration Structures

Acceleration Structuresは,交差判定の高速に行うための仕組みです。レイトレのプログラムをCPUで組んだことがある人は,BVHやグリッド構造を想像してもらえると理解が速いかと思います。
 DXRの高速化機構は2レイヤーに分かれています。

さて、この2つの違いですがBLASは,ジオメトリ形状を定義するのに使用します。要はポリゴンやプリミティブから作成する高速化機構になります。一方,TLASはBLASのインスタンスにトランスフォームを適用するものになります。[Microsoft 2020]に掲載されている図 2を見るとわかりやすいと思います。

図 2: Ray-geometry interaction diagram

※図は, [Microsoft 2020]より引用。

3.2 シェーダと実行パイプライン

DXRはレイトレーシングはどのように実行されるのでしょうか?今度はDXRのパイプラインを見てみます。

図 3: DXRパイプライン

青い囲みで書かれているのが,新設された6つのシェーダです。

RayGenerationシェーダはその名の通りレイを生成するシェーダです。レイの定義はRayDesc構造体を使用します。

struct RayDesc
{
    float3 Origin;
    float  TMin;
    float3 Direction;
    float  TMax;
};

Originはレイの原点,Directionはレイの方向ベクトル,TMinとTMaxは交差と判断するための距離の範囲を指定します。

図 4: レイの定義

TraceRay()メソッドの引数として,RayDescを渡すことによりレイトレーシングが開始されます。レイトレ中に交差が発生したらClosestHitシェーダが呼び出され,交差が発生しなかった場合はMissシェーダが呼び出されます。交差判定で,実際に交差が発生した場所を知る方法ですがRayTCurrent()メソッドによって交差地点までの距離を取得することができるので,Origin + Direction * RayTCurrent()を計算することによって衝突点を求めることができます。さて,三角形については組み込みされているので問題ないのですが,三角形の交差判定以外の独自に定義したプリミティブなどの交差判定をしたい場合はどうすればよいでしょうか?この場合は,Intersectionシェーダを書いて対応します。Intersectionシェーダを使った場合は柔軟性は向上しますが,組み込みのレイvs三角形の交差に比べると効率が悪くなるので,注意してください。AnyHitシェーダはレイがジオメトリインスタンスとの交差が発生するたびに実行されるシェーダです。使いどころとしては,アルファテストの判定を行い,透過と判定する場合はIgnoreHit()で交差を無効として,非透過と判定する場合はAcceptHitAndEndSearch()メソッドを呼び出してトラバーサルを終了するなどの使い方が考えられます。AnyHitシェーダは交差のたびに呼び出されますが,これに対してClosestHitシェーダはレイのトラバーサルが終了した後に,T値が最も小さかった時の,システム定義変数やAttributeとともに一度だけ呼び出されます。最後のCallableシェーダは別のシェーダからCallShader()メソッドによって呼び出されるシェーダです。引数としてinoutでパラメータを取れるため,共通処理やパラメータ更新などに利用することが考えられます。

3.3 Shader Table

 レイトレーシングの場合は色々なプリミティブとヒットする可能性があります。ラスタライザを用いたグラフィックスパイプラインでは,「これは不透明メッシュなのでこのシェーダ,これは半透明メッシュなのでフォワードレンダリング用のシェーダ,これはアルファテスト使うので,このシェーダ」…といった具合に,プリミティブに基づいてシェーダを切り替えしていたと思います。レイトレーシングの場合は,レイを飛ばす方向が定まるまで,どのプリミティブに当たるわからないといった状況になります。どのシェーダを使うかが不定になるので,マテリアル計算に使用するシェーダをすべて持っておかなくてはならないという状況が発生するのは容易に想像できると思います。こうした背景からDXRではシェーダテーブル(Shader Table)という仕組みがあります。シェーダテーブルは、連続したメモリ領域にあるシェーダレコードの集合です。シェーダレコードは,Shader Identifier(シェーダ識別子)とLocal Root Argumetns(ローカルルート引数)を持ちます。シェーダ識別子は,レイトレーシングシェーダ(RayGeneration Shader, HitGroup, Miss Shader, Callable Shader)の1つを一意に識別する32バイトのデータBlob(バイナリラージオブジェクト)です。要するにシェーダへのポインタと考えることができます。DXRではシェーダテーブルのインデックスを指定することによってシェーダを呼び出します。

4 はじめてのDXR

さて,ざっくりした説明はしましたが,実際のコード見ながら説明しないとよくわからないと思うので,DXRを触りながら詳細を見ていくことにしましょう。まずは,三角形の交差を取って,色を塗るだけの簡単なプログラムを作ってみます。

4.1 シェーダの準備

 シェーダから準備するのが簡単そうなので,シェーダから取り掛かります。今回は以下のようなシェーダを用意しました。

//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include "Math.hlsli"


#define VERTEX_STRIDE   (7 * 4)     // 1頂点あたりのサイズ.
#define INDEX_STRIDE    (3 * 4)     // 1ポリゴンあたりのインデックスサイズ.
#define COLOR_OFFSET    (3 * 4)     // 1頂点データの先頭からカラーデータまでのオフセット.

//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
typedef BuiltInTriangleIntersectionAttributes HitArgs;


///////////////////////////////////////////////////////////////////////////////
// SceneParam structure
///////////////////////////////////////////////////////////////////////////////
struct SceneParam
{
    float4x4    InvView;
    float4x4    InvViewProj;
};

///////////////////////////////////////////////////////////////////////////////
// RayPayload structure
///////////////////////////////////////////////////////////////////////////////
struct RayPayload
{
    float4 Color;
    float3 RayDir;
};

///////////////////////////////////////////////////////////////////////////////
// Vertex structure
///////////////////////////////////////////////////////////////////////////////
struct Vertex
{
    float3 Position;
    float4 Color;
};

//-----------------------------------------------------------------------------
// Resources
//-----------------------------------------------------------------------------
RaytracingAccelerationStructure Scene         : register(t0);
Texture2D<float4>               BackGround    : register(t1);
ByteAddressBuffer               Indices       : register(t2);
ByteAddressBuffer               Vertices      : register(t3);
RWTexture2D<float4>             RenderTarget  : register(u0);
ConstantBuffer<SceneParam>      SceneParam    : register(b0);
SamplerState                    LinearSampler : register(s0);


//-----------------------------------------------------------------------------
//      プリミティブ番号を取得します.
//-----------------------------------------------------------------------------
uint3 GetIndices(uint triangleIndex)
{
    uint address = triangleIndex * INDEX_STRIDE;
    return Indices.Load3(address);
}

//-----------------------------------------------------------------------------
//      重心座標で補間した頂点データを取得します.
//-----------------------------------------------------------------------------
Vertex GetVertex(uint triangleIndex, float3 barycentrices)
{
    uint3 indices = GetIndices(triangleIndex);
    Vertex v = (Vertex)0;

    [unroll]
    for(uint i=0; i<3; ++i)
    {
        uint address = indices[i] * VERTEX_STRIDE;
        v.Position += asfloat(Vertices.Load3(address)) * barycentrices[i];

        address += COLOR_OFFSET;
        v.Color += asfloat(Vertices.Load4(address)) * barycentrices[i];
    }

    return v;
}

//-----------------------------------------------------------------------------
//      レイを求めます.
//-----------------------------------------------------------------------------
void CalcRay(float2 index, out float3 pos, out float3 dir)
{
    float4 orig   = float4(0.0f, 0.0f, 0.0f, 1.0f);           // カメラの位置.
    float4 screen = float4(-2.0f * index + 1.0f, 0.0f, 1.0f); // スクリーンの位置.

    orig   = mul(SceneParam.InvView,     orig);
    screen = mul(SceneParam.InvViewProj, screen);

    // w = 1 に射影.
    screen.xyz /= screen.w;

    // レイの位置と方向を設定.
    pos = orig.xyz;
    dir = normalize(screen.xyz - orig.xyz);
}


//-----------------------------------------------------------------------------
//      レイを生成します.
//-----------------------------------------------------------------------------
[shader("raygeneration")]
void OnGenerateRay()
{
    float2 index = (float2)DispatchRaysIndex() / (float2)DispatchRaysDimensions();

    // レイを生成.
    float3 rayOrig;
    float3 rayDir;
    CalcRay(index, rayOrig, rayDir);

    // ペイロード初期化.
    RayPayload payload;
    payload.RayDir = rayDir;
    payload.Color  = float4(0.0f, 0.0f, 0.0f, 0.0f);

    // レイを設定.
    RayDesc ray;
    ray.Origin    = rayOrig;
    ray.Direction = rayDir;
    ray.TMin      = 1e-3f;
    ray.TMax      = 10000.0f;

    // レイを追跡.
    TraceRay(Scene, RAY_FLAG_NONE, ~0, 0, 1, 0, ray, payload);

    // レンダーターゲットに格納.
    RenderTarget[DispatchRaysIndex().xy] = payload.Color;
}

//-----------------------------------------------------------------------------
//      交差後の処理です.
//-----------------------------------------------------------------------------
[shader("closesthit")]
void OnClosestHit(inout RayPayload payload, in HitArgs args)
{
    // 重心座標を求める.
    float3 barycentrics = float3(
        1.0f - args.barycentrics.x - args.barycentrics.y,
        args.barycentrics.x,
        args.barycentrics.y);

    // プリミティブ番号取得.
    uint triangleIndex = PrimitiveIndex();

    // 頂点データ取得.
    Vertex v = GetVertex(triangleIndex, barycentrics);

    // カラーを格納.
    payload.Color = v.Color;
}

//-----------------------------------------------------------------------------
//      非交差後の処理です.
//-----------------------------------------------------------------------------
[shader("miss")]
void OnMiss(inout RayPayload payload)
{
    // スフィアマップのテクスチャ座標を算出.
    float2 uv = ToSphereMapCoord(payload.RayDir);

    // スフィアマップをサンプル.
    float4 color = BackGround.SampleLevel(LinearSampler, uv, 0.0f);

    // 色を設定.
    payload.Color = color;
}

 まず,シェーダが3つに分かれています。レイを生成する処理を行うのがOnGenerateRay()メソッドです。レイとペイロードの設定を行い,シェーダ組み込み関数のTraceRay()を呼び出します。TraceRay()関数は次の通りです。

Template<payload_t>
void TraceRay(RaytracingAccelerationStructure AccelerationStructure,
    uint RayFlags,
    uint InstanceInclusionMask,
    uint RayContributionToHitGroupIndex,
    uint MultiplierForGeometryContributionToHitGroupIndex,
    uint MissShaderIndex,
    RayDesc Ray,
    inout payload_t Payload);

 第1引数には高速化機構を設定します。第2引数には,レイフラグを指定します。レイフラグとして指定できるのものは次の通りです。

enum RAY_FLAG : uint
{
    RAY_FLAG_NONE                            = 0x00,
    RAY_FLAG_FORCE_OPAQUE                    = 0x01,
    RAY_FLAG_FORCE_NON_OPAQUE                = 0x02,
    RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH = 0x04,
    RAY_FLAG_SKIP_CLOSEST_HIT_SHADER         = 0x08,
    RAY_FLAG_CULL_BACK_FACING_TRIANGLES      = 0x10,
    RAY_FLAG_CULL_FRONT_FACING_TRIANGLES     = 0x20,
    RAY_FLAG_CULL_OPAQUE                     = 0x40,
    RAY_FLAG_CULL_NON_OPAQUE                 = 0x80,
    RAY_FLAG_SKIP_TRIANGLES                  = 0x100,
    RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES      = 0x200,
};

 今回は特別に変わったことはしないので,RAY_FLAG_NONEを指定しておきました。さて,再びTraceRay()に戻り,第3引数のInstanceInclusionMaskですが,これはジオメトリを棄却するのに使うマスクを指定するようです。今回は使わないので,~0を指定しました。第4引数のRayContributionToHitGroupIndexはヒットグループのインデックス作成のためにシェーダーテーブル内のアドレス計算に追加するオフセットです。この値の下位4ビットのみが使用されるとのことです。こちらも今回は特に利用しないのでゼロを指定しました。第5引数はGeometryContributionToHitGroupIndexに乗算するストライドです。今回は1を指定しました。第6引数のMissShaderIndexはミスシェーダのインデックスを指定します。今回ミスシェーダは1個ですので,0番を指定しておきます。第7引数はレイの設定を指定します。第8引数はペイロードを指定します。
 TraceRay()を呼び出すとレイトレが実行されます。トレースした結果がペイロードに格納されるので,最後にレンダーターゲット代わりとなるUAVに書き込んでOnGenerateRay()メソッドの処理は終了です。レイを飛ばす方向はCalcRay()メソッドで計算しています。説明については今給黎さんのページ(https://t-pot.com/program/92_RayTraceSphere/index.html)に記載されているので,そちらを見てください。
 次は,OnClosestHit()メソッドを見てます。closesthitシェーダは先ほども述べたように最も交差距離が短かったものときのシステム定義変数やAttributeとともに一度だけ呼び出されます。引数として重心座標が渡されてくるので,ペイロードに重心座標から求めた色を格納してします。
 続いて,OnMiss()メソッドですが,レイの方向からスフィアマップのテクスチャ座標を算出して,テクスチャフェッチした色をペイロードに格納しています。
 今回のサンプルのシェーダ自体はそんなに複雑なものではないと思うので,大丈夫でしょう。よくわからない箇所がある場合は,DXRの仕様(https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html)を参照しておくと良いでしょう。
OnMiss()メソッド内で登場するToSphereMapCoord()メソッドですが,これは以前にBlog記事の方で取り上げています(http://project-asura.com/blog/archives/6007)ので,Blog記事の説明を参照してください。

4.2 DXRの初期化

 シェーダは複雑ではないのですが,C++側の処理はやたらと面倒くさいです。今回のサンプルプログラムでは次の順番で初期化を行っています。

それでは順にみていきます。

4.2.1 DXRのサポートチェック

DXRがサポートされているGPUではないとDXRが実行できません。そのため,サポートされているかどうかのチェックを行います。

//-----------------------------------------------------------------------------
//      DXRがサポートされているかどうかチェックします.
//-----------------------------------------------------------------------------
bool IsSupportDXR(ID3D12Device6* pDevice)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS5 options = {};
    auto hr = pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options, sizeof(options));
    if (FAILED(hr))
    { return false; }

    return options.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED;
}

DXRがサポートされている場合は,IsSupportDXR()メソッドの返り値がtrueとなります。

4.2.2 グローバルルートシグニチャの生成

シェーダテーブルごとにレイアウトが変化しないものをグローバルルートシグニチャとして生成しておきます。今回のサンプルでは…

RaytracingAccelerationStructure Scene         : register(t0);
Texture2D<float4>               BackGround    : register(t1);
ByteAddressBuffer               Indices       : register(t2);
ByteAddressBuffer               Vertices      : register(t3);
RWTexture2D<float4>             RenderTarget  : register(u0);
ConstantBuffer<SceneParam>      SceneParam    : register(b0);
SamplerState                    LinearSampler : register(s0);

上記のデータはシーンで一意に決まるものなので,これらをグローバルルートシグニチャとして設定します。実装は下記のようにしました。

    // グローバルルートシグニチャの生成.
    {
        D3D12_DESCRIPTOR_RANGE range[2] = {};
        range[0].RangeType                          = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
        range[0].NumDescriptors                     = 1;
        range[0].BaseShaderRegister                 = 0;
        range[0].RegisterSpace                      = 0;
        range[0].OffsetInDescriptorsFromTableStart  = 0;

        range[1].RangeType                          = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
        range[1].NumDescriptors                     = 1;
        range[1].BaseShaderRegister                 = 1;
        range[1].RegisterSpace                      = 0;
        range[1].OffsetInDescriptorsFromTableStart  = 0;

        D3D12_ROOT_PARAMETER param[6] = {};
        param[ROOT_PARAM_U0].ParameterType                          = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
        param[ROOT_PARAM_U0].DescriptorTable.NumDescriptorRanges    = 1;
        param[ROOT_PARAM_U0].DescriptorTable.pDescriptorRanges      = &range[0];
        param[ROOT_PARAM_U0].ShaderVisibility                       = D3D12_SHADER_VISIBILITY_ALL;

        param[ROOT_PARAM_T0].ParameterType                          = D3D12_ROOT_PARAMETER_TYPE_SRV;
        param[ROOT_PARAM_T0].Descriptor.ShaderRegister              = 0;
        param[ROOT_PARAM_T0].Descriptor.RegisterSpace               = 0;
        param[ROOT_PARAM_T0].ShaderVisibility                       = D3D12_SHADER_VISIBILITY_ALL;

        param[ROOT_PARAM_T1].ParameterType                          = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
        param[ROOT_PARAM_T1].DescriptorTable.NumDescriptorRanges    = 1;
        param[ROOT_PARAM_T1].DescriptorTable.pDescriptorRanges      = &range[1];
        param[ROOT_PARAM_T1].ShaderVisibility                       = D3D12_SHADER_VISIBILITY_ALL;

        param[ROOT_PARAM_T2].ParameterType                          = D3D12_ROOT_PARAMETER_TYPE_SRV;
        param[ROOT_PARAM_T2].Descriptor.ShaderRegister              = 2;
        param[ROOT_PARAM_T2].Descriptor.RegisterSpace               = 0;
        param[ROOT_PARAM_T2].ShaderVisibility                       = D3D12_SHADER_VISIBILITY_ALL;

        param[ROOT_PARAM_T3].ParameterType                          = D3D12_ROOT_PARAMETER_TYPE_SRV;
        param[ROOT_PARAM_T3].Descriptor.ShaderRegister              = 3;
        param[ROOT_PARAM_T3].Descriptor.RegisterSpace               = 0;
        param[ROOT_PARAM_T3].ShaderVisibility                       = D3D12_SHADER_VISIBILITY_ALL;

        param[ROOT_PARAM_B0].ParameterType                          = D3D12_ROOT_PARAMETER_TYPE_CBV;
        param[ROOT_PARAM_B0].Descriptor.ShaderRegister              = 0;
        param[ROOT_PARAM_B0].Descriptor.RegisterSpace               = 0;
        param[ROOT_PARAM_B0].ShaderVisibility                       = D3D12_SHADER_VISIBILITY_ALL;

 
        D3D12_STATIC_SAMPLER_DESC sampler = {};
        sampler.Filter              = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
        sampler.AddressU            = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
        sampler.AddressV            = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
        sampler.AddressW            = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
        sampler.MipLODBias          = 0;
        sampler.MaxAnisotropy       = 0;
        sampler.ComparisonFunc      = D3D12_COMPARISON_FUNC_NEVER;
        sampler.BorderColor         = D3D12_STATIC_BORDER_COLOR_OPAQUE_BLACK;
        sampler.MinLOD              = 0.0f;
        sampler.MaxLOD              = D3D12_FLOAT32_MAX;
        sampler.ShaderRegister      = 0;
        sampler.RegisterSpace       = 0;
        sampler.ShaderVisibility    = D3D12_SHADER_VISIBILITY_ALL;
        
        D3D12_ROOT_SIGNATURE_DESC desc = {};
        desc.NumParameters      = _countof(param);
        desc.pParameters        = param;
        desc.NumStaticSamplers  = 1;
        desc.pStaticSamplers    = &sampler;

        asdx::RefPtr<ID3DBlob> blob;
        asdx::RefPtr<ID3DBlob> error;

        auto hr = D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, blob.GetAddress(), error.GetAddress());
        if (FAILED(hr))
        {
            ELOGA("Error : D3D12SerializeRootSignature() Failed. errcode = 0x%x, msg = %s", hr, reinterpret_cast<char*>(error->GetBufferPointer()));
            return false;
        }

        hr = pDevice->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(m_GlobalRootSig.GetAddress()));
        if (FAILED(hr))
        {
            ELOGA("Error : ID3D12Device::CreateRootSignature() Failed. errcode = 0x%x", hr);
            return false;
        }
    }

4.2.3 ローカルルートシグニチャの生成

 今回のサンプルでは,必要ありませんが… 今後サンプルを作るテンプレートとして今回作ったサンプルを再利用したいので,空設定のローカルルートシグニチャを生成しました。  

    // ローカルルートシグニチャの生成. 
    // シェーダテーブルごとに異なるものをはこちらで設定する.
    {
        asdx::RefPtr<ID3DBlob> blob;
        asdx::RefPtr<ID3DBlob> error;

        D3D12_ROOT_SIGNATURE_DESC desc = {};
        desc.NumParameters      = 0;
        desc.pParameters        = nullptr;
        desc.NumStaticSamplers  = 0;
        desc.pStaticSamplers    = nullptr;
        desc.Flags              = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE;

        auto hr = D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, blob.GetAddress(), error.GetAddress());
        if (FAILED(hr))
        {
            ELOGA("Error : D3D12SerializeRootSignature() Failed. errcode = 0x%x", hr);
            return false;
        }

        hr = pDevice->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(m_LocalRootSig.GetAddress()));
        if (FAILED(hr))
        {
            ELOGA("Error : ID3D12Device::CreateRootSignature() Failed. errcode = 0x%x", hr);
            return false;
        }
    }

4.2.4 ステートオブジェクトの生成

 レイトレーシングパイプラインでは,従来のID3D12PipelineStateを使わず,ID3D12StateObjectを利用します。ID3D12StateObjectはID3D12Device5::CreateStateObject()メソッドを呼び出すことで生成を行います。DXRでめんどいのがD3D12_STATE_OBJECT_DESC構造体を埋めることです。次のような感じで実装しました。  

    // ステートオブジェクトの生成.
    {
        asdx::SubObjects subObjects;

        D3D12_EXPORT_DESC exports[3] = {
            { L"OnGenerateRay", nullptr, D3D12_EXPORT_FLAG_NONE },
            { L"OnClosestHit",  nullptr, D3D12_EXPORT_FLAG_NONE },
            { L"OnMiss",        nullptr, D3D12_EXPORT_FLAG_NONE },
        };

        // グローバルルートシグニチャの設定.
        D3D12_GLOBAL_ROOT_SIGNATURE globalRootSig = {};
        globalRootSig.pGlobalRootSignature = m_GlobalRootSig.GetPtr();
        subObjects.Push(&globalRootSig);

        // ローカルルートシグニチャの設定.
        D3D12_LOCAL_ROOT_SIGNATURE localRootSig = {};
        localRootSig.pLocalRootSignature = m_LocalRootSig.GetPtr();
        subObjects.Push(&localRootSig);

        // DXILライブラリの設定.
        D3D12_DXIL_LIBRARY_DESC dxilLib = {};
        dxilLib.DXILLibrary = { SampleRayTracing, sizeof(SampleRayTracing) };
        dxilLib.NumExports  = _countof(exports);
        dxilLib.pExports    = exports;
        subObjects.Push(&dxilLib);

        // ヒットグループの設定.
        D3D12_HIT_GROUP_DESC hitGroup = {};
        hitGroup.ClosestHitShaderImport = L"OnClosestHit";
        hitGroup.HitGroupExport         = L"MyHitGroup";
        hitGroup.Type                   = D3D12_HIT_GROUP_TYPE_TRIANGLES;
        subObjects.Push(&hitGroup);

        // シェーダ設定.
        D3D12_RAYTRACING_SHADER_CONFIG shaderConfig = {};
        shaderConfig.MaxPayloadSizeInBytes   = sizeof(asdx::Vector4) + sizeof(asdx::Vector3);
        shaderConfig.MaxAttributeSizeInBytes = sizeof(asdx::Vector2);
        subObjects.Push(&shaderConfig);

        // パイプライン設定.
        D3D12_RAYTRACING_PIPELINE_CONFIG pipelineConfig = {};
        pipelineConfig.MaxTraceRecursionDepth = 1;
        subObjects.Push(&pipelineConfig);

        // ステート設定取得.
        auto desc = subObjects.GetDesc();

        // ステートオブジェクトを生成.
        auto hr = pDevice->CreateStateObject(&desc, IID_PPV_ARGS(m_StateObject.GetAddress()));
        if (FAILED(hr))
        {
            ELOGA("Error : ID3D12Device::CreateStateObject() Failed. errcode = 0x%x", hr);
            return false;
        }
    }

 ちなみに,SubObjects::Push()メソッドやSubObjects::GetDesc()メソッドの実装は次の通りです。

//-----------------------------------------------------------------------------
//      ステートオブジェクト設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_STATE_OBJECT_CONFIG* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_STATE_OBJECT_CONFIG;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      グローバルルートシグニチャ設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_GLOBAL_ROOT_SIGNATURE* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      ローカルルートシグニチャ設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_LOCAL_ROOT_SIGNATURE* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_LOCAL_ROOT_SIGNATURE;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      ノードマスク設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_NODE_MASK* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_NODE_MASK;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      DXILライブラリ設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_DXIL_LIBRARY_DESC* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      既存コレクション設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_EXISTING_COLLECTION_DESC* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_EXISTING_COLLECTION;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      エクスポートを関連付けるサブオブジェクト設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      エクスポートを関連付けるDXILサブオブジェクト設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_SUBOBJECT_TO_EXPORTS_ASSOCIATION;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      ヒットグループ設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_HIT_GROUP_DESC* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      シェーダ設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_RAYTRACING_SHADER_CONFIG* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      パイプライン設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_RAYTRACING_PIPELINE_CONFIG* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      パイプライン設定を追加します.
//-----------------------------------------------------------------------------
void SubObjects::Push(const D3D12_RAYTRACING_PIPELINE_CONFIG1* pDesc)
{
    assert(m_Count + 1 < kMaxCount);
    auto idx = m_Count;
    m_Objects[idx].pDesc = pDesc;
    m_Objects[idx].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG1;
    m_Count++;
}

//-----------------------------------------------------------------------------
//      ステートオブジェクトの構成設定を作成します.
//-----------------------------------------------------------------------------
D3D12_STATE_OBJECT_DESC SubObjects::GetDesc() const
{
    D3D12_STATE_OBJECT_DESC result = {};
    result.Type          = D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE;
    result.NumSubobjects = m_Count;
    result.pSubobjects   = m_Objects;

    return result;
}

 まことに面倒くさい。これで,パイプラインステートに対応するステートオブジェクトまでが生成できたことになります。

4.2.5 頂点データ生成

 つづいて、頂点データ生成です。シェーダ上でByteAddressBufferとして利用できるように処理を実装しました。

    // 頂点データ生成.
    {
        const Vertex vertices[] = {
            { asdx::Vector3( 0.0f,  0.7f, 1.0f), asdx::Vector4(1.0f, 0.0f, 0.0f, 1.0f) },
            { asdx::Vector3(-0.7f, -0.7f, 1.0f), asdx::Vector4(0.0f, 1.0f, 0.0f, 1.0f) },
            { asdx::Vector3( 0.7f, -0.7f, 1.0f), asdx::Vector4(0.0f, 0.0f, 1.0f, 1.0f) },
        };

        auto size = sizeof(vertices);
        if (!asdx::CreateUploadBuffer(pDevice, size, m_VB.GetAddress()))
        {
            ELOGA("Error : CreateUploadBuffer() Failed.");
            return false;
        }

        void* ptr = nullptr;
        auto hr = m_VB->Map(0, nullptr, &ptr);
        if (FAILED(hr))
        {
            ELOGA("Error : ID3D12Resource::Map() Failed. errcode = 0x%x", hr);
            return false;
        }

        memcpy(ptr, vertices, sizeof(vertices));
        m_VB->Unmap(0, nullptr);

        if (!asdx::CreateBufferSRV(pDevice, m_VB.GetPtr(), sizeof(vertices) / 4, 0, m_VertexSRV.GetAddress()))
        {
            ELOGA("Error : CreateBufferSRV() Failed.");
            return false;
        }
    }

上記のコードで登場するCreateUploadBuffer()メソッドとCreateBufferSRV()メソッドは次の通りです。

//-----------------------------------------------------------------------------
//      バッファSRVを生成します.
//-----------------------------------------------------------------------------
bool CreateBufferSRV
(
    ID3D12Device*           pDevice,
    ID3D12Resource*         pResource,
    UINT                    elementCount,
    UINT                    elementSize,
    IShaderResourceView**   ppView
)
{
    D3D12_SHADER_RESOURCE_VIEW_DESC desc = {};
    desc.ViewDimension           = D3D12_SRV_DIMENSION_BUFFER;
    desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
    desc.Buffer.NumElements      = elementCount;
    if (elementSize == 0)
    {
        desc.Format                     = DXGI_FORMAT_R32_TYPELESS;
        desc.Buffer.Flags               = D3D12_BUFFER_SRV_FLAG_RAW;
        desc.Buffer.StructureByteStride = 0;
    }
    else
    {
        desc.Format                     = DXGI_FORMAT_UNKNOWN;
        desc.Buffer.Flags               = D3D12_BUFFER_SRV_FLAG_NONE;
        desc.Buffer.StructureByteStride = elementSize;
    }

    return asdx::CreateShaderResourceView(pResource, &desc, ppView);
}

//-----------------------------------------------------------------------------
//      アップロードバッファを生成します.
//-----------------------------------------------------------------------------
bool CreateUploadBuffer
(
    ID3D12Device*           pDevice,
    UINT64                  bufferSize,
    ID3D12Resource**        ppResource
)
{
    D3D12_HEAP_PROPERTIES props = {};
    props.Type                  = D3D12_HEAP_TYPE_UPLOAD;
    props.CPUPageProperty       = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
    props.MemoryPoolPreference  = D3D12_MEMORY_POOL_UNKNOWN;
    props.CreationNodeMask      = 1;
    props.VisibleNodeMask       = 1;

    D3D12_RESOURCE_DESC desc = {};
    desc.Dimension          = D3D12_RESOURCE_DIMENSION_BUFFER;
    desc.Alignment          = 0;
    desc.Width              = bufferSize;
    desc.Height             = 1;
    desc.DepthOrArraySize   = 1;
    desc.MipLevels          = 1;
    desc.Format             = DXGI_FORMAT_UNKNOWN;
    desc.SampleDesc.Count   = 1;
    desc.SampleDesc.Quality = 0;
    desc.Layout             = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
    desc.Flags              = D3D12_RESOURCE_FLAG_NONE;

    auto hr = pDevice->CreateCommittedResource(
        &props,
        D3D12_HEAP_FLAG_NONE,
        &desc,
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(ppResource));
    if (FAILED(hr))
    {
        ELOGA("Error : ID3D12Device::CreateCommittedResource() Failed. errcode = 0x%x", hr);
        return false;
    }

    return true;
}

 まぁ、普通にバッファを作っているだけですね。

4.2.6 頂点インデックスデータの生成

 頂点バッファと同じようにインデックスバッファも生成しておきます。

    // インデックスデータ生成.
    {
        uint32_t indices[] = { 0, 1, 2 };

        if (!asdx::CreateUploadBuffer(pDevice, sizeof(indices), m_IB.GetAddress()))
        {
            ELOGA("Error : CreateUploadBuffer() Failed.");
            return false;
        }

        void* ptr = nullptr;
        auto hr = m_IB->Map(0, nullptr, &ptr);
        if (FAILED(hr))
        {
            ELOGA("Error : ID3D12Resource::Map() Failed. errcode = 0x%x", hr);
            return false;
        }

        memcpy(ptr, indices, sizeof(indices));
        m_IB->Unmap(0, nullptr);

        if (!asdx::CreateBufferSRV(pDevice, m_IB.GetPtr(), 3, 0, m_IndexSRV.GetAddress()))
        {
            ELOGA("Error : CreateBufferSRV() Failed.");
            return false;
        }
    }

4.2.7 高速化機構の生成

 ジオメトリが用意できたので,レイトレ用の高速化機構を生成します。先ほど説明したように高速化機構はBLAS(Bottom Level Accelaration Structure)とTLAS(Top Level Accelaration Structure)の2レイヤーに分かれています。まずは,BLASを生成していきますが,今後のサンプルでも使いまわししやすいようにクラス化しました。クラスの宣言は次の通りです。

///////////////////////////////////////////////////////////////////////////////
// Blas class
///////////////////////////////////////////////////////////////////////////////
class Blas
{
    //=========================================================================
    // list of friend classes and methods.
    //=========================================================================
    /* NOTHING */

public:
    //=========================================================================
    // public variables.
    //=========================================================================
    /* NOTHING */

    //=========================================================================
    // public methods.
    //=========================================================================

    //-------------------------------------------------------------------------
    //! @brief      コンストラクタです.
    //-------------------------------------------------------------------------
    Blas() = default;

    //-------------------------------------------------------------------------
    //! @brief      デストラクタです.
    //-------------------------------------------------------------------------
    ~Blas();

    //-------------------------------------------------------------------------
    //! @brief      初期化処理を行います.
    //-------------------------------------------------------------------------
    bool Init(
        ID3D12Device6*              pDevice,
        uint32_t                    count,
        const DXR_GEOMETRY_DESC*    pDescs,
        DXR_BUILD_FLAGS             flags);

    //-------------------------------------------------------------------------
    //! @brief      終了処理を行います.
    //-------------------------------------------------------------------------
    void Term();

    //-------------------------------------------------------------------------
    //! @brief      ビルドします.
    //-------------------------------------------------------------------------
    void Build(ID3D12GraphicsCommandList6* pCmd);

    //-------------------------------------------------------------------------
    //! @brief      ジオメトリ数を取得します.
    //-------------------------------------------------------------------------
    uint32_t GetGeometryCount() const;

    //-------------------------------------------------------------------------
    //! @brief      ジオメトリ構成を取得します.
    //-------------------------------------------------------------------------
    const DXR_GEOMETRY_DESC& GetGeometry(uint32_t index) const;

    //-------------------------------------------------------------------------
    //! @brief      ジオメトリ構成を設定します.
    //-------------------------------------------------------------------------
    void SetGeometry(uint32_t index, const DXR_GEOMETRY_DESC& desc);

    //-------------------------------------------------------------------------
    //! @brief      リソースを取得します.
    //-------------------------------------------------------------------------
    ID3D12Resource* GetResource() const;

private:
    //=========================================================================
    // private variables.
    //=========================================================================
    RefPtr<ID3D12Resource>          m_Scratch;
    RefPtr<ID3D12Resource>          m_Structure;
    DXR_BUILD_DESC                  m_BuildDesc;
    std::vector<DXR_GEOMETRY_DESC>  m_GeometryDesc;

    //=========================================================================
    // private methods.
    //=========================================================================
    /* NOTHING */
};

 使い方としては,Init()で初期化して,そのあとでBuild()を呼んで構築を行います。Init()の呼び方は次のような感じです。

    // 下位レベル高速化機構の生成.
    {
        asdx::DXR_GEOMETRY_DESC desc = {};
        desc.Type                                   = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES;
        desc.Flags                                  = D3D12_RAYTRACING_GEOMETRY_FLAG_NONE;
        desc.Triangles.VertexCount                  = 3;
        desc.Triangles.VertexBuffer.StartAddress    = m_VB->GetGPUVirtualAddress();
        desc.Triangles.VertexBuffer.StrideInBytes   = sizeof(Vertex);
        desc.Triangles.VertexFormat                 = DXGI_FORMAT_R32G32B32_FLOAT;
        desc.Triangles.IndexCount                   = 3;
        desc.Triangles.IndexBuffer                  = m_IB->GetGPUVirtualAddress();
        desc.Triangles.IndexFormat                  = DXGI_FORMAT_R32_UINT;
        desc.Triangles.Transform3x4                 = 0;

        if (!m_BLAS.Init(pDevice, 1, &desc, asdx::DXR_BUILD_FLAG_PREFER_FAST_TRACE))
        {
            ELOGA("Error : Blas::Init() Failed.");
            return false;
        }
    }

 先ほど用意した頂点バッファとインデックスバッファを設定して,Init()メソッドの引数に突っ込めばOKです。Init()メソッドの実装自体は次のようにしています。

//-----------------------------------------------------------------------------
//      初期化処理を行います.
//-----------------------------------------------------------------------------
bool Blas::Init
(
    ID3D12Device6*              pDevice,
    uint32_t                    count,
    const DXR_GEOMETRY_DESC*    pDescs,
    DXR_BUILD_FLAGS             flags
)
{
    if (pDevice == nullptr)
    { return false; }

    // 設定をコピっておく.
    m_GeometryDesc.resize(count);
    if (pDescs != nullptr)
    {
        for(auto i=0u; i<count; ++i)
        { m_GeometryDesc[i] = pDescs[i]; }
    }

    // 高速化機構の入力設定.
    D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS inputs = {};
    inputs.DescsLayout      = D3D12_ELEMENTS_LAYOUT_ARRAY;
    inputs.Flags            = flags;
    inputs.NumDescs         = count;
    inputs.pGeometryDescs   = m_GeometryDesc.data();
    inputs.Type             = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL;

    // ビルド前情報を取得.
    D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO prebuildInfo = {};
    pDevice->GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo);
    if (prebuildInfo.ResultDataMaxSizeInBytes == 0)
    { return false; }

    // スクラッチバッファ生成.
    if (!CreateBufferUAV(
        pDevice,
        prebuildInfo.ScratchDataSizeInBytes,
        m_Scratch.GetAddress(),
        D3D12_RESOURCE_STATE_UNORDERED_ACCESS))
    {
        ELOGA("Error : CreateUAVBuffer() Failed.");
        return false;
    }
    m_Scratch->SetName(L"asdxBlasScratch");

    // 高速化機構用バッファ生成.
    if (!CreateBufferUAV(
        pDevice,
        prebuildInfo.ResultDataMaxSizeInBytes,
        m_Structure.GetAddress(),
        D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE))
    {
        ELOGA("Error : CreateUAVBuffer() Failed.");
        return false;
    }
    m_Structure->SetName(L"asdxBlas");

    // ビルド設定.
    memset(&m_BuildDesc, 0, sizeof(m_BuildDesc));
    m_BuildDesc.Inputs                              = inputs;
    m_BuildDesc.ScratchAccelerationStructureData    = m_Scratch->GetGPUVirtualAddress();
    m_BuildDesc.DestAccelerationStructureData       = m_Structure->GetGPUVirtualAddress();

    // 正常終了.
    return true;
}

 ドライバーによって実装が変わるのか,GetRaytracingAccelarationStructurePrebuildInfo()を呼んで,サイズ等を設定しないといけないみたいです。初期化の時点ではバッファの生成しかしていないので,高速化機構のビルドがされていません。ビルドを実行するためにはBuild()メソッドを呼び出します。このメソッドの実装は次のようにしました。

//-----------------------------------------------------------------------------
//      ビルドします.
//-----------------------------------------------------------------------------
void Blas::Build(ID3D12GraphicsCommandList6* pCmd)
{
    // 高速化機構を構築.
    pCmd->BuildRaytracingAccelerationStructure(&m_BuildDesc, 0, nullptr);

    // バリアを張っておく.
    D3D12_RESOURCE_BARRIER barrier = {};
    barrier.Type            = D3D12_RESOURCE_BARRIER_TYPE_UAV;
    barrier.UAV.pResource   = m_Structure.GetPtr();
    pCmd->ResourceBarrier(1, &barrier);
}

構築前にGPU上で参照されてしまうのを防ぐためにUAVバリアを張ってあります。
 次に,TLASを作成します。こちらも後で使いまわしやすいようにクラス化しました。クラスの宣言は次の通りです。

///////////////////////////////////////////////////////////////////////////////
// Tlas class
///////////////////////////////////////////////////////////////////////////////
class Tlas
{
    //=========================================================================
    // list of friend classes and methods.
    //=========================================================================
    /* NOTHING */

public:
    //=========================================================================
    // public variables.
    //=========================================================================
    /* NOTHING */

    //=========================================================================
    // public methods.
    //=========================================================================

    //-------------------------------------------------------------------------
    //! @brief      コンストラクタです.
    //-------------------------------------------------------------------------
    Tlas() = default;

    //-------------------------------------------------------------------------
    //! @brief      デストラクタです.
    //-------------------------------------------------------------------------
    ~Tlas();

    //-------------------------------------------------------------------------
    //! @brief      初期化処理を行います.
    //-------------------------------------------------------------------------
    bool Init(
        ID3D12Device6*              pDevice, 
        uint32_t                    instanceCount,
        const DXR_INSTANCE_DESC*    pInstanceDescs,
        DXR_BUILD_FLAGS             flags);

    //-------------------------------------------------------------------------
    //! @brief      終了処理を行います.
    //-------------------------------------------------------------------------
    void Term();

    //-------------------------------------------------------------------------
    //! @brief      ビルドします.
    //-------------------------------------------------------------------------
    void Build(ID3D12GraphicsCommandList6* pCmd);

    //-------------------------------------------------------------------------
    //! @brief      メモリマッピングを行います.
    //-------------------------------------------------------------------------
    DXR_INSTANCE_DESC* Map();

    //-------------------------------------------------------------------------
    //! @brief      メモリマッピングを解除します.
    //-------------------------------------------------------------------------
    void Unmap();

    //-------------------------------------------------------------------------
    //! @brief      リソースを取得します.
    //-------------------------------------------------------------------------
    ID3D12Resource* GetResource() const;

private:
    //=========================================================================
    // private variables.
    //=========================================================================
    RefPtr<ID3D12Resource>  m_Scratch;
    RefPtr<ID3D12Resource>  m_Structure;
    RefPtr<ID3D12Resource>  m_Instances;
    DXR_BUILD_DESC          m_BuildDesc;

    //=========================================================================
    // private methods.
    //=========================================================================
    /* NOTHING */
};

 使い方は次のような感じで,BLASからGPU仮想アドレスを引っ張ってきて,インスタンス配置用の行列を一緒に渡します。

    // 上位レベル高速化機構の生成.
    {
        auto matrix = asdx::Transform3x4();

        asdx::DXR_INSTANCE_DESC desc = {};
        memcpy(desc.Transform, &matrix, sizeof(matrix));
        desc.InstanceMask           = 0x1;
        desc.AccelerationStructure  = m_BLAS.GetResource()->GetGPUVirtualAddress();

        if (!m_TLAS.Init(pDevice, 1, &desc, asdx::DXR_BUILD_FLAG_PREFER_FAST_TRACE))
        {
            ELOGA("Error : Tlas::Init() Failed.");
            return false;
        }
    }

 上記コードで呼び出しているTlas::Init()メソッドの実装は次の通りです。

//-----------------------------------------------------------------------------
//      初期化処理を行います.
//-----------------------------------------------------------------------------
bool Tlas::Init
(
    ID3D12Device6*              pDevice,
    uint32_t                    instanceDescCount,
    const DXR_INSTANCE_DESC*    pInstanceDescs,
    DXR_BUILD_FLAGS             flags
)
{
    // アップロードバッファ生成.
    if (!CreateUploadBuffer(
        pDevice, sizeof(DXR_INSTANCE_DESC) * instanceDescCount, 
        m_Instances.GetAddress()))
    {
        ELOGA("Error : CreateUploadBuffer() Failed.");
        return false;
    }

    // インスタンス設定をコピー.
    {
        DXR_INSTANCE_DESC* ptr = nullptr;
        auto hr = m_Instances->Map(0, nullptr, reinterpret_cast<void**>(&ptr));
        if (FAILED(hr))
        {
            ELOGA("Error : ID3D12Resource::Map() Failed. errcode = 0x%x", hr);
            return false;
        }

        memcpy(ptr, pInstanceDescs, sizeof(DXR_INSTANCE_DESC) * instanceDescCount);

        m_Instances->Unmap(0, nullptr);
    }

    // 高速化機構の入力設定.
    D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS inputs = {};
    inputs.DescsLayout      = D3D12_ELEMENTS_LAYOUT_ARRAY;
    inputs.Flags            = flags;
    inputs.NumDescs         = instanceDescCount;
    inputs.InstanceDescs    = m_Instances->GetGPUVirtualAddress();
    inputs.Type             = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL;

    // ビルド前情報を取得.
    D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO prebuildInfo = {};
    pDevice->GetRaytracingAccelerationStructurePrebuildInfo(&inputs, &prebuildInfo);
    if (prebuildInfo.ResultDataMaxSizeInBytes == 0)
    { return false; }

    // スクラッチバッファ生成.
    if (!CreateBufferUAV(
        pDevice,
        prebuildInfo.ScratchDataSizeInBytes,
        m_Scratch.GetAddress(),
        D3D12_RESOURCE_STATE_UNORDERED_ACCESS))
    {
        ELOGA("Error : CreateUAVBuffer() Failed.");
        return false;
    }
    m_Scratch->SetName(L"asdxTlasScratch");

    // 高速化機構用バッファ生成.
    if (!CreateBufferUAV(
        pDevice,
        prebuildInfo.ResultDataMaxSizeInBytes,
        m_Structure.GetAddress(),
        D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE))
    {
        ELOGA("Error : CreateUAVBuffer() Failed.");
        return false;
    }
    m_Structure->SetName(L"asdxTlas");

    // ビルド設定.
    memset(&m_BuildDesc, 0, sizeof(m_BuildDesc));
    m_BuildDesc.Inputs                              = inputs;
    m_BuildDesc.ScratchAccelerationStructureData    = m_Scratch->GetGPUVirtualAddress();
    m_BuildDesc.DestAccelerationStructureData       = m_Structure->GetGPUVirtualAddress();

    // 正常終了.
    return true;
}

 BLASと同じようにTLASもビルドをする必要があります。Tlas::Build()メソッドの実装は次の通りです。

//-----------------------------------------------------------------------------
//      ビルドします.
//-----------------------------------------------------------------------------
void Tlas::Build(ID3D12GraphicsCommandList6* pCmd)
{
    // 高速化機構を構築.
    pCmd->BuildRaytracingAccelerationStructure(&m_BuildDesc, 0, nullptr);

    // バリアを張っておく.
    D3D12_RESOURCE_BARRIER barrier = {};
    barrier.Type            = D3D12_RESOURCE_BARRIER_TYPE_UAV;
    barrier.UAV.pResource   = m_Structure.GetPtr();
    pCmd->ResourceBarrier(1, &barrier);
}

BLASのときと同じ関数呼んでるだけです。クラスの実装コードが長いのが難点ですが,1回実装しておけば次回以降はインスタンス作って呼び出すだけなので,面倒でよく出てくるコードほど再利用可能な様に実装しておくと良いと思います。
 今回はデータが動的に変化しないので,ビルドは初期化時に一回だけ呼び出すことにしました。次のような感じです。

    // 高速化機構のビルド.
   {
       asdx::CommandList commandList;
       if (!commandList.Init(pDevice, D3D12_COMMAND_LIST_TYPE_DIRECT))
       {
           ELOGA("Error : CommandList::Init() Failed.");
           return false;
       }

       auto pCmd = commandList.Reset();
       m_BLAS.Build(pCmd);
       m_TLAS.Build(pCmd);

       pCmd->Close();
       ID3D12CommandList* pCmds[] = {
           pCmd
       };

       // コマンドを実行.
       m_pGraphicsQueue->Execute(1, pCmds);

       // 待機点を発行.
       m_FrameWaitPoint = m_pGraphicsQueue->Signal();

       // 完了を待機.
       m_pGraphicsQueue->Sync(m_FrameWaitPoint);

       // コマンドリスト破棄.
       commandList.Term();
   }

 これで,高速化機構の構築までが終わりました。

4.2.8 シェーダテーブルの生成

 続いてはシェーダテーブルの生成です。サンプルでは次のような感じで生成を行っています。

    // シェーダテーブルの生成.
    {
        asdx::RefPtr<ID3D12StateObjectProperties> props;
        auto hr = m_StateObject->QueryInterface(IID_PPV_ARGS(props.GetAddress()));
        if (FAILED(hr))
        {
            ELOGA("Error : ID3D12StateObject::QueryInteface() Failed. errcode = 0x%x", hr);
            return false;
        }

        // レイ生成シェーダテーブル.
        {
            asdx::ShaderRecord record;
            record.ShaderIdentifier = props->GetShaderIdentifier(L"OnGenerateRay");

            if (!m_RayGenTable.Init(pDevice, 1, &record))
            {
                ELOGA("Error : ShaderTable::Init() Failed.");
                return false;
            }
        }

        // ミスシェーダテーブル.
        {
            asdx::ShaderRecord record;
            record.ShaderIdentifier = props->GetShaderIdentifier(L"OnMiss");

            if (!m_MissTable.Init(pDevice, 1, &record))
            {
                ELOGA("Error : ShaderTable::Init() Failed.");
                return false;
            }
        }

        // ヒットグループシェーダテーブル
        {
            asdx::ShaderRecord record;
            record.ShaderIdentifier = props->GetShaderIdentifier(L"MyHitGroup");

            if (!m_HitGroupTable.Init(pDevice, 1, &record))
            {
                ELOGA("Error : ShaderTable::Init() Failed.");
                return false;
            }
        }
    }

 さて、肝心のシェーダテーブルの実装ですが,単なるバッファの生成処理です。

//-----------------------------------------------------------------------------
//      初期化処理です.
//-----------------------------------------------------------------------------
bool ShaderTable::Init
(
    ID3D12Device*       pDevice,
    uint32_t            recordCount,
    const ShaderRecord* records,
    uint32_t            localRootArgumentSize
)
{
    // アライメントを揃える.
    m_RecordSize = RoundUp(
        D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES + localRootArgumentSize,
        D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT);

    auto bufferSize = m_RecordSize * recordCount;

    // アップロードバッファ生成.
    if (!CreateUploadBuffer(pDevice, bufferSize, m_Resource.GetAddress()))
    {
        ELOGA("Error : CreateUploadBuffer() Failed.");
        return false;
    }

    // メモリマッピング.
    uint8_t* ptr = nullptr;
    auto hr = m_Resource->Map(0, nullptr, reinterpret_cast<void**>(&ptr));
    if (FAILED(hr))
    {
        ELOGA("Error : ID3D12Resource::Map() Failed. errcode = 0x%x", hr);
        return false;
    }

    // 大きく分岐して高速化.
    if (localRootArgumentSize == 0)
    {
        for(auto i=0u; i<recordCount; ++i)
        {
            auto record = records[i];
            memcpy(ptr, record.ShaderIdentifier, D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES);
            ptr += m_RecordSize;
        }
    }
    else
    {
        for(auto i=0u; i<recordCount; ++i)
        {
            auto record = records[i];
            memcpy(ptr, record.ShaderIdentifier, D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES);
            memcpy(ptr + D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES, record.LocalRootArguments, localRootArgumentSize);
            ptr += m_RecordSize;
        }
    }

    // メモリマッピング解除.
    m_Resource->Unmap(0, nullptr);

    // 正常終了.
    return true;
}

4.2.9 レンダーターゲットとなるUAVの生成

 最後にレイトレの結果を書き込むUAVを用意してあげてください。ここは普通に2次元のRWTexture2D作ればいいだけなので,説明は省略します。  

4.3 DXRの実行

 はぁ、初期化滅茶苦茶長かったですね。あとは描画処理ですね。基本的にはID3D12GraphicsCommandList4::DispatchRay()を呼び出すだけです。今回のサンプルではCopyResource()を使ってレイトレ結果をバックバッファにコピーして画面に表示するよう実装を行いました。

//-----------------------------------------------------------------------------
//      フレーム描画時の処理です.
//-----------------------------------------------------------------------------
void SampleApp::OnFrameRender(asdx::FrameEventArgs& args)
{
    if (m_pGraphicsQueue == nullptr)
    { return; }

    auto idx  = GetCurrentBackBufferIndex();
    auto pCmd = m_GfxCmdList.Reset();

    asdx::GfxDevice().SetUploadCommand(pCmd);

    // シーンバッファ更新.
    {
        // ビュー行列.
        auto view = asdx::Matrix::CreateLookAt(
            asdx::Vector3(0.0f, 0.0f, -2.0f),
            asdx::Vector3(0.0f, 0.0f, 0.0f),
            asdx::Vector3(0.0f, 1.0f, 0.0f));

        // 射影行列.
        auto proj = asdx::Matrix::CreatePerspectiveFieldOfView(
            asdx::ToRadian(37.5f),
            float(m_Width) / float(m_Height),
            1.0f,
            1000.0f);

        SceneParam res = {};
        res.InvView     = asdx::Matrix::Invert(view);
        res.InvViewProj = asdx::Matrix::Invert(proj) * res.InvView;

        m_SceneBuffer.SwapBuffer();
        m_SceneBuffer.Update(&res, sizeof(res));
    }

    // レイトレ実行.
    {
        // グローバルルートシグニチャ設定.
        pCmd->SetComputeRootSignature(m_GlobalRootSig.GetPtr());

        // ステートオブジェクト設定.
        pCmd->SetPipelineState1(m_StateObject.GetPtr());

        // リソースをバインド.
        asdx::SetTable(pCmd, ROOT_PARAM_U0, m_Canvas      .GetUAV(), true);
        asdx::SetSRV  (pCmd, ROOT_PARAM_T0, m_TLAS   .GetResource(), true);
        asdx::SetTable(pCmd, ROOT_PARAM_T1, m_BackGround .GetView(), true);
        asdx::SetSRV  (pCmd, ROOT_PARAM_T2, m_IndexSRV   .GetPtr (), true);
        asdx::SetSRV  (pCmd, ROOT_PARAM_T3, m_VertexSRV  .GetPtr (), true);
        asdx::SetCBV  (pCmd, ROOT_PARAM_B0, m_SceneBuffer.GetView(), true);

        D3D12_DISPATCH_RAYS_DESC desc = {};
        desc.HitGroupTable              = m_HitGroupTable.GetTableView();
        desc.MissShaderTable            = m_MissTable    .GetTableView();
        desc.RayGenerationShaderRecord  = m_RayGenTable  .GetRecordView();
        desc.Width                      = UINT(m_Canvas.GetDesc().Width);
        desc.Height                     = m_Canvas.GetDesc().Height;
        desc.Depth                      = 1;

        pCmd->DispatchRays(&desc);

        // バリアを張っておく.
        asdx::BarrierUAV(pCmd, m_Canvas.GetResource());
    }

    // レイトレ結果をバックバッファにコピー.
    {
        asdx::ScopedTransition b0(pCmd, m_Canvas.GetResource(), asdx::STATE_UAV, asdx::STATE_COPY_SRC);
        asdx::ScopedTransition b1(pCmd, m_ColorTarget[idx].GetResource(), asdx::STATE_PRESENT, asdx::STATE_COPY_DST);
        pCmd->CopyResource(m_ColorTarget[idx].GetResource(), m_Canvas.GetResource());
    }

    pCmd->Close();

    ID3D12CommandList* pCmds[] = {
        pCmd
    };

    // 前フレームの描画の完了を待機.
    if (m_FrameWaitPoint.IsValid())
    { m_pGraphicsQueue->Sync(m_FrameWaitPoint); }

    // コマンドを実行.
    m_pGraphicsQueue->Execute(1, pCmds);

    // 待機点を発行.
    m_FrameWaitPoint = m_pGraphicsQueue->Signal();

    // 画面に表示.
    Present(0);

    // フレーム同期.
    asdx::GfxDevice().FrameSync();
}

 描画処理は,レイトレに使用する定数バッファを更新して,リソースをバインドします。ID3D12StateObjectをSetPipeline1()メソッドを使って生成して,DispatchRays()を呼び出します。グローバルルートシグニチャをSetComputeRootSignature()で設定しておくのを忘れないでください。DispatchRays()を呼び出したら,UAVバリアを張って処理完了待機を入れておきます。きちんと実行されれば次のような結果が画面に表示されるはずです。画面に表示されない場合はバリデーション機能を使ってデバッグを進めましょう。

図 5: 実行結果

5 おわりに

 DXRは初期化周りのコードが長くなるのが難点ですね。もうちょっとAPI設計どうにか出来なかったのでしょうか?面倒くさいことこの上ないです。APIの細かい説明は端折っているので,Microsoftのドキュメントを読んで補間してください。古典的なレイトレ実装しても何の面白みも嬉しさも無いので,次回はもう少しまともな内容を実装してみようと思います。

6 参考文献

7 サンプルコード

本ソースコードおよびプログラムはMIT Licenseに準じます。
プログラムの作成にはMicrosoft Visual Studio Community 2019, 及び Windows SDK 10.0.20161.0を用いています。
Github - D3D12_FirstDXR