BLOGブログ

2026.02.25UE5UEUE/ C++UE/ Plugin

[UE5] レイトレシェーダーを追加してみよう

執筆バージョン: Unreal Engine 5.7.1

今回はピクセルシェーダーやコンピュートシェーダーでレイトレを行うインラインレイトレーシングと通常のレイトレシェーダーを使ったレイトレーシングの実装方法について解説を行います。

学べる内容

  • ピクセルシェーダーでインラインレイトレーシングを行う方法
  • レイトレーシングシェーダーでマテリアルの情報を扱う方法
  • レイトレーシングシェーダーでPrimitiveDataの情報を扱う方法

主にシェーダーの中身の解説というより、どうやってレイトレシェーダーを使うのかセットアップの部分を中心に解説します。

インラインレイトレーシングはレイトレーシングの一部の機能が使用できないかわりに、ピクセルシェーダーやコンピュートシェーダーでレイトレを実行することができます。そのため既存の実装に組み込みやすく、通常のシェーダーを追加したことがある人ならすぐに使用することができます。
インラインレイトレーシングと通常のレイトレーシングの違いは以下のページを参考にしてください。

Unreal Engine でのレイ トレーシング パフォーマンス ガイド | Unreal Engine 5.7 ドキュメンテーション | Epic Developer Community

完全なコードは以下のリポジトリを参照してください。
GitHub – historia-Inc/CustomRaytracingShader

基本的な手順は通常のグローバルシェーダーを追加する手順と同じため、追加したことが無い方は以下の公式ドキュメントを参考にしてください。
Unreal Engine にグローバル シェーダを追加する | Unreal Engine 5.7 ドキュメンテーション | Epic Developer Community

エンジン改造は不要です。

1: セットアップとViewExtensionの作成

C++プロジェクトを作成します。
新規プラグインを作成
Blankを選択して、適当な名前をつけて作成

ViewExtensionをプラグインに追加

ViewExtensionをプラグインに追加します。このクラスを使ってシェーダーの呼び出しを行います。
FSceneViewExtensionBaseを継承したクラスを作成します。
今回は一番実装が簡単なPrePostProcessPass_RenderThreadをオーバーライドしてポストプロセスの前にパスを挿入します。

SimpleShadowViewExtension.h

SimpleShadowViewExtension.cpp

ViewExtensionの初期化処理

次はViewExtensionの初期化処理をプラグインモジュールに実装します。

CustomRaytracingShader.h

CustomRaytracingShader.cpp

2: インラインレイトレーシングの実装

次にシェーダーを定義します。
まずは実装が簡単なインラインレイトレーシングから実装していくため、ピクセルシェーダーを作成します。このあたりまでは通常のシェーダーと手順が全く同じです。

シェーダーの定義とパスの実装

シェーダーの実装です。シェーダー本体は非常に簡単なレイトレシャドウの実装をします。 CustomRaytracingShader.cppのShaderDirectoryで定義したフォルダに配置します。 今回の場合は(.uprojectのあるディレクトリ)/Plugins/CustomRaytracingShader/Shaders/SimpleShadow.usf

SimpleShadowViewExtension.cpp

SimpleShadow.usf

中身はとりあえず適当で良いです。cpp側で定義したSimpleShadowPSという名前の関数が存在していれば問題なく動くはずです。一旦仮で、シェーダーが呼び出されているか確認するため画面が真っ赤になる様にしました。

Build.csの設定

Build.csに必要なモジュールを追加します。
PrivateIncludePathsに普段見慣れない記述がありますが、シェーダー関連でプライベートなヘッダーファイルをインクルードする必要があるためこれが必要になります。

CustomRaytracingShader.Build.cs

プラグインのローディングフェーズ変更

プラグインのローディングフェーズをPostConfigInitに変更します。

CustomRaytracingShader.uplugin

ここまでで一度ビルドして実行してみます。画面が真っ赤になっていれば正常にシェーダーが呼び出されています。

レイトレ情報のバインド

一度エディターを閉じて実装に戻ります。
レイトレをするには専用のシーン情報が必要です。
TLASというレイトレに必要なシーン情報をシェーダーにバインドします。
シェーダーでView.〇〇系の関数を使用できるようにViewUniformBufferも追加します。

SimpleShadowViewExtension.cpp

