メインコンテンツへスキップ
Tech Playground
低レイヤ・言語

DirectX 12 レイトレーシングパイプライン実装完全ガイド【2026年版】

DXR 1.1の新機能Inline Ray Tracingを活用したレイトレーシングパイプラインの実装方法を解説。シェーダーバインディングテーブル最適化からGPUメモリ管理まで実践的に学べます。

約13分で読めます

DirectX 12 レイトレーシングパイプラインの最新動向

DirectX Raytracing (DXR) は2018年のDXR 1.0リリースから大きく進化し、2026年現在ではDXR 1.1の成熟したAPIとして広く採用されています。特に2025年11月のWindows 11 24H2アップデートで導入されたInline Ray Tracing最適化機能により、シェーダー内で直接レイトレーシングクエリを実行できるようになり、従来のレイトレーシングパイプライン(RTPSO)と比較して最大40%のオーバーヘッド削減が実現されました。

本記事では、2026年最新のDXR 1.1仕様に基づき、レイトレーシングパイプラインの構築から最適化まで、実装に必要なすべての要素を解説します。特にシェーダーバインディングテーブル(SBT)の効率的な設計、加速構造(AS)のメモリ管理、Inline Ray Tracingの実践的な活用方法にフォーカスします。

従来のラスタライゼーションパイプラインとは根本的に異なるレイトレーシングパイプラインの設計思想を理解することで、リアルタイムレイトレーシングの性能を最大限引き出すことが可能になります。

レイトレーシングパイプライン(RTPSO)の構築

DirectX 12のレイトレーシングパイプラインは、複数のシェーダーステージとサブオブジェクトから構成される複雑なステートオブジェクトです。以下のダイアグラムは、RTPSOの構成要素とレイトレーシング実行フローを示しています。

flowchart TD
    A["Ray Generation Shader"] --> B["TraceRay() 呼び出し"]
    B --> C["加速構造のトラバース"]
    C --> D{"ヒット判定"}
    D -->|ヒット| E["Closest Hit Shader"]
    D -->|ミス| F["Miss Shader"]
    D -->|カスタム判定| G["Intersection Shader"]
    G --> H["Any Hit Shader"]
    H --> E
    E --> I["シェーディング計算"]
    F --> J["背景色/環境マップ"]
    I --> K["出力バッファへ書き込み"]
    J --> K
    
    style A fill:#4a90e2
    style E fill:#e24a4a
    style F fill:#e2a44a
    style G fill:#4ae24a

DXR 1.1では、パイプラインステートオブジェクト(PSO)の作成にID3D12StateObjectインターフェースを使用します。2026年3月のDirectX 12 Agility SDK 1.614.1で追加されたD3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINEタイプを指定することで、レイトレーシング専用のPSOを構築できます。

以下は、基本的なRTPSOの作成コード例です:

// サブオブジェクト配列の準備
std::vector<D3D12_STATE_SUBOBJECT> subobjects;

// DXILライブラリ(シェーダーコード)
D3D12_DXIL_LIBRARY_DESC dxilLibDesc = {};
dxilLibDesc.DXILLibrary.pShaderBytecode = rayTracingShaderBlob->GetBufferPointer();
dxilLibDesc.DXILLibrary.BytecodeLength = rayTracingShaderBlob->GetBufferSize();

// エクスポートするシェーダー関数名
D3D12_EXPORT_DESC exports[] = {
    {L"RayGenShader", nullptr, D3D12_EXPORT_FLAG_NONE},
    {L"ClosestHitShader", nullptr, D3D12_EXPORT_FLAG_NONE},
    {L"MissShader", nullptr, D3D12_EXPORT_FLAG_NONE}
};
dxilLibDesc.NumExports = _countof(exports);
dxilLibDesc.pExports = exports;

D3D12_STATE_SUBOBJECT dxilLib = {};
dxilLib.Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY;
dxilLib.pDesc = &dxilLibDesc;
subobjects.push_back(dxilLib);

// ヒットグループの定義(Closest Hit + Intersection/Any Hit)
D3D12_HIT_GROUP_DESC hitGroupDesc = {};
hitGroupDesc.HitGroupExport = L"HitGroup";
hitGroupDesc.ClosestHitShaderImport = L"ClosestHitShader";
hitGroupDesc.Type = D3D12_HIT_GROUP_TYPE_TRIANGLES;

D3D12_STATE_SUBOBJECT hitGroup = {};
hitGroup.Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP;
hitGroup.pDesc = &hitGroupDesc;
subobjects.push_back(hitGroup);

// シェーダー設定(ペイロードとアトリビュートサイズ)
D3D12_RAYTRACING_SHADER_CONFIG shaderConfig = {};
shaderConfig.MaxPayloadSizeInBytes = 16; // float4 RGB + depth
shaderConfig.MaxAttributeSizeInBytes = 8; // float2 barycentric

D3D12_STATE_SUBOBJECT shaderConfigObj = {};
shaderConfigObj.Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG;
shaderConfigObj.pDesc = &shaderConfig;
subobjects.push_back(shaderConfigObj);

// パイプライン設定(最大再帰深度)
D3D12_RAYTRACING_PIPELINE_CONFIG pipelineConfig = {};
pipelineConfig.MaxTraceRecursionDepth = 3; // プライマリ + 反射1回 + GI1回

D3D12_STATE_SUBOBJECT pipelineConfigObj = {};
pipelineConfigObj.Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG;
pipelineConfigObj.pDesc = &pipelineConfig;
subobjects.push_back(pipelineConfigObj);

// グローバルルートシグネチャ
D3D12_STATE_SUBOBJECT globalRootSig = {};
globalRootSig.Type = D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE;
globalRootSig.pDesc = &globalRootSignaturePtr;
subobjects.push_back(globalRootSig);

// RTPSO作成
D3D12_STATE_OBJECT_DESC rtpsoDesc = {};
rtpsoDesc.Type = D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE;
rtpsoDesc.NumSubobjects = static_cast<UINT>(subobjects.size());
rtpsoDesc.pSubobjects = subobjects.data();

ComPtr<ID3D12StateObject> rtpso;
device->CreateStateObject(&rtpsoDesc, IID_PPV_ARGS(&rtpso));

このコードのポイントは、MaxPayloadSizeInBytesMaxAttributeSizeInBytesを最小限に抑えることです。2025年12月のNVIDIA Technical Blogによれば、ペイロードサイズを16バイト以下に保つことで、レジスタスピルを回避し最大25%の性能向上が得られます。

シェーダーバインディングテーブル(SBT)の最適化設計

シェーダーバインディングテーブルは、レイトレーシングパイプラインの性能を左右する最も重要な要素の一つです。SBTは、レイがシーン内のオブジェクトにヒットした際に実行するシェーダーとそのパラメータを格納するGPUメモリ領域です。

以下のダイアグラムは、SBTのメモリレイアウトと、レイトレーシング実行時のシェーダー選択プロセスを示しています。

sequenceDiagram
    participant RGS as Ray Gen Shader
    participant AS as 加速構造
    participant SBT as Shader Binding Table
    participant CHS as Closest Hit Shader
    
    RGS->>AS: TraceRay() 呼び出し
    AS->>AS: BVHトラバース
    AS->>AS: 三角形交差判定
    Note over AS: InstanceID=2, GeometryIndex=1<br/>HitGroupIndex計算
    AS->>SBT: HitGroupレコード取得<br/>Offset = HitGroupStride * (InstanceContributionToHitGroupIndex<br/>+ MultiplierForGeometryContributionToHitGroupIndex<br/>* GeometryIndex + RayContributionToHitGroupIndex)
    SBT->>CHS: シェーダーポインタ + ローカルルート引数
    CHS->>CHS: マテリアルデータ読み込み
    CHS->>RGS: ペイロード返却

SBTの効率的な設計には、以下の3つの原則を守る必要があります:

1. アライメント要件の遵守

2026年2月のDirectX 12 Agility SDK 1.614.0ドキュメントによれば、SBTの各レコードは以下のアライメント要件を満たす必要があります:

  • レコード開始位置: D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT(64バイト)でアライメント
  • レコードサイズ: D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT(32バイト)の倍数
  • シェーダー識別子サイズ: 常にD3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES(32バイト)

以下は、効率的なSBTレコード構造の実装例です:

// シェーダー識別子の取得
ComPtr<ID3D12StateObjectProperties> rtpsoProps;
rtpso.As(&rtpsoProps);

const void* rayGenID = rtpsoProps->GetShaderIdentifier(L"RayGenShader");
const void* missID = rtpsoProps->GetShaderIdentifier(L"MissShader");
const void* hitGroupID = rtpsoProps->GetShaderIdentifier(L"HitGroup");

// SBTレコード構造(32バイトアライメント)
struct SBTRecord {
    uint8_t shaderIdentifier[D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES]; // 32バイト
    // ローカルルート引数(ポインタのみ推奨)
    D3D12_GPU_VIRTUAL_ADDRESS materialBuffer; // 8バイト
    uint32_t materialIndex; // 4バイト
    uint32_t padding[5]; // 20バイトパディング(合計64バイト)
};

constexpr uint32_t recordSize = sizeof(SBTRecord); // 64バイト
static_assert(recordSize % D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT == 0);

// SBTバッファ作成(Upload Heap使用)
const uint32_t numHitGroups = 100; // マテリアル数
const uint32_t sbtSize = recordSize * (1 + 1 + numHitGroups); // RayGen + Miss + HitGroups

ComPtr<ID3D12Resource> sbtBuffer;
CD3DX12_HEAP_PROPERTIES uploadHeap(D3D12_HEAP_TYPE_UPLOAD);
CD3DX12_RESOURCE_DESC bufferDesc = CD3DX12_RESOURCE_DESC::Buffer(
    sbtSize,
    D3D12_RESOURCE_FLAG_NONE,
    D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT
);

device->CreateCommittedResource(
    &uploadHeap,
    D3D12_HEAP_FLAG_NONE,
    &bufferDesc,
    D3D12_RESOURCE_STATE_GENERIC_READ,
    nullptr,
    IID_PPV_ARGS(&sbtBuffer)
);

// SBT書き込み
uint8_t* sbtData;
sbtBuffer->Map(0, nullptr, reinterpret_cast<void**>(&sbtData));

// Ray Generation レコード
memcpy(sbtData, rayGenID, D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES);
sbtData += recordSize;

// Miss レコード
memcpy(sbtData, missID, D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES);
sbtData += recordSize;

// Hit Group レコード(各マテリアル)
for (uint32_t i = 0; i < numHitGroups; ++i) {
    SBTRecord* record = reinterpret_cast<SBTRecord*>(sbtData);
    memcpy(record->shaderIdentifier, hitGroupID, D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES);
    record->materialBuffer = materialBuffers[i]->GetGPUVirtualAddress();
    record->materialIndex = i;
    sbtData += recordSize;
}

sbtBuffer->Unmap(0, nullptr);

2. ローカルルート引数の最小化

AMD GPUOpen Blog(2025年10月)の分析によれば、SBTレコードサイズが128バイトを超えると、L1キャッシュミスが増加し性能が最大30%低下します。ローカルルート引数には、以下のデータのみを含めるべきです:

  • マテリアルバッファへのGPU仮想アドレス(8バイト)
  • マテリアルインデックス(4バイト)
  • テクスチャディスクリプタインデックス(4バイト)

頂点バッファやインデックスバッファのアドレスは、グローバルルートシグネチャ経由でアクセスする方が効率的です。

3. インスタンス単位のHitGroup割り当て最適化

DispatchRays()呼び出し時に使用されるD3D12_DISPATCH_RAYS_DESC構造体では、HitGroupテーブルのストライドを指定します:

D3D12_DISPATCH_RAYS_DESC dispatchDesc = {};

// Ray Generation
dispatchDesc.RayGenerationShaderRecord.StartAddress = sbtBuffer->GetGPUVirtualAddress();
dispatchDesc.RayGenerationShaderRecord.SizeInBytes = recordSize;

// Miss
dispatchDesc.MissShaderTable.StartAddress = sbtBuffer->GetGPUVirtualAddress() + recordSize;
dispatchDesc.MissShaderTable.SizeInBytes = recordSize;
dispatchDesc.MissShaderTable.StrideInBytes = recordSize;

// Hit Group
dispatchDesc.HitGroupTable.StartAddress = sbtBuffer->GetGPUVirtualAddress() + recordSize * 2;
dispatchDesc.HitGroupTable.SizeInBytes = recordSize * numHitGroups;
dispatchDesc.HitGroupTable.StrideInBytes = recordSize;

// レイトレーシング実行
dispatchDesc.Width = screenWidth;
dispatchDesc.Height = screenHeight;
dispatchDesc.Depth = 1;

commandList->SetPipelineState1(rtpso.Get());
commandList->DispatchRays(&dispatchDesc);

InstanceContributionToHitGroupIndexを適切に設定することで、インスタンスごとに異なるマテリアルセットを効率的に管理できます。

加速構造(AS)のメモリ管理とビルド戦略

DirectX 12のレイトレーシングでは、Bottom-Level Acceleration Structure (BLAS) とTop-Level Acceleration Structure (TLAS) の2層構造を使用します。2026年1月のMicrosoft DirectX Developer Blogによれば、加速構造のビルド戦略が総フレーム時間の**20〜40%**を占めるため、適切な管理が不可欠です。

以下のダイアグラムは、加速構造のビルドフローとメモリ配置を示しています。

flowchart LR
    A["頂点バッファ"] --> B["BLAS入力記述子"]
    C["インデックスバッファ"] --> B
    B --> D["BuildRaytracingAccelerationStructure<br/>(BLAS)"]
    D --> E["BLASバッファ(Default Heap)"]
    
    F["インスタンス記述配列"] --> G["TLAS入力記述子"]
    E --> G
    G --> H["BuildRaytracingAccelerationStructure<br/>(TLAS)"]
    H --> I["TLASバッファ(Default Heap)"]
    
    D -.->|一時メモリ| J["スクラッチバッファ<br/>(再利用可能)"]
    H -.->|一時メモリ| J
    
    style E fill:#4a90e2
    style I fill:#e24a4a
    style J fill:#e2a44a

BLASのビルド最適化

静的ジオメトリにはD3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACEフラグを使用し、トレース性能を優先します。動的オブジェクトにはD3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_ALLOW_UPDATEを指定し、フレームごとの更新コストを削減します。

// BLAS入力記述子(静的メッシュ)
D3D12_RAYTRACING_GEOMETRY_DESC geometryDesc = {};
geometryDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES;
geometryDesc.Flags = D3D12_RAYTRACING_GEOMETRY_FLAG_OPAQUE; // Any Hitスキップ
geometryDesc.Triangles.VertexBuffer.StartAddress = vertexBuffer->GetGPUVirtualAddress();
geometryDesc.Triangles.VertexBuffer.StrideInBytes = sizeof(Vertex);
geometryDesc.Triangles.VertexCount = vertexCount;
geometryDesc.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT;
geometryDesc.Triangles.IndexBuffer = indexBuffer->GetGPUVirtualAddress();
geometryDesc.Triangles.IndexCount = indexCount;
geometryDesc.Triangles.IndexFormat = DXGI_FORMAT_R32_UINT;

D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS blasInputs = {};
blasInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL;
blasInputs.Flags = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE;
blasInputs.NumDescs = 1;
blasInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
blasInputs.pGeometryDescs = &geometryDesc;

// メモリ要件の取得
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO blasPrebuildInfo = {};
device->GetRaytracingAccelerationStructurePrebuildInfo(&blasInputs, &blasPrebuildInfo);

// スクラッチバッファ作成(複数BLASで共有可能)
ComPtr<ID3D12Resource> scratchBuffer;
CD3DX12_RESOURCE_DESC scratchDesc = CD3DX12_RESOURCE_DESC::Buffer(
    blasPrebuildInfo.ScratchDataSizeInBytes,
    D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS,
    D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT
);

device->CreateCommittedResource(
    &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
    D3D12_HEAP_FLAG_NONE,
    &scratchDesc,
    D3D12_RESOURCE_STATE_COMMON,
    nullptr,
    IID_PPV_ARGS(&scratchBuffer)
);

// BLASバッファ作成
ComPtr<ID3D12Resource> blasBuffer;
CD3DX12_RESOURCE_DESC blasDesc = CD3DX12_RESOURCE_DESC::Buffer(
    blasPrebuildInfo.ResultDataMaxSizeInBytes,
    D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS
);

device->CreateCommittedResource(
    &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
    D3D12_HEAP_FLAG_NONE,
    &blasDesc,
    D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE,
    nullptr,
    IID_PPV_ARGS(&blasBuffer)
);

// BLASビルド
D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC blasBuildDesc = {};
blasBuildDesc.Inputs = blasInputs;
blasBuildDesc.ScratchAccelerationStructureData = scratchBuffer->GetGPUVirtualAddress();
blasBuildDesc.DestAccelerationStructureData = blasBuffer->GetGPUVirtualAddress();

commandList->BuildRaytracingAccelerationStructure(&blasBuildDesc, 0, nullptr);

// UAVバリア(BLASビルド完了待機)
CD3DX12_RESOURCE_BARRIER uavBarrier = CD3DX12_RESOURCE_BARRIER::UAV(blasBuffer.Get());
commandList->ResourceBarrier(1, &uavBarrier);

NVIDIA Technical Blog(2025年11月)によれば、スクラッチバッファを複数のBLASビルド間で再利用することで、メモリ使用量を最大70%削減できます。

TLASの動的更新

キャラクターや車両などの動的オブジェクトは、TLASインスタンス変換行列の更新のみで対応します。BLASの再ビルドは不要です。

// インスタンス記述配列(Upload Heap)
std::vector<D3D12_RAYTRACING_INSTANCE_DESC> instances(numInstances);

for (uint32_t i = 0; i < numInstances; ++i) {
    // 変換行列(3x4行列を転置して格納)
    DirectX::XMMATRIX transform = objectTransforms[i];
    DirectX::XMFLOAT3X4 transformFloat;
    DirectX::XMStoreFloat3x4(&transformFloat, DirectX::XMMatrixTranspose(transform));
    
    memcpy(instances[i].Transform, &transformFloat, sizeof(instances[i].Transform));
    instances[i].InstanceID = i; // SV_InstanceIDで取得可能
    instances[i].InstanceMask = 0xFF;
    instances[i].InstanceContributionToHitGroupIndex = i * numGeometriesPerInstance;
    instances[i].Flags = D3D12_RAYTRACING_INSTANCE_FLAG_NONE;
    instances[i].AccelerationStructure = blasBuffers[objectMeshIndices[i]]->GetGPUVirtualAddress();
}

// インスタンスバッファへコピー
memcpy(instanceBufferMappedData, instances.data(), instances.size() * sizeof(D3D12_RAYTRACING_INSTANCE_DESC));

// TLAS更新ビルド(初回以降)
D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS tlasInputs = {};
tlasInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL;
tlasInputs.Flags = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_ALLOW_UPDATE | 
                   D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_BUILD;
tlasInputs.NumDescs = numInstances;
tlasInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY;
tlasInputs.InstanceDescs = instanceBuffer->GetGPUVirtualAddress();

D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC tlasBuildDesc = {};
tlasBuildDesc.Inputs = tlasInputs;
tlasBuildDesc.ScratchAccelerationStructureData = scratchBuffer->GetGPUVirtualAddress();
tlasBuildDesc.DestAccelerationStructureData = tlasBuffer->GetGPUVirtualAddress();
tlasBuildDesc.SourceAccelerationStructureData = tlasBuffer->GetGPUVirtualAddress(); // 更新モード

commandList->BuildRaytracingAccelerationStructure(&tlasBuildDesc, 0, nullptr);

2025年12月のAMD FidelityFX SDK 1.1.1ドキュメントによれば、TLAS更新(Update)は再ビルド(Build)と比較して平均2.5倍高速です。

Inline Ray Tracingによるハイブリッドレンダリング

DXR 1.1で導入されたInline Ray Tracingは、Compute ShaderやPixel Shaderから直接RayQueryオブジェクトを使用してレイトレーシングクエリを実行できる機能です。2025年11月のWindows 11 24H2アップデートで最適化され、従来のRTPSO方式と比較して最大40%のCPUオーバーヘッド削減が実現されました。

以下は、Compute ShaderでのInline Ray Tracing実装例です:

// グローバルリソース
RaytracingAccelerationStructure g_Scene : register(t0);
RWTexture2D<float4> g_Output : register(u0);
ConstantBuffer<SceneConstants> g_SceneConstants : register(b0);

[numthreads(8, 8, 1)]
void CSMain(uint3 dispatchThreadID : SV_DispatchThreadID)
{
    uint2 pixelCoord = dispatchThreadID.xy;
    
    // レイの生成
    float2 uv = (pixelCoord + 0.5) / float2(g_SceneConstants.screenWidth, g_SceneConstants.screenHeight);
    float2 ndc = uv * 2.0 - 1.0;
    ndc.y = -ndc.y; // Y軸反転
    
    float3 rayOrigin = g_SceneConstants.cameraPosition;
    float3 rayDir = normalize(
        ndc.x * g_SceneConstants.cameraRight * g_SceneConstants.aspectRatio +
        ndc.y * g_SceneConstants.cameraUp +
        g_SceneConstants.cameraForward
    );
    
    // RayQueryオブジェクト作成
    RayQuery<RAY_FLAG_CULL_BACK_FACING_TRIANGLES> rayQuery;
    
    // レイ記述子
    RayDesc ray;
    ray.Origin = rayOrigin;
    ray.Direction = rayDir;
    ray.TMin = 0.001;
    ray.TMax = 10000.0;
    
    // レイトレーシング実行(同期的)
    rayQuery.TraceRayInline(
        g_Scene,
        RAY_FLAG_CULL_BACK_FACING_TRIANGLES,
        0xFF, // InstanceMask
        ray
    );
    
    // トラバース処理
    while (rayQuery.Proceed())
    {
        // カスタム交差判定の場合のみここに到達
        if (rayQuery.CandidateType() == CANDIDATE_NON_OPAQUE_TRIANGLE)
        {
            // アルファテスト等を実装
            rayQuery.CommitNonOpaqueTriangleHit();
        }
    }
    
    // ヒット情報取得
    float3 color = float3(0.1, 0.3, 0.6); // デフォルト背景色
    
    if (rayQuery.CommittedStatus() == COMMITTED_TRIANGLE_HIT)
    {
        // ヒット情報
        float2 barycentrics = rayQuery.CommittedTriangleBarycentrics();
        uint instanceID = rayQuery.CommittedInstanceID();
        uint primitiveIndex = rayQuery.CommittedPrimitiveIndex();
        float hitT = rayQuery.CommittedRayT();
        
        // 簡易シェーディング(法線ベース)
        float3 hitPosition = rayOrigin + rayDir * hitT;
        float3 geometryNormal = rayQuery.CommittedObjectToWorld3x4()[2].xyz; // Z軸
        
        float ndotl = saturate(dot(geometryNormal, g_SceneConstants.lightDirection));
        color = float3(0.8, 0.8, 0.8) * ndotl + float3(0.2, 0.2, 0.2); // Lambert + Ambient
    }
    
    g_Output[pixelCoord] = float4(color, 1.0);
}

Inline Ray Tracingの主な利点は以下の通りです:

  • シェーダーバインディングテーブル不要: SBTの構築・管理コストがゼロ
  • 柔軟な制御フロー: ループや条件分岐内でレイトレーシングを実行可能
  • 既存パイプラインとの統合容易性: ラスタライゼーションとレイトレーシングのハイブリッド実装が簡潔

2026年2月のIntel Arc Graphics Developer Guideによれば、Ambient Occlusion(AO)やShadowのような単純なレイトレーシング処理では、Inline Ray Tracingが従来方式より30〜50%高速です。

パフォーマンス最適化とプロファイリング戦略

レイトレーシングパイプラインの性能最適化には、PIX for Windows(2026年3月版 v2403.18)やNsight Graphics(2025年12月版 2025.6.0)などのプロファイリングツールが不可欠です。

以下のダイアグラムは、レイトレーシングパフォーマンスボトルネックの診断フローを示しています。

flowchart TD
    A["フレーム時間計測"] --> B{"ボトルネック特定"}
    B -->|AS構築時間 > 5ms| C["加速構造ビルド最適化"]
    B -->|TraceRay時間 > 10ms| D["レイトレーシング実行最適化"]
    B -->|シェーダー時間 > 8ms| E["シェーダー最適化"]
    
    C --> C1["BLAS更新 → 差分更新に変更"]
    C --> C2["TLAS再ビルド頻度削減"]
    C --> C3["ジオメトリフラグ最適化<br/>(OPAQUE設定)"]
    
    D --> D1["ペイロードサイズ削減<br/>(16バイト以下)"]
    D --> D2["最大再帰深度削減<br/>(3以下推奨)"]
    D --> D3["レイ本数削減<br/>(適応的サンプリング)"]
    
    E --> E1["Any Hit Shader削除<br/>(OPAQUEフラグ使用)"]
    E --> E2["ローカルメモリアクセス削減"]
    E --> E3["Inline Ray Tracing検討"]
    
    style C fill:#e24a4a
    style D fill:#4a90e2
    style E fill:#e2a44a

重要な最適化ポイント

  1. ペイロードサイズの削減

    • 16バイト以下に抑えることでレジスタスピルを回避(NVIDIA推奨値)
    • 複雑なデータはUAVバッファ経由でアクセス
  2. 再帰深度の制限

    • プライマリレイ + 反射1回 + GI1回で深度3が実用的な上限
    • 深度が1増えるごとに性能が平均20%低下(AMD測定値)
  3. OPAQUEフラグの積極的使用

    • D3D12_RAYTRACING_GEOMETRY_FLAG_OPAQUE指定でAny Hit Shaderをスキップ
    • 不透明オブジェクトで平均15%の性能向上(Intel測定値)
  4. 適応的サンプリング

    • フレーム間の差分が小さい領域ではレイ本数を削減
    • Temporal Denoisingと組み合わせて品質維持

PIX for Windowsでの計測例:

// PIXイベントマーカー
PIXBeginEvent(commandList, PIX_COLOR_INDEX(1), "Ray Tracing Pass");

// タイムスタンプクエリ開始
commandList->EndQuery(queryHeap.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 0);

// レイトレーシング実行
commandList->SetComputeRootSignature(globalRootSignature.Get());
commandList->SetPipelineState1(rtpso.Get());
commandList->DispatchRays(&dispatchDesc);

// タイムスタンプクエリ終了
commandList->EndQuery(queryHeap.Get(), D3D12_QUERY_TYPE_TIMESTAMP, 1);

PIXEndEvent(commandList);

// クエリ結果解決
commandList->ResolveQueryData(
    queryHeap.Get(),
    D3D12_QUERY_TYPE_TIMESTAMP,
    0, 2,
    queryResultBuffer.Get(),
    0
);

2026年1月のMicrosoft DirectX Developer Blogによれば、PIX for Windowsのレイトレーシング専用ビューアー機能(v2401以降)を使用することで、BVHトラバース効率の可視化が可能になり、最適化時間を平均40%短縮できます。

まとめ

DirectX 12レイトレーシングパイプラインの実装において、2026年最新の知見をまとめると以下の通りです:

  • RTPSO構築: サブオブジェクトの適切な設定と、ペイロードサイズ16バイト以下の厳守が性能の鍵
  • SBT最適化: レコードサイズ64バイト、ローカルルート引数はポインタのみ、アライメント要件の遵守が必須
  • 加速構造管理: 静的BLASはPREFER_FAST_TRACE、動的TLASはALLOW_UPDATEで更新コスト削減
  • Inline Ray Tracing: AOやシャドウなどの単純なクエリでは従来方式より30〜50%高速、SBT管理不要の利点
  • プロファイリング: PIX for WindowsまたはNsight Graphicsでボトルネック特定、ペイロード・再帰深度・OPAQUEフラグが最優先最適化項目

2025年11月のWindows 11 24H2アップデート以降、Inline Ray Tracingの最適化により、ラスタライゼーションとレイトレーシングのハイブリッドレンダリングが実用的な選択肢となりました。今後のゲーム開発では、フルレイトレーシングではなく、重要な視覚効果(反射・GI・AO)のみにレイトレーシングを適用する戦略が主流になると予測されます。

適切な実装と最適化により、RTX 4060クラスのGPUでも1080p/60fpsでのリアルタイムレイトレーシングが十分達成可能な時代になっています。

参考リンク

#DirectX12 #レイトレーシング #DXR #GPU最適化 #グラフィックスAPI
シェア: