DXRで遊ぶ

# 概要
## はじめに
[Direct3D 12 Raytracing](https://docs.microsoft.com/en-us/windows/desktop/direct3d12/direct3d-12-raytracing), 
[DirectX-Graphics-Samples](https://github.com/Microsoft/DirectX-Graphics-Samples)より正しく詳しい情報は得られません.
処理の概観についてまとめます.

## 必要環境
ハードとソフトに強く依存します.

|||
|:-----------------|:------------------|
|Windows 10|Version 1809|
|Windows SDK|October 2018 (17763)|
|Nvidia Driver|Version 415|
|GPU|RTXシリーズ|

従来のCompute Shaderでエミュレートする環境が用意されていますが今回は無視します.

## SDK
d3d12_1.h がWindows SDK (17763)にはないため, [DirectX-Graphics-Samples](https://github.com/Microsoft/DirectX-Graphics-Samples)
からコピーします.

# 初期化
## サポートチェック
デバイスは, *D3D12_FEATURE_D3D12_OPTIONS5::RaytracingTier*をチェックするだけです. *HLSL Shader Model 6.3*が必要ですが, 包含していると考えていいのでしょう.

<details><summary>デバイスチェック</summary><div>

``` cpp
D3D12_FEATURE_DATA_D3D12_OPTIONS5 featureSupportData = {};
return SUCCEEDED(device_->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &featureSupportData, sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS5)))
        && D3D12_RAYTRACING_TIER_NOT_SUPPORTED != featureSupportData.RaytracingTier;
```
</div></details>

## DXRインターフェイス
ID3D12Device5と, ID3D12GraphicsCommandList4が追加されました.

<details><summary>インテーフェイス取得</summary><div>

``` cpp
ID3D12Device5* device5 = nullptr;
ID3D12Device* device->QueryInterface(IID_PPV_ARGS(&device5));
ID3D12GraphicsCommandList4* commandList4 = nullptr;
ID3D12GraphicsCommandList* commandList->QueryInterface(IID_PPV_ARGS(&commandList4));
```
</div></details>

# パイプライン
シェーダ関数をコンピュートユニットに発行することで処理が進みます. 
CPUからは, *ID3D12GraphicsCommandList4::DispatchRays*

``` cpp
ID3D12GraphicsCommandList4::DispatchRays(D3D12_DISPATCH_RAYS_DESC* dispatchDesc)
```

シェーダ内の各関数に *[shader("attribute name")]*の形式で属性を付けておきます.
*raygeneration*関数がシステムから呼ばれ, *TraceRay*でレイを生成します. 
"レイが何かにヒットした"等のイベントごとに, それぞれ対応する関数が呼ばれます.

下図のように*TraceRay*でレイを生成して, シーンをトラバースします. *Intersection* はBVHの境界ボリュームに交差したときのイベントです. 
![Process00.png](https://qiita-image-store.s3.amazonaws.com/0/27065/87390b35-51cc-2b04-704a-d9bd2b3dfeaf.png)

## HLSL
シェーダモデル6.3が必要です. 従来のfxcではなく, dxcに*lib_6_3*を指定してコンパイルします.

<details><summary>サンプルから引用(Raytracing.hlsl)</summary><div>

``` cpp
struct Viewport
{
    float left;
    float top;
    float right;
    float bottom;
};

struct RayGenConstantBuffer
{
    Viewport viewport;
    Viewport stencil;
};

RaytracingAccelerationStructure Scene : register(t0, space0);
RWTexture2D<float4> RenderTarget : register(u0);
ConstantBuffer<RayGenConstantBuffer> g_rayGenCB : register(b0);

struct RayPayload
{
    float4 color;
};

[shader("raygeneration")]
void MyRaygenShader()
{
    float2 lerpValues = (float2)DispatchRaysIndex() / (float2)DispatchRaysDimensions();
    
    // +Z方向に垂直投影
    float3 rayDir = float3(0, 0, 1);
    float3 origin = float3(
        lerp(g_rayGenCB.viewport.left, g_rayGenCB.viewport.right, lerpValues.x),
        lerp(g_rayGenCB.viewport.top, g_rayGenCB.viewport.bottom, lerpValues.y),
        0.0f);

    RayDesc ray;
    ray.Origin = origin;
    ray.Direction = rayDir;
    ray.TMin = 0.001;
    ray.TMax = 10000.0;
    RayPayload payload = {float4(0, 0, 0, 0)};
    TraceRay(Scene, RAY_FLAG_CULL_BACK_FACING_TRIANGLES, ~0, 0, 1, 0, ray, payload);

    RenderTarget[DispatchRaysIndex().xy] = payload.color;
}

[shader("closesthit")]
void MyClosestHitShader(inout RayPayload payload, in BuiltInTriangleIntersectionAttributes attr)
{
    float3 barycentrics = float3(1 - attr.barycentrics.x - attr.barycentrics.y, attr.barycentrics.x, attr.barycentrics.y);
    payload.color = float4(barycentrics, 1);
}

[shader("miss")]
void MyMissShader(inout RayPayload payload)
{
    payload.color = float4(0, 0, 0, 1);
}
```
</div></details>

## BVH生成
Acceleration Structureが必須です. データ構造が公開されていないため自作できません.

*ID3D12GraphicsCommandList4::BuildRaytracingAccelerationStructure*で作成します. 
トップレベルとボトムレベルのふたつを作成する必要があります.

*ID3D12GraphicsCommandList4::CopyRaytracingAccelerationStructure*を使えば, 事前生成ができそうです.
ただし, *D3D12_SERIALIZED_RAYTRACING_ACCELERATION_STRUCTURE_HEADER*の
*D3D12_SERIALIZED_DATA_DRIVER_MATCHING_IDENTIFIER*をチェックする必要があります.

# まとめ
処理の概観についてまとめました. 詳細はサンプルコードを読むしかないのですが, 
シェーダを中心に必要なものを生成していくコードを読むとわかりやすいと思います.

従来のラスタライザパイプラインとは完全に分離しているため, 結果を合成するなりしないといけません.