- 作業環境
- 始まり
- リポジトリ
- 実装内容に興味がない方々向け
- 動作対象(絶対に動くとは言っていない)
- エンジン改造の(暗黙的な)ルール
- 実装
- Engine/Source/Runtime/Renderer/Private/ShadowRendering.h
- Engine/Source/Runtime/Renderer/Private/ShadowRendering.cpp
- Engine/Source/Runtime/Renderer/Private/SceneRendering.h
- Engine/Source/Runtime/Renderer/Private/LightGridInjection.cpp
- Engine/Source/Runtime/Renderer/Private/LightRendering.cpp
- Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.h
- Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cpp
- Engine/Source/Runtime/Renderer/Private/Shadows/ShadowSceneRenderer.h
- Engine/Source/Runtime/Renderer/Private/Shadows/ShadowSceneRenderer.cpp
- Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.h
- Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp
- Engine/Source/Runtime/Renderer/Public/PostProcess/PostProcessMaterialInputs.h
- Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessMaterial.h
- Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessMaterial.cpp
- Engine/Shaders/Private/Common.ush
- Engine/Content/Functions/MF_LightAttenuationFromShadow.uasset
- 動作確認
作業環境
- Windows 10
- Visual Studio 2022
- Visual Studio Code
- Unreal Engine 5.3
始まり
ポストプロセスマテリアルでシャドウマップを使えるようにする改造を過去にしました。
改造を施した当時のバージョンは5.0.3でした。
知っている方もいるかもしれませんが、5.0.3は改造非推奨バージョンに含まれます。
Unreal Engine 4系から5系への大幅アップデートが初期段階で、高頻度で修正コミットされていたことが理由ですね。筆者さんは待ちきれずに改造をしちゃった訳ですが。(エンジンコードの差分を見るのも結構勉強になるからね。)
そんな不安定なバージョンも過去の話になった今日、レンダリングまわりは5.2 ~ 5.3の間で結構な変更がありました。その結果、当時の改造箇所をマージしても動作不良を起こしていました。(実際に動かしてはいないが、愚直にマージした程度では動くはずもないだろうなぁくらいの変更量はあった。)
最近、個人プロジェクトを5.2から5.3にアップデートしたということもあり、まぁタイミングが良かったので、ちょっくら5.3対応しちゃいます。(ポストエフェクトでざっくりシェーディングをしたい時に本改造が意外と有用なのよ。)
実装内容に興味がない方々向け
「実装に興味はないわ、動けばいいんだよ」という方々は、githubのリリース機能から UnrealEngine-[version]-PPI-ShadowMap.zip をダウンロードして、WinMergeで差分を取り込んで下さい。ディレクトリは合わせているので簡単に取り込めると思います。
動作対象(絶対に動くとは言っていない)
- 対応
- Unreal Engine 5.3
- Shadow MapとVirtual Shadow Map(以降「VSM」と表記。)の両対応
- VSMは動作確認甘いのでクラッシュする可能性も
- ディレクショナルライトのみ対応
- 複数ビューやScene Capture、Scene Capture Cubeの対応
- 動作確認甘いのでクラッシュする可能性も
- 非対応
エンジン改造の(暗黙的な)ルール
エンジン改造を施した箇所にはコメントを残します。
以下はあくまで筆者さんがよく使うフォーマットの説明です。このフォーマットに沿う必要はありませんが、絶対にコメントだけは残してください。書かないと引継ぎする際にキレると思います。(書いていてもエンジン改造はキレるレベル。毎回過去の自分に怒ってる。)
既存の実装に追加で書き込む場合にはディレクティブでオリジナルコードを残すようにしています。残しておくとエンジンのバージョンアップ時にWinMergeする負担がかなり軽減できます。
//// ReinCarnation @kobayashi-arata 2023/12/27 //// #if 0 RenderLights(GraphBuilder, SceneTextures, TranslucencyLightingVolumeTextures, LightingChannelsTexture, SortedLightSet); #else RenderLights(GraphBuilder, SceneTextures, TranslucencyLightingVolumeTextures, LightingChannelsTexture, SortedLightSet, &ShadowProjectionResourceMap); #endif //// ReinCarnation @kobayashi-arata 2023/12/27 ////
完全に新規で実装、挿入する場合にはディレクティブは不要です。でもコメントは残しましょう。
//// ReinCarnation @kobayashi-arata 2023/12/27 //// #include "ShadowRendering.h" //// ReinCarnation @kobayashi-arata 2023/12/27 ////
実装
前回は複数ビューの対応をしていませんでしたが、今回はやっていきましょうか。勉強がてらに。
実装はgithubのURLを添付していますが、サイト負荷になってしまうのでReleasesからダウンロードしてローカルで閲覧してください。行数もコメントしてあるので特に不自由ないと思います。(そもそもエンジン改造という行為自体が不自由そのもの。)
Engine/Source/Runtime/Renderer/Private/ShadowRendering.h
シャドウ関数内で作成したShadowMapテクスチャリソースのコピーしたものを格納するための構造体を定義しています。
FShadowProjectionPassResourcesMap
はTInlineAllocator<4>
で確保しているため、4つまでのビューはメモリの動的確保が省略されます。半透明の実装を流用しただけですね。
構造体名 | 説明 |
---|---|
FShadowProjectionPassResources |
シャドウマップリソースとビューのサイズ、VSM(Virtual Shadow Map)のパラメータ |
FShadowProjectionPassResourcesMap |
FShadowProjectionPassResources をViews分保持している |
FShadowProjectionViewResourcesMap |
ポストプロセス関数に渡す際にconst修飾子を付けたいやつ |
Engine/Source/Runtime/Renderer/Private/ShadowRendering.cpp
ShadowMapのShadowMapをコピーしています(笑)。
Virtual Shadow MapのShadowMap、Shadow MapのShadowMap、間違っていないのですが、VSMが登場したことにより、区別をつけるとこういう書き方になるのですよね。違和感やばい。やっぱり笑うわ。
githubで実装を見る(L2020-L2027)
githubで実装を見る(L2031-L2034)
githubで実装を見る(L2082-L2084)
githubで実装を見る(L2089-L2091)
githubで実装を見る(L2097-L2109)
シャドウ関数にFShadowProjectionPassResourcesMap
引数を追加しています。
githubで実装を見る(L2119-L2126)
githubで実装を見る(L2170-L2177)
ShadowMapとVSMをコールにFShadowProjectionPassResourcesMap
引数を追加しています。
githubで実装を見る(L2334-L2341)
githubで実装を見る(L2353-L2361)
モバイルは非対応なためnullptr
を渡しています。
Engine/Source/Runtime/Renderer/Private/SceneRendering.h
ShadowMapの関数にFShadowProjectionPassResourcesMap
の引数を追加しています。
githubで実装を見る(L91-93)
githubで実装を見る(L2305-2312)
githubで実装を見る(L2321-2328)
Engine/Source/Runtime/Renderer/Private/LightGridInjection.cpp
フォワードレンダリングは本改造非対応なためnullptr
を指定します。
デフォルト引数で指定する形でも問題ありません。
記事で触れておきたかったのと、バージョンアップ時に本関数の使用箇所が増えた際、エラーを出力させて追加された場所を把握しておきたかったという理由で指定していないだけなので。
Engine/Source/Runtime/Renderer/Private/LightRendering.cpp
シャドウ関数にFShadowProjectionPassResourcesMap
引数を追加しています。
Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.h
FShadowProjectionPassResourcesMap
は前方宣言します。
5.2あたりからレンダリングまわりのクラスや構造体は定義系ファイルに纏められてヘッダーでのインクルードを減らす動きが見られました。これだけソリューションがデカいと焼け石に水な気もしますが、EpicさんなりにUnityより圧倒的に劣っているビルド時間の改善を頑張っているのでしょう。
ライティングとシャドウ関数にFShadowProjectionPassResourcesMap
引数を追加します。
Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cpp
FShadowProjectionPassResourcesMap
を宣言しています。
ライト関数の引数に渡すことで、作成・コピーしたシャドウマップとパラメータを取得しています。
githubで実装を見る(L3864-L3866)
githubで実装を見る(L3910-L3916)
ポストプロセス関数で扱えるようにFPostProcessingInputs
構造体にセットします。
Engine/Source/Runtime/Renderer/Private/Shadows/ShadowSceneRenderer.h
VSMの関数にFShadowProjectionPassResourcesMap
の引数を追加しています。
Engine/Source/Runtime/Renderer/Private/Shadows/ShadowSceneRenderer.cpp
5.0.3では未対応だったVSMの対応です。
注意点としてVSMは適用される最適化によってはシャドウマップ ScreenShadowMaskTexture
が生成されません。その場合はコピー元が存在しないため、クラッシュするかもしれません。筆者さんがVSMで遊ぶことが少ないため、割と雑な対応です。
Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.h
ポストプロセス関数の引数であるFPostProcessingInputs
にFShadowProjectionViewResourcesMap
を追加します。
インクルードサイズを減らしたい場合は、ポインタに変えてもいいかもしれません。
TranslucentRendering.h
がバリバリにインクルードされていたので、筆者も気にせずShadowRendering.h
をインクルードしています。中途半端にValidate
関数で使用しているのが悪いのでしょうね。そのうち改修入りそうな予感。
githubで実装を見る(L9-L11)
githubで実装を見る(L48-L51)
githubで実装を見る(L57-L59)
Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp
ポストプロセス関数からポストプロセスマテリアル関数を呼ぶ際には、必要なリソース・パラメータをFPostProcessMaterialInputs
に格納しなおしているんですよね。こういう所、意外とまめな実装していますよね。ぶっちゃけFPostProcessingInputs
を渡しちゃってもいい気するのに。Epicさんの気分次第なのかな。
Engine/Source/Runtime/Renderer/Public/PostProcess/PostProcessMaterialInputs.h
前方宣言で使用するためポインタをくっつけています。
Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessMaterial.h
ポストプロセスマテリアルのシェーダーパラメータに、VSMのパラメータのDistanceFadeMAD
とシャドウマップテクスチャとサンプラのLightAttenuationTexture, Sampler
を追加しています。
Unreal Engineはアライメントを自動で整えてくれるため16の倍数を意識せずに定義できるのが嬉しいですね。
Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessMaterial.cpp
ポストプロセスマテリアルパラメータに作成・コピーしたシャドウマップリソースをセットしています。
作成・コピーしたシャドウマップリソースが存在しない場合には、ダミーのWhiteテクスチャをセットすることで影なしになるようにしています。全影にしたい場合はBlackにするだけです。(そんなケースないと思うけど。)
githubで実装を見る(L559-L561)
githubで実装を見る(L565-L567)
githubで実装を見る(L629-L642)
Engine/Content/Functions/MF_LightAttenuationFromShadow.uasset
ポストエフェクトで使用される想定のマテリアル関数です。適当にエンジンコンテンツに配置していますが、場所はどこでもいいです。好きなところに配置してくださいな。
カスタムノードのコードは掲載するとEULA違反になるのでココでは見せられません。ローカルでご確認ください。
動作確認
MF_LightAttenuationFromShadow
を使用したポストプロセスマテリアルを作成してUnlitに影を落として、SceneCaptureのキャプチャ結果をPlaneメッシュにアタッチしたマテリアルに投影してみます。
メインビューとSceneCaptureの両方でポストエフェクトが適用されていることを確認できました。
動作不良な箇所があったらコメントくださいな。気分次第では修正します。
VSMはUnlitマスクしている
VSMだとUnlitに影が落ちないのですが、どうやらVSM側でマスクをしているみたいです。
筆者は負荷的にVSM使っていない民なので、対応するほどの興味は沸きませんでした。