|
執筆バージョン: 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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#pragma once #include "CoreMinimal.h" #include "SceneViewExtension.h" class CUSTOMRAYTRACINGSHADER_API FSimpleShadowViewExtension:public FSceneViewExtensionBase { public: FSimpleShadowViewExtension(const FAutoRegister& AutoRegister); ~FSimpleShadowViewExtension() = default; virtual void PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& InView, const FPostProcessingInputs& Inputs) override; }; |
SimpleShadowViewExtension.cpp
|
1 2 3 4 5 6 7 8 9 10 11 |
#include "SimpleShadowViewExtension.h" FSimpleShadowViewExtension::FSimpleShadowViewExtension(const FAutoRegister& AutoRegister) : FSceneViewExtensionBase(AutoRegister) { } void FSimpleShadowViewExtension::PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& InView, const FPostProcessingInputs& Inputs) { } |
ViewExtensionの初期化処理
次はViewExtensionの初期化処理をプラグインモジュールに実装します。
CustomRaytracingShader.h
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#pragma once #include "Modules/ModuleManager.h" class FSimpleShadowViewExtension; class FCustomRaytracingShaderModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; private: void OnPostEngineInit(); TSharedPtr<FSimpleShadowViewExtension, ESPMode::ThreadSafe> ViewExtension; }; |
CustomRaytracingShader.cpp
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include "CustomRaytracingShader.h" #include "Interfaces/IPluginManager.h" #include "Misc/Paths.h" #include "SimpleShadowViewExtension.h" #define LOCTEXT_NAMESPACE "FCustomRaytracingShaderModule" void FCustomRaytracingShaderModule::StartupModule() { FString ShaderDirectory = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("CustomRaytracingShader"))->GetBaseDir(), TEXT("Shaders")); AddShaderSourceDirectoryMapping(TEXT("/Plugin/CustomRaytracingShader"), ShaderDirectory); FCoreDelegates::OnPostEngineInit.AddRaw(this, &FCustomRaytracingShaderModule::OnPostEngineInit); } void FCustomRaytracingShaderModule::ShutdownModule() { ViewExtension.Reset(); } void FCustomRaytracingShaderModule::OnPostEngineInit() { ViewExtension = FSceneViewExtensions::NewExtension(); } #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FCustomRaytracingShaderModule, CustomRaytracingShader) |
2: インラインレイトレーシングの実装
次にシェーダーを定義します。
まずは実装が簡単なインラインレイトレーシングから実装していくため、ピクセルシェーダーを作成します。このあたりまでは通常のシェーダーと手順が全く同じです。
シェーダーの定義とパスの実装
シェーダーの実装です。シェーダー本体は非常に簡単なレイトレシャドウの実装をします。 CustomRaytracingShader.cppのShaderDirectoryで定義したフォルダに配置します。 今回の場合は(.uprojectのあるディレクトリ)/Plugins/CustomRaytracingShader/Shaders/SimpleShadow.usf
SimpleShadowViewExtension.cpp
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#include "SimpleShadowViewExtension.h" #include "Runtime/Renderer/Internal/PostProcess/PostProcessInputs.h" #include "Runtime/Renderer/Private/ScenePrivate.h" class FSimpleShadowPS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FSimpleShadowPS) SHADER_USE_PARAMETER_STRUCT(FSimpleShadowPS, FGlobalShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FSimpleShadowPS, "/Plugin/CustomRaytracingShader/SimpleShadow.usf", "SimpleShadowPS", SF_Pixel); FSimpleShadowViewExtension::FSimpleShadowViewExtension(const FAutoRegister& AutoRegister) : FSceneViewExtensionBase(AutoRegister) { } void FSimpleShadowViewExtension::PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& InView, const FPostProcessingInputs& Inputs) { const FViewInfo& View = static_cast(InView); const FIntRect PrimaryViewRect = View.ViewRect; FScreenPassTexture SceneColor((*Inputs.SceneTextures)->SceneColorTexture, PrimaryViewRect); const FScreenPassTextureViewport InputViewport(SceneColor); const FScreenPassTextureViewport OutputViewport(InputViewport); TShaderMapRef PixelShader(View.ShaderMap); FSimpleShadowPS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneColor.Texture, ERenderTargetLoadAction::ELoad); AddDrawScreenPass( GraphBuilder, RDG_EVENT_NAME("SimpleShadowPS"), InView, OutputViewport, InputViewport, PixelShader, PassParameters); } |
SimpleShadow.usf
|
1 2 3 4 5 6 7 8 |
#include "/Engine/Private/Common.ush" void SimpleShadowPS( FScreenVertexOutput Input, out float4 OutputColor : SV_Target0) { OutputColor = float4(1.0f, 0.0f, 0.0f, 1.0f); } |
中身はとりあえず適当で良いです。cpp側で定義したSimpleShadowPSという名前の関数が存在していれば問題なく動くはずです。一旦仮で、シェーダーが呼び出されているか確認するため画面が真っ赤になる様にしました。
Build.csの設定
Build.csに必要なモジュールを追加します。
PrivateIncludePathsに普段見慣れない記述がありますが、シェーダー関連でプライベートなヘッダーファイルをインクルードする必要があるためこれが必要になります。
CustomRaytracingShader.Build.cs
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
using System.IO; using UnrealBuildTool; public class CustomRaytracingShader : ModuleRules { public CustomRaytracingShader(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PublicIncludePaths.AddRange( new string[] { } ); PrivateIncludePaths.AddRange( new string[] { Path.Combine(GetModuleDirectory("Renderer"), "Internal"), Path.Combine(GetModuleDirectory("Renderer"), "Private"), } ); PublicDependencyModuleNames.AddRange( new string[] { "Core", } ); PrivateDependencyModuleNames.AddRange( new string[] { "CoreUObject", "Engine", "Slate", "SlateCore", "Projects", "RenderCore", "RHI", "Renderer" } ); } } |
プラグインのローディングフェーズ変更
プラグインのローディングフェーズをPostConfigInitに変更します。
CustomRaytracingShader.uplugin
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "FileVersion": 3, "Version": 1, "VersionName": "1.0", "FriendlyName": "CustomRaytracingShader", "Description": "", "Category": "Other", "CreatedBy": "", "CreatedByURL": "", "DocsURL": "", "MarketplaceURL": "", "SupportURL": "", "CanContainContent": true, "IsBetaVersion": false, "IsExperimentalVersion": false, "Installed": false, "Modules": [ { "Name": "CustomRaytracingShader", "Type": "Runtime", "LoadingPhase": "PostConfigInit" } ] } |
レイトレ情報のバインド
一度エディターを閉じて実装に戻ります。
レイトレをするには専用のシーン情報が必要です。
TLASというレイトレに必要なシーン情報をシェーダーにバインドします。
シェーダーでView.〇〇系の関数を使用できるようにViewUniformBufferも追加します。
SimpleShadowViewExtension.cpp
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
#include "SimpleShadowViewExtension.h" #include "Runtime/Renderer/Internal/PostProcess/PostProcessInputs.h" #include "Runtime/Renderer/Private/ScenePrivate.h" #include "Runtime/Renderer/Private/SceneRendering.h" #if RHI_RAYTRACING class FSimpleShadowPS : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FSimpleShadowPS) SHADER_USE_PARAMETER_STRUCT(FSimpleShadowPS, FGlobalShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_BUFFER_SRV(RaytracingAccelerationStructure, TLAS) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FSimpleShadowPS, "/Plugin/CustomRaytracingShader/SimpleShadow.usf", "SimpleShadowPS", SF_Pixel); #endif FSimpleShadowViewExtension::FSimpleShadowViewExtension(const FAutoRegister& AutoRegister) : FSceneViewExtensionBase(AutoRegister) { } void FSimpleShadowViewExtension::PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& InView, const FPostProcessingInputs& Inputs) { FScene* Scene = InView.Family->Scene->GetRenderScene(); if (!Scene) return; const FRayTracingScene& RayTracingScene = Scene->RayTracingScene; const FViewInfo& View = static_cast(InView); const FIntRect PrimaryViewRect = View.ViewRect; FScreenPassTexture SceneColor((*Inputs.SceneTextures)->SceneColorTexture, PrimaryViewRect); const FScreenPassTextureViewport InputViewport(SceneColor); const FScreenPassTextureViewport OutputViewport(InputViewport); TShaderMapRef PixelShader(View.ShaderMap); FSimpleShadowPS::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->TLAS = RayTracingScene.GetLayerView(ERayTracingSceneLayer::Base, View.GetRayTracingSceneViewHandle()); PassParameters->ViewUniformBuffer = InView.ViewUniformBuffer; PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneColor.Texture, ERenderTargetLoadAction::ELoad); AddDrawScreenPass( GraphBuilder, RDG_EVENT_NAME("SimpleShadowPS"), InView, OutputViewport, InputViewport, PixelShader, PassParameters); } //リンカーエラーが出るためSceneRendering.cppから関数をコピー const FViewInfo* FViewInfo::GetPrimaryView() const { // It is valid for this function to return itself if it's already the primary view. if (Family && Family->Views.IsValidIndex(PrimaryViewIndex)) { const FSceneView* PrimaryView = Family->Views[PrimaryViewIndex]; check(PrimaryView->bIsViewInfo); return static_cast(PrimaryView); } return this; } |
UE5.7からGetLayerViewの引数が変わってリンカーエラーが出るようになったため、引数で渡しているView.GetRayTracingSceneViewHandle()の中で使われているGetPrimaryViewの実装をファイルの下の方に追加しておく必要があります。
シェーダーでのレイ飛ばし実装
次にシェーダー側でレイを飛ばします。
基本的にはBPで使うLineTraceのようなものをイメージしてもらえればわかりやすいと思います。
まず、どの方向にレイを飛ばすかRayDescで設定します。
SimpleShadow.usf
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
... void SimpleShadowPS( FScreenVertexOutput Input, out float4 OutputColor : SV_Target0) { float2 PixelPos = Input.Position.xy; float3 CameraDir; float3 DummyPos; ReconstructTranslatedWorldPositionAndCameraDirectionFromDeviceZ(PixelPos, 0.1f, DummyPos, CameraDir); RayDesc Ray; Ray.Origin = View.TranslatedWorldCameraOrigin; Ray.Direction = CameraDir; Ray.TMin = 0.0; Ray.TMax = 1e20f; ... |
次にRayQueryを使ってレイを飛ばします。TraceRayInlineがLineTrace関数、RayQueryがLineTraceの結果が入ったHitResultのようなイメージです。
TraceRayInlineの引数はTLAS(レイトレ用のシーン情報)、CullingModeのフラグ(RAY_FLAG_NONEはカリング無効)、InstanceInclusionMask(何に対してレイを飛ばすか?)、RayDesc(どこにレイを飛ばすか?)
SimpleShadow.usf
|
1 2 3 4 5 |
... RayQuery q; q.TraceRayInline(TLAS, RAY_FLAG_NONE, RAY_TRACING_MASK_OPAQUE, Ray); q.Proceed(); .. |
3番目の引数InstanceInclusionMaskは
Engine\Shaders\Shared\RayTracingDefinitions.hに定義が書かれています。今回使用したRAY_TRACING_MASK_OPAQUEは不透明のメッシュに対してのみ当たり判定を取ります。
レイを飛ばした結果はRayQueryから取得することが出来ます。取得できる内容は以下のファイルに定義されています。
Engine\Shaders\Private\RayTracing\TraceRayInline.ush
とりあえず当たったかどうか判定して、当たったらその距離を色にするようにしてみます。
SimpleShadow.usf
|
1 2 3 4 5 6 7 8 9 10 |
... if (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) { OutputColor = float4(q.CommittedRayT() / 100, 0.0f, 0.0f, 1.0f); } else { OutputColor = float4(0.0f, 0.0f, 0.0f, 1.0f); } ... |
SimpleShadow.usf全体
SimpleShadow.usf
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include "/Engine/Private/Common.ush" #include "/Engine/Private/RayTracing/RayTracingCommon.ush" RaytracingAccelerationStructure TLAS; void SimpleShadowPS( FScreenVertexOutput Input, out float4 OutputColor : SV_Target0) { float2 PixelPos = Input.Position.xy; float3 CameraDir; float3 DummyPos; ReconstructTranslatedWorldPositionAndCameraDirectionFromDeviceZ(PixelPos, 0.1f, DummyPos, CameraDir); RayDesc Ray; Ray.Origin = View.TranslatedWorldCameraOrigin; Ray.Direction = CameraDir; Ray.TMin = 0.0; Ray.TMax = 1e20f; RayQuery q; q.TraceRayInline(TLAS, RAY_FLAG_NONE, RAY_TRACING_MASK_OPAQUE, Ray); q.Proceed(); if (q.CommittedStatus() == COMMITTED_TRIANGLE_HIT) { OutputColor = float4(q.CommittedRayT() / 100, 0.0f, 0.0f, 1.0f); } else { OutputColor = float4(0.0f, 0.0f, 0.0f, 1.0f); } } |
一旦ここまででレイトレが動くかどうか確認してみます。
レイトレの有効化確認
エディターを起動する前にエンジン設定を変更してレイトレシャドウを有効化します。レイトレするために必要です。
DefaultEngine.ini
|
1 2 |
[/Script/Engine.RendererSettings] r.RayTracing.Shadows=1 |
この状態でエディターを起動すると、赤色のシーンデプスのような表示になっていると思います。
レイトレが動いていることを確認したらエディターを閉じて、シンプルなレイトレシャドウのシェーダーを実装していきます。
シンプルなレイトレシャドウの実装
まずSceneColorを取得するためにシェーダーにパラメーターを追加します。
SimpleShadowViewExtension.cpp
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include "Runtime/Renderer/Private/SceneRendering.h" ... //FSimpleShadowPS BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures) SHADER_PARAMETER_RDG_BUFFER_SRV(RaytracingAccelerationStructure, TLAS) SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer) RENDER_TARGET_BINDING_SLOTS() END_SHADER_PARAMETER_STRUCT() ... //PrePostProcessPass_RenderThread PassParameters->SceneTextures = GetSceneTextureShaderParameters(Inputs.SceneTextures); PassParameters->TLAS = RayTracingScene.GetLayerView(ERayTracingSceneLayer::Base, View.GetRayTracingSceneViewHandle()); PassParameters->ViewUniformBuffer = InView.ViewUniformBuffer; PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneColor.Texture, ERenderTargetLoadAction::ELoad); ... |
次はレイトレシャドウについて簡単な解説を行います。レイトレシャドウはシャドウマップなどと違い非常にシンプルでわかりやすいです。
まずカメラから全ピクセルに対してレイを飛ばします。(画像では代表的な2本のみ表示)
次にレイが衝突したところからライトの方向にレイを飛ばします。
レイが衝突しなければライトを遮るものが何も無いということなので影ではありません。
レイが衝突した場合は、ライトを遮るものがあるので影になります。
ディレクショナルライトのみになりますが、これだけで影の判定が行えます。
シェーダーを実装します。
SimpleShadow.usf
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include "/Engine/Private/Common.ush" #include "/Engine/Private/RayTracing/RayTracingCommon.ush" RaytracingAccelerationStructure TLAS; void SimpleShadowPS( FScreenVertexOutput Input, out float4 OutputColor : SV_Target0) { float2 PixelPos = Input.Position.xy; float3 CameraDir; float3 DummyPos; ReconstructTranslatedWorldPositionAndCameraDirectionFromDeviceZ(PixelPos, 0.1f, DummyPos, CameraDir); RayDesc PrimaryRay; PrimaryRay.Origin = View.TranslatedWorldCameraOrigin; PrimaryRay.Direction = CameraDir; PrimaryRay.TMin = 0.0; PrimaryRay.TMax = 1e20f; RayQuery q_primary; q_primary.TraceRayInline(TLAS, RAY_FLAG_NONE, RAY_TRACING_MASK_OPAQUE, PrimaryRay); q_primary.Proceed(); float3 ShadowColor = float3(1.0f, 0.0f, 0.0f); if (q_primary.CommittedStatus() == COMMITTED_TRIANGLE_HIT) { float HitT = q_primary.CommittedRayT(); float3 HitWorldPos = PrimaryRay.Origin + (PrimaryRay.Direction * HitT); RayDesc ShadowRay; ShadowRay.Origin = HitWorldPos; ShadowRay.Direction = View.DirectionalLightDirection; ShadowRay.TMin = 1.0f; ShadowRay.TMax = 1e20f; RayQuery q_shadow; q_shadow.TraceRayInline(TLAS, RAY_FLAG_NONE, RAY_TRACING_MASK_OPAQUE, ShadowRay); q_shadow.Proceed(); if (q_shadow.CommittedStatus() == COMMITTED_TRIANGLE_HIT) { OutputColor = float4(ShadowColor, 1.0f); return; } } OutputColor = float4(CalcSceneColor(Input.UV), 1.0f); } |
シェーダーはエディターを起動したまま変更することが出来ます。シェーダーファイルを保存したらエディターで以下のコマンドを入力します。このコマンドでシェーダーを更新することが出来ます。
|
1 |
recompileshaders changed |
シェーダーを更新したらエディターで影の部分が赤色になっていれば正しく計算できています。
非常にシンプルなレイトレシャドウの実装を行いました。
しかし、インラインレイトレーシングではマテリアルの情報を取得できないため、「特定のマテリアルだけに影を落としたい」のようなことが実現できません。(GBufferに映っている範囲であれば可能)
画面外のオブジェクトのマテリアルを取得するにはレイトレシェーダーを使用する必要があります。
3: レイトレシェーダーでレイトレ
まずは、先程実装したインラインレイトレーシングは使用しないためコンソールコマンドで無効化できるようにします。
コンソールコマンドによる切り替え実装
SimpleShadowViewExtension.cpp
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
... //インクルードのした static TAutoConsoleVariable CVarInlineShadowEnable( TEXT("r.Raytracing.CustomInlineSimpleShadow.Enable"), 0, TEXT("Enables the Raytracing CustomSimpleShadow. \n Note: Crash if ray tracing shadows are not enabled. \n0: Off, 1: On"), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarShadowEnable( TEXT("r.Raytracing.CustomSimpleShadow.Enable"), 0, TEXT("Enables the Raytracing CustomSimpleShadow. \n Note: Crash if ray tracing shadows are not enabled. \n0: Off, 1: On"), ECVF_RenderThreadSafe ); ... void FSimpleShadowViewExtension::PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& InView, const FPostProcessingInputs& Inputs) { if (CVarInlineShadowEnable.GetValueOnRenderThread()) { //先程実装したコード ... } } |
これで先程実装したシェーダーはデフォルトで無効化されます。
有効化するときはコンソールコマンドに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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
... #include "RayTracingShaderBindingLayout.h" ... class FSimpleShadowRG : public FGlobalShader { public: DECLARE_GLOBAL_SHADER(FSimpleShadowRG) SHADER_USE_ROOT_PARAMETER_STRUCT(FSimpleShadowRG, FGlobalShader) BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, OutputTexture) //出力用のテクスチャ SHADER_PARAMETER_STRUCT_INCLUDE(FSceneTextureShaderParameters, SceneTextures) // SceneColorやGBufferなど SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneUniformParameters, Scene) // レイトレマテリアルのパイプラインとシグネチャを合わせるために必要 SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FNaniteRayTracingUniformParameters, NaniteRayTracing) // レイトレマテリアルのパイプラインとシグネチャを合わせるために必要 SHADER_PARAMETER_RDG_BUFFER_SRV(RaytracingAccelerationStructure, TLAS) //レイトレ用のシーン情報 SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, ViewUniformBuffer) //View.〇〇を使うのに必要 END_SHADER_PARAMETER_STRUCT() static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("RAY_TRACING_PAYLOAD_TYPE"), 0); } // ペイロードにはレイトレマテリアル static ERayTracingPayloadType GetRayTracingPayloadType(const int32 /*PermutationId*/) { return ERayTracingPayloadType::RayTracingMaterial; } // エンジンのバインディングレイアウトを使用する static const FShaderBindingLayout* GetShaderBindingLayout(const FShaderPermutationParameters& Parameters) { return RayTracing::GetShaderBindingLayout(Parameters.Platform); } }; IMPLEMENT_GLOBAL_SHADER(FSimpleShadowRG, "/Plugin/CustomRaytracingShader/SimpleShadow.usf", "SimpleShadowRG", SF_RayGen); |
レイトレシェーダーのパス構築
インラインレイトレーシングではAddDrawScreenPassで簡単にパスを挿入できましたが、通常のレイトレにはそのような便利な関数は用意されていなかったためAddPassを使用してパスを挿入します。ラムダ式の中でパイプラインを作って最後にRayTraceDispatchでパスが挿入できます。コンピュートシェーダーの様に実行する数を設定します。
また、出力用のテクスチャ作成とコピーの処理も必要です。
SimpleShadowViewExtension.cpp
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
... #include "Nanite/NaniteRayTracing.h" #include "RayTracing/RayTracingMaterialHitShaders.h" #include "MaterialShaderType.h" #include "RHIResources.h" ... void FSimpleShadowViewExtension::PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& InView, const FPostProcessingInputs& Inputs) { ... if (CVarShadowEnable.GetValueOnRenderThread()) { FScene* Scene = InView.Family->Scene->GetRenderScene(); if (!Scene) return; const FViewInfo& View = static_cast(InView); const FRayTracingScene& RayTracingScene = Scene->RayTracingScene; const FIntRect PrimaryViewRect = View.ViewRect; FScreenPassTexture SceneColor((*Inputs.SceneTextures)->SceneColorTexture, PrimaryViewRect); //出力用のテクスチャを作成 FIntPoint TextureSize = SceneColor.Texture->Desc.Extent; FRDGTextureDesc OutputDesc = FRDGTextureDesc::Create2D( TextureSize, SceneColor.Texture->Desc.Format, FClearValueBinding::None, TexCreate_ShaderResource | TexCreate_UAV ); FRDGTexture* OutputRDGTexture = GraphBuilder.CreateTexture(OutputDesc, TEXT("SimpleShadow.Output")); FRDGTextureUAV* OutputUAV = GraphBuilder.CreateUAV(OutputRDGTexture); //pass param { TShaderMapRef RayGenerationShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); FSimpleShadowRG::FParameters* PassParameters = GraphBuilder.AllocParameters(); PassParameters->ViewUniformBuffer = InView.ViewUniformBuffer; PassParameters->TLAS = RayTracingScene.GetLayerView(ERayTracingSceneLayer::Base, View.GetRayTracingSceneViewHandle()); PassParameters->Scene = GetSceneUniformBufferRef(GraphBuilder, InView); PassParameters->OutputTexture = OutputUAV; PassParameters->NaniteRayTracing = Nanite::GetPublicGlobalRayTracingUniformBuffer(); FSceneTextures SceneTextures = View.GetSceneTextures(); PassParameters->SceneTextures = GetSceneTextureShaderParameters(Inputs.SceneTextures); GraphBuilder.AddPass( RDG_EVENT_NAME("SimpleShadowRG"), PassParameters, ERDGPassFlags::Compute| ERDGPassFlags::NeverCull, [PassParameters, RayGenerationShader, TextureSize, &View](FRHICommandList& RHICmdList) { if (!View.MaterialRayTracingData.PipelineState) return; //シェーダーのバインドをルートシグネチャと合わせるために必要 FRHIUniformBuffer* SceneUniformBuffer = PassParameters->Scene->GetRHI(); FRHIUniformBuffer* NaniteRayTracingUniformBuffer = PassParameters->NaniteRayTracing->GetRHI(); TOptional StaticUniformBufferScope = RayTracing::BindStaticUniformBufferBindings(View, SceneUniformBuffer, NaniteRayTracingUniformBuffer, RHICmdList); //エンジン組み込みのパイプラインを使用する FRayTracingPipelineState* PipeLine =View.MaterialRayTracingData.PipelineState; FShaderBindingTableRHIRef SBT = View.MaterialRayTracingData.ShaderBindingTable; FRHIBatchedShaderParameters& GlobalResources = RHICmdList.GetScratchShaderParameters(); SetShaderParameters(GlobalResources, RayGenerationShader, *PassParameters); RHICmdList.RayTraceDispatch( PipeLine, RayGenerationShader.GetRayTracingShader(), SBT, GlobalResources, TextureSize.X, TextureSize.Y ); } ); } AddCopyTexturePass(GraphBuilder, OutputRDGTexture, SceneColor.Texture); } } |
RayGenerationシェーダーの実装
次はシェーダー側の実装です。
インラインレイトレーシングで使用したファイルと同じファイル内にRayGenerationシェーダーを追加します。RAY_TRACING_ENTRY_RAYGENというマクロを使用して関数を定義します。
とりあえず実装は画面全体を真っ赤にするだけにしておきます。
SimpleShadow.usf
|
1 2 3 4 5 6 |
... RWTexture2D OutputTexture; RAY_TRACING_ENTRY_RAYGEN(SimpleShadowRG) { OutputTexture[DispatchRaysIndex().xy] = float4(1.0f, 0.0f, 0.0f, 1.0f); } |
パイプラインへのシェーダー登録
このエンジンが用意しているパイプラインが作成される際に、使用したいシェーダーが登録されていないとそのシェーダーは使用できないためシェーダーをパイプラインに追加する処理を作ります。
GIPluginというグローバルイルミネーションの拡張用のデリゲートを使用してシェーダーを登録します。グローバルイルミネーションを実装するわけではないため若干ハック的なやり方かもしれません。
この作業は自作したヒットシェーダー、ミスシェーダーを使う場合は不要です。
CustomRaytracingShader.h
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#pragma once #include "Modules/ModuleManager.h" class FSimpleShadowViewExtension; class FCustomRaytracingShaderModule : public IModuleInterface { public: virtual void StartupModule() override; virtual void ShutdownModule() override; private: void OnPostEngineInit(); TSharedPtr<FSimpleShadowViewExtension, ESPMode::ThreadSafe> ViewExtension; void OnPrepareRayTracing(const class FViewInfo& View, TArray<FRHIRayTracingShader*>& OutRayGenShaders); FDelegateHandle PrepareRayTracingDelegateHandle; }; |
CustomRaytracingShader.cpp
StartupModuleでバインドします。
CustomRaytracingShader.cpp
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include "CustomRaytracingShader.h" #include "Interfaces/IPluginManager.h" #include "Misc/Paths.h" #include "SimpleShadowViewExtension.h" #include "DeferredShadingRenderer.h" #define LOCTEXT_NAMESPACE "FCustomRaytracingShaderModule" void FCustomRaytracingShaderModule::StartupModule() { FString ShaderDirectory = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("CustomRaytracingShader"))->GetBaseDir(), TEXT("Shaders")); AddShaderSourceDirectoryMapping(TEXT("/Plugin/CustomRaytracingShader"), ShaderDirectory); FCoreDelegates::OnPostEngineInit.AddRaw(this, &FCustomRaytracingShaderModule::OnPostEngineInit); PrepareRayTracingDelegateHandle = FGlobalIlluminationPluginDelegates::PrepareRayTracing().AddRaw(this, &FCustomRaytracingShaderModule::OnPrepareRayTracing); } void FCustomRaytracingShaderModule::ShutdownModule() { ViewExtension.Reset(); } void FCustomRaytracingShaderModule::OnPostEngineInit() { ViewExtension = FSceneViewExtensions::NewExtension(); } void FCustomRaytracingShaderModule::OnPrepareRayTracing(const class FViewInfo& View, TArray<FRHIRayTracingShader*>& OutRayGenShaders) { ViewExtension->OnPrepareRayTracing(View, OutRayGenShaders); } #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FCustomRaytracingShaderModule, CustomRaytracingShader) |
SimpleShadowViewExtension.h
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#pragma once #include "CoreMinimal.h" #include "SceneViewExtension.h" class CUSTOMRAYTRACINGSHADER_API FSimpleShadowViewExtension:public FSceneViewExtensionBase { public: FSimpleShadowViewExtension(const FAutoRegister& AutoRegister); ~FSimpleShadowViewExtension() = default; virtual void PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& InView, const FPostProcessingInputs& Inputs) override; void OnPrepareRayTracing(const class FViewInfo& View, TArray<FRHIRayTracingShader*>& OutRayGenShaders); }; |
SimpleShadowViewExtension.cpp
エンジンが用意しているパイプラインに自作したシェーダーの登録を行います。
SimpleShadowViewExtension.cpp
|
1 2 3 4 5 6 7 8 |
... void FSimpleShadowViewExtension::OnPrepareRayTracing(const class FViewInfo& View, TArray<FRHIRayTracingShader*>& OutRayGenShaders) { TShaderMapRef RayGenerationShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); OutRayGenShaders.Add(RayGenerationShader.GetRayTracingShader()); } ... |
レイトレシェーダーの動作確認
ここまでで一度ビルドしてエディターを起動してみます。
起動できたら以下のコマンドを入力して画面が真っ赤になるか確認します。
|
1 |
r.Raytracing.CustomSimpleShadow.Enable 1 |
ちゃんと反映されているか色を変更してみます。
SimpleShadow.usf
|
1 2 3 4 5 |
RWTexture2D OutputTexture; RAY_TRACING_ENTRY_RAYGEN(SimpleShadowRG) { OutputTexture[DispatchRaysIndex().xy] = float4(1.0f, 1.0f, 0.0f, 1.0f); } |
このコマンドでシェーダーを更新します。
|
1 |
recompileshaders changed |
マテリアル情報を利用した実装
それではこちらも影のシェーダーを実装していきます。基本的には先程と同じですが、使用する関数が一部異なります。レイトレを実行する関数はTraceMaterialRayPackedを使用します。これを使用することでレイトレの結果にマテリアルの情報が含まれるようになります。
|
1 2 3 4 5 6 |
Payload.GetBaseColor(); Payload.GetMetallic(); Payload.GetRoughness(); Payload.GetWorldNormal(); Payload.GetBlendingMode(); ... |
ペイロードになんのデータが含まれているかは以下のファイルを参考にすると良いと思います。
\Engine\Shaders\Private\RayTracing\RayTracingCommon.ush
インラインレイトレーシングで実装した内容と同じことを実装します。
SimpleShadow.usf
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
RWTexture2D OutputTexture; RAY_TRACING_ENTRY_RAYGEN(SimpleShadowRG) { uint2 PixelCoord = DispatchRaysIndex().xy + View.ViewRectMin.xy; float2 ScreenUV = PixelCoord * View.BufferSizeAndInvSize.zw; float3 SceneColor = CalcSceneColor(ScreenUV); float3 CameraDir; float3 WorldPos; ReconstructTranslatedWorldPositionAndCameraDirectionFromDeviceZ(PixelCoord, 0.001f, WorldPos, CameraDir); FRayDesc PrimaryRay; PrimaryRay.Origin = View.TranslatedWorldCameraOrigin; PrimaryRay.Direction = CameraDir; PrimaryRay.TMin = 0.0f; PrimaryRay.TMax = 1e20f; FPackedMaterialClosestHitPayload Payload = (FPackedMaterialClosestHitPayload)0; FRayCone RayCone = (FRayCone)0; RayCone.SpreadAngle = View.EyeToPixelSpreadAngle; TraceMaterialRayPacked( Payload, TLAS, RAY_FLAG_FORCE_OPAQUE, RAY_TRACING_MASK_OPAQUE, PrimaryRay, RayCone, false, true ); float3 ShadowColor = float3(1.0f, 0.0f, 0.0f); if (Payload.IsHit()) { float HitT = Payload.HitT; float3 HitWorldPos = PrimaryRay.Origin + (PrimaryRay.Direction * HitT); FPackedMaterialClosestHitPayload ShadowPayload = (FPackedMaterialClosestHitPayload)0; FRayDesc ShadowRay; ShadowRay.Origin = HitWorldPos; ShadowRay.Direction = View.DirectionalLightDirection; ShadowRay.TMin = 1.0f; ShadowRay.TMax = 1e20f; TraceMaterialRayPacked( ShadowPayload, TLAS, RAY_FLAG_FORCE_OPAQUE, RAY_TRACING_MASK_OPAQUE, ShadowRay, RayCone, false, true ); if (ShadowPayload.IsHit()) { OutputTexture[DispatchRaysIndex().xy] = float4(ShadowColor, 1.0f); return; } } OutputTexture[DispatchRaysIndex().xy] = float4(SceneColor, 1.0f); } |
シェーダーファイルを保存してrecompileshaders changedで更新します。正常に実装できていればインラインレイトレーシングのときと同じ影が表示されるはずです。
ここまではインラインレイトレーシングと同じように見えますが、最大の違いは画面外のマテリアルを取得できるかどうかです。
試しに影の判定の部分に条件を追加してみましょう。
SimpleShadow.usf
|
1 2 3 4 5 6 7 |
... if (ShadowPayload.IsHit() && ShadowPayload.GetMetallic() > 0.5f) { OutputTexture[DispatchRaysIndex().xy] = float4(ShadowColor, 1.0f); return; } ... |
金属マテリアルのときだけ影が落ちるようになりました。
GBufferを使ったときとは違い、オブジェクトが画面外に出ても問題なく判定できていることがわかります。
PrimitiveDataを取得する方法
ここまででマテリアルのデータを読み取ることは出来ました。ここからはメッシュ単位のデータにアクセスする方法を紹介します。
メッシュ単位のデータとは、メッシュの座標や回転などのデータに加え、ユーザー自由に設定できるCustomPrimitiveDataなどがあります。
標準のレイトレマテリアルのレンダリングパイプラインを使用している場合、ペイロードからSceneInstanceIndexという情報が取得できます。これはStaticMeshComponentやSkeletalMeshComopnentなどメッシュ単位で割り振られた番号です。
PrimitiveDataのデータはSceneのバッファーに格納されています。Sceneのバッファーはシェーダークラスを作る際に既にバインド済みなので、C++は変更せずにシェーダーの変更だけで大丈夫です。
SimpleShadowViewExtension.cpp
|
1 2 3 4 5 6 7 |
... SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FSceneUniformParameters, Scene) ... PassParameters->Scene = GetSceneUniformBufferRef(GraphBuilder, InView); ... |
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
|
1 2 3 4 5 6 7 8 |
... FPackedMaterialClosestHitPayload Payload = (FPackedMaterialClosestHitPayload)0; Payload.SetLumenPayload(); // LumenPayloadにすることでInstanceIndexが取得できる様になる FRayCone RayCone = (FRayCone)0; RayCone.SpreadAngle = View.EyeToPixelSpreadAngle; TraceMaterialRayPacked( ... |
LumenPayloadでトレースすることでGetSceneInstanceIndex()でインスタンスのIDを取得出来ます。
SimpleShadow.usf
|
1 2 3 4 5 6 |
... if (Payload.IsHit()) { uint InstanceIndex = Payload.GetSceneInstanceIndex(); FInstanceSceneData InstanceData = GetInstanceSceneData(InstanceIndex); ... |
GetSceneInstanceIndexという関数名ですが、実際に取得できるのはInstanceIDです。名前はにていますがそれぞれ全く別物ですので詳しい違いについてはこちらのページを参照してください。
Direct3D 12 レイトレーシング HLSL システム値組み込み関数 – Win32 apps
InstanceSceneDataを取得する方法
こちらもIDから取得できるGetInstanceSceneDataという関数が用意されているのでそれを使用します。IDには先程SceneInstanceIndexから取得したデータを使用します。
InstanceSceneDataにはメッシュのTransformやBoundingBoxなどの情報が含まれています。
ここで一度メッシュのForwardVectorを取得してみましょう。わかりにくいですが、LocalToWorldの0番目にForwardVector(X軸)の情報が入っています。
これをShadowColorに入れてみましょう。
SimpleShadow.usf
|
1 2 3 4 5 6 7 8 |
... if (Payload.IsHit()) { uint InstanceID = Payload.GetSceneInstanceIndex(); FInstanceSceneData InstanceData = GetInstanceSceneData(InstanceID); float3 ForwardVector = normalize(InstanceData.LocalToWorld.M[0].xyz); ShadowColor = ForwardVector; ... |
影の色が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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
... if (Payload.IsHit()) { uint InstanceID = Payload.GetSceneInstanceIndex(); FInstanceSceneData InstanceData = GetInstanceSceneData(InstanceID); const uint PrimitiveId = InstanceData.PrimitiveId ; uint PrimitiveIndex = PrimitiveId * PRIMITIVE_SCENE_DATA_STRIDE; // 参考:Engine/Shaders/Private/SceneData.ush:489 uint ItemIndex = 0; uint TargetIdx = PrimitiveIndex + ItemIndex + 35; const float4 CustomData = Scene.GPUScene.GPUScenePrimitiveSceneData[TargetIdx]; ShadowColor = CustomData.xyz; ... |
詳細パネルからCustomPrimitiveDataを設定すると影の色が変更されていることがわかります。
これでメッシュ単位で任意のデータをレイトレシェーダーに渡せるようになったので、「特定のメッシュだけレイトレで処理を行う」などができるようになりました。0~34までにも様々なデータが格納されているので色々活用できそうです。
完全なコードはこちらのリポジトリを参照してください。
GitHub – historia-Inc/CustomRaytracingShader