UE5.7からGetLayerViewの引数が変わってリンカーエラーが出るようになったため、引数で渡しているView.GetRayTracingSceneViewHandle()の中で使われているGetPrimaryViewの実装をファイルの下の方に追加しておく必要があります。

シェーダーでのレイ飛ばし実装

次にシェーダー側でレイを飛ばします。
基本的にはBPで使うLineTraceのようなものをイメージしてもらえればわかりやすいと思います。
まず、どの方向にレイを飛ばすかRayDescで設定します。

SimpleShadow.usf

次にRayQueryを使ってレイを飛ばします。TraceRayInlineがLineTrace関数、RayQueryがLineTraceの結果が入ったHitResultのようなイメージです。
TraceRayInlineの引数はTLAS(レイトレ用のシーン情報)、CullingModeのフラグ(RAY_FLAG_NONEはカリング無効)、InstanceInclusionMask(何に対してレイを飛ばすか?)、RayDesc(どこにレイを飛ばすか?)

SimpleShadow.usf

3番目の引数InstanceInclusionMaskは
Engine\Shaders\Shared\RayTracingDefinitions.hに定義が書かれています。今回使用したRAY_TRACING_MASK_OPAQUEは不透明のメッシュに対してのみ当たり判定を取ります。
レイを飛ばした結果はRayQueryから取得することが出来ます。取得できる内容は以下のファイルに定義されています。
Engine\Shaders\Private\RayTracing\TraceRayInline.ush
とりあえず当たったかどうか判定して、当たったらその距離を色にするようにしてみます。

SimpleShadow.usf

SimpleShadow.usf全体

SimpleShadow.usf

一旦ここまででレイトレが動くかどうか確認してみます。

レイトレの有効化確認

エディターを起動する前にエンジン設定を変更してレイトレシャドウを有効化します。レイトレするために必要です。

DefaultEngine.ini

この状態でエディターを起動すると、赤色のシーンデプスのような表示になっていると思います。
レイトレが動いていることを確認したらエディターを閉じて、シンプルなレイトレシャドウのシェーダーを実装していきます。

シンプルなレイトレシャドウの実装

まずSceneColorを取得するためにシェーダーにパラメーターを追加します。

SimpleShadowViewExtension.cpp

次はレイトレシャドウについて簡単な解説を行います。レイトレシャドウはシャドウマップなどと違い非常にシンプルでわかりやすいです。
まずカメラから全ピクセルに対してレイを飛ばします。(画像では代表的な2本のみ表示)
次にレイが衝突したところからライトの方向にレイを飛ばします。
レイが衝突しなければライトを遮るものが何も無いということなので影ではありません。
レイが衝突した場合は、ライトを遮るものがあるので影になります。
ディレクショナルライトのみになりますが、これだけで影の判定が行えます。

シェーダーを実装します。

SimpleShadow.usf

シェーダーはエディターを起動したまま変更することが出来ます。シェーダーファイルを保存したらエディターで以下のコマンドを入力します。このコマンドでシェーダーを更新することが出来ます。

シェーダーを更新したらエディターで影の部分が赤色になっていれば正しく計算できています。
非常にシンプルなレイトレシャドウの実装を行いました。
しかし、インラインレイトレーシングではマテリアルの情報を取得できないため、「特定のマテリアルだけに影を落としたい」のようなことが実現できません。(GBufferに映っている範囲であれば可能)
画面外のオブジェクトのマテリアルを取得するにはレイトレシェーダーを使用する必要があります。

3: レイトレシェーダーでレイトレ

まずは、先程実装したインラインレイトレーシングは使用しないためコンソールコマンドで無効化できるようにします。

コンソールコマンドによる切り替え実装

SimpleShadowViewExtension.cpp

これで先程実装したシェーダーはデフォルトで無効化されます。
有効化するときはコンソールコマンドにr.Raytracing.CustomInlineSimpleShadow.Enable 1を入力します。

レイトレシェーダーの定義

それではレイトレシェーダーを実装していきます。
レイトレはPixelShaderやVertexShaderの様にレイトレシェーダーというものがあるわけでななく、RayGenerationShader、ClosestHitShader, MissShaderなどの複数のシェーダーから構成されています。
Direct3D 12 レイトレーシング HLSL シェーダー – Win32 apps

それぞれ自前で実装することも可能ですが、今回はエンジンが用意しているMaterialRayTracingというパイプラインを使用するため実装するのはRayGenerationShaderだけです。MaterialRayTracingを使用することで自分でヒットシェーダーを作成する必要がなく、マテリアルシェーダーの値を参照することができます。
PixelShaderのときと同じ様にシェーダーを定義します。

マクロがPixelShaderの時と異なるため注意
SHADER_USE_ROOT_PARAMETER_STRUCT
エンジンが用意しているパイプラインを使用するためバインドするパラメーターも同じにしなければいけません。(追加のパラメーターをバインドするのは大丈夫)
また、PixelShaderと違いレンダーターゲットをバインドしてシェーダー側の引数から受け取れないため、出力用のテクスチャもバインドします。

SimpleShadowViewExtension.cpp

レイトレシェーダーのパス構築

インラインレイトレーシングではAddDrawScreenPassで簡単にパスを挿入できましたが、通常のレイトレにはそのような便利な関数は用意されていなかったためAddPassを使用してパスを挿入します。ラムダ式の中でパイプラインを作って最後にRayTraceDispatchでパスが挿入できます。コンピュートシェーダーの様に実行する数を設定します。
また、出力用のテクスチャ作成とコピーの処理も必要です。

SimpleShadowViewExtension.cpp

RayGenerationシェーダーの実装

次はシェーダー側の実装です。
インラインレイトレーシングで使用したファイルと同じファイル内にRayGenerationシェーダーを追加します。RAY_TRACING_ENTRY_RAYGENというマクロを使用して関数を定義します。
とりあえず実装は画面全体を真っ赤にするだけにしておきます。

SimpleShadow.usf

パイプラインへのシェーダー登録

このエンジンが用意しているパイプラインが作成される際に、使用したいシェーダーが登録されていないとそのシェーダーは使用できないためシェーダーをパイプラインに追加する処理を作ります。

GIPluginというグローバルイルミネーションの拡張用のデリゲートを使用してシェーダーを登録します。グローバルイルミネーションを実装するわけではないため若干ハック的なやり方かもしれません。
この作業は自作したヒットシェーダー、ミスシェーダーを使う場合は不要です。

CustomRaytracingShader.h

CustomRaytracingShader.cpp
StartupModuleでバインドします。

CustomRaytracingShader.cpp

SimpleShadowViewExtension.h

SimpleShadowViewExtension.cpp
エンジンが用意しているパイプラインに自作したシェーダーの登録を行います。

SimpleShadowViewExtension.cpp

レイトレシェーダーの動作確認

ここまでで一度ビルドしてエディターを起動してみます。
起動できたら以下のコマンドを入力して画面が真っ赤になるか確認します。

ちゃんと反映されているか色を変更してみます。

SimpleShadow.usf

このコマンドでシェーダーを更新します。

色が黄色に変わっていれば正常に機能しています。

マテリアル情報を利用した実装

それではこちらも影のシェーダーを実装していきます。基本的には先程と同じですが、使用する関数が一部異なります。レイトレを実行する関数はTraceMaterialRayPackedを使用します。これを使用することでレイトレの結果にマテリアルの情報が含まれるようになります。

ペイロードになんのデータが含まれているかは以下のファイルを参考にすると良いと思います。
\Engine\Shaders\Private\RayTracing\RayTracingCommon.ush

インラインレイトレーシングで実装した内容と同じことを実装します。

SimpleShadow.usf

シェーダーファイルを保存してrecompileshaders changedで更新します。正常に実装できていればインラインレイトレーシングのときと同じ影が表示されるはずです。
ここまではインラインレイトレーシングと同じように見えますが、最大の違いは画面外のマテリアルを取得できるかどうかです。
試しに影の判定の部分に条件を追加してみましょう。

SimpleShadow.usf

金属マテリアルのときだけ影が落ちるようになりました。
GBufferを使ったときとは違い、オブジェクトが画面外に出ても問題なく判定できていることがわかります。

PrimitiveDataを取得する方法

ここまででマテリアルのデータを読み取ることは出来ました。ここからはメッシュ単位のデータにアクセスする方法を紹介します。
メッシュ単位のデータとは、メッシュの座標や回転などのデータに加え、ユーザー自由に設定できるCustomPrimitiveDataなどがあります。
標準のレイトレマテリアルのレンダリングパイプラインを使用している場合、ペイロードからSceneInstanceIndexという情報が取得できます。これはStaticMeshComponentやSkeletalMeshComopnentなどメッシュ単位で割り振られた番号です。

PrimitiveDataのデータはSceneのバッファーに格納されています。Sceneのバッファーはシェーダークラスを作る際に既にバインド済みなので、C++は変更せずにシェーダーの変更だけで大丈夫です。

SimpleShadowViewExtension.cpp

PrimitiveDataを取得する流れ

はじめにPrimitiveDataを取得する流れを説明します。
残念ながらPrimitiveDataはレイトレを実行したらすぐに取得できるわけではなく、複数のデータを経由する必要があります。

Payload → InstanceID → InstanceSceneData → PrimitiveID → PrimitiveData

レイトレを実行するとヒットしたメッシュのInstanceIDというものが取得できます。これはレイトレ専用のシーン上のIDなのでこれだけではPrimitiveDataは取得できません。
このレイトレシーンのIDからGPUSceneの情報(InstanceSceneData )を取得して、さらにここからC++側でバインドしたSceneの情報が格納されたバッファーにアクセスできるインデックスに変換が必要です。
手順が多いですが、1つずつ行っていきましょう。

InstanceID を取得する方法

レイトレのペイロードにIDを取得するための関数が用意されているのでこれを使用します。
ここで重要なのがトレースする前にPayloadをLumenPayloadに設定する必要がある点です。これを行わないとSceneInstanceIndexが取得出来ません。(常に0が返ってきます)

SimpleShadow.usf

LumenPayloadでトレースすることでGetSceneInstanceIndex()でインスタンスのIDを取得出来ます。

SimpleShadow.usf

GetSceneInstanceIndexという関数名ですが、実際に取得できるのはInstanceIDです。名前はにていますがそれぞれ全く別物ですので詳しい違いについてはこちらのページを参照してください。
Direct3D 12 レイトレーシング HLSL システム値組み込み関数 – Win32 apps

InstanceSceneDataを取得する方法

こちらもIDから取得できるGetInstanceSceneDataという関数が用意されているのでそれを使用します。IDには先程SceneInstanceIndexから取得したデータを使用します。
InstanceSceneDataにはメッシュのTransformやBoundingBoxなどの情報が含まれています。
ここで一度メッシュのForwardVectorを取得してみましょう。わかりにくいですが、LocalToWorldの0番目にForwardVector(X軸)の情報が入っています。
これをShadowColorに入れてみましょう。

SimpleShadow.usf

影の色がForwardVectorになっていることがわかります。

これだけでもかなりできることが増えますが、PrimitiveDataからはInstanceSceneDataよりも多くの情報を取得することが出来ます。
中でも有用なのは好きなデータを渡すことができるCustomPrimitiveDataだと思います。設定の仕方など詳細は以下の公式ドキュメントを参照してください。
プリミティブごとに Unreal Engine マテリアルにカスタム データを保存する | Unreal Engine 5.7 ドキュメンテーション | Epic Developer Community

PrimitiveDataを取得する方法

先程取得したInstanceSceneDataにはPrimitiveIDという情報が含まれています。そこからSceneのバッファーで使用できるIndexに変換する作業が必要です。
PrimitiveDataを取得するためのGetPrimitiveDataという関数も用意されているようですが、クラッシュしてしまうため今回はSceneバッファーから直接取得します。
どのインデックスにどの情報が格納されているかは面倒ですがエンジンコードを確認する必要があります。Engine/Shaders/Private/SceneData.ush:359に定義されているGetPrimitiveDataの関数の中身を参考にします。CustomPrimitiveDataは35~44(35+9の最大9個)に格納されるようです。

SimpleShadow.usf

詳細パネルからCustomPrimitiveDataを設定すると影の色が変更されていることがわかります。

これでメッシュ単位で任意のデータをレイトレシェーダーに渡せるようになったので、「特定のメッシュだけレイトレで処理を行う」などができるようになりました。0~34までにも様々なデータが格納されているので色々活用できそうです。

完全なコードはこちらのリポジトリを参照してください。
GitHub – historia-Inc/CustomRaytracingShader