執筆バージョン: Unreal Engine 5.5.4, 5.6.0 Preview
|
Unreal Engineで背景を透過したアプリケーションを作る方法を紹介します。
サンプルコードはこちら: https://github.com/historia-Inc/WindowTransparency
このサンプルコードの内容を以下の4回に分けて解説する予定です。
- 第1回: 背景を透過したアプリケーションを作る方法 (本記事)
- 第2回: 背景を透過した部分だけをクリックスルーする方法
- 第3回: 外部のウィンドウとインタラクションする方法
- 第4回: デスクトップの壁紙として設定する方法
透過処理の基本
Windowsにおけるウィンドウ透過処理の基本的な考え方は、DWM (Desktop Window Manager) APIなどを利用してウィンドウの特定領域を透明に描画させることです。以下の記事やリポジトリで解説されている内容が参考になります。
- デスクトップが透過するアプリ・ライブ壁紙アプリを Unity で作る – Qiita
- GitHub – kirurobo/UniWindowController: Makes your Unity window transparent and allows you to drop files
上記の記事を参考に今回は主にWindowsAPIの呼び出し方やレンダリング設定などUE特有の部分を中心に解説していきます。
1: セットアップ
まずは、WindowsAPIを使用できるようにUnreal Engineプロジェクトをセットアップします。 新規にC++プロジェクトを作成してください。
プロジェクト設定
ウィンドウを透過できるようにAlphaチャンネルの出力等の設定を行います。
- AlphaOutput
- DefaultRHI
- Tearing
Alpha Outputを有効化
プロジェクト設定(Edit > Project Settings…)を開き、「Engine」 > 「Rendering」セクションにある「Alpha Output」にチェックを入れます。これでバックバッファにAlphaチャンネルが書き込まれるようになります。
RenderDocなどのグラフィックスデバッガを使用すれば、バックバッファにアルファが書き込まれているか確認できます。アルファチャンネルが書き込まれていてもウィンドウ設定が正しくないと透過されませんが、問題の切り分けに役立ちます。
Default RHI (レンダリングAPI)
プロジェクト設定の「Platforms」 > 「Windows」 > 「Targeted RHIs」セクションで、「Default RHI」をDirectX11に変更します。今回の手法ではDirectX12では背景を透過することができません。
DirectX11を使用するため、LumenなどDirectX12 (SM6) の機能を必要とする一部のレンダリング機能は使用できなくなります。
Tearingの設定
Config/DefaultEngine.iniファイルを開き、[/Script/Engine.RendererSettings]セクションに以下の行を追加します。
DefaultEngine.ini
1 |
r.D3D11.UseAllowTearing=0 |
この設定はエディターのプロジェクト設定UIからは変更できないため、直接iniファイルを編集する必要があります。
この設定をすることでDXGIを使用したフリップモードを無効化できます。DXGIフリップモードは透明度をサポートしていないため無効化する必要があります。Unityにおける「Use DXGI flip model swapchain for D3D11」設定に該当します。
参考:
モジュールのビルド設定 (Build.cs)
プロジェクトのソースフォルダ内にある[プロジェクト名].Build.csファイル(例: MyProject.Build.cs)に設定を追加します。
PrivateDependencyModuleNamesにEngine、Slate、SlateCoreを追加します。 これらはアプリケーションのウィンドウ情報を取得するために必要です。
WindowsAPIを使用するために、PublicAdditionalLibraries.Add(“Dwmapi.lib”);をWindowsプラットフォーム限定で追加します。
myProjectName.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 |
using UnrealBuildTool; public class WindowTransparency : ModuleRules // クラス名は実際のプロジェクト/モジュール名に合わせる { public WindowTransparency(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange( new string[] { "Core", } ); PrivateDependencyModuleNames.AddRange( new string[] { "CoreUObject", "Engine", // GEngine "Slate", // SWindow "SlateCore", // SWidget } ); // Windowsプラットフォーム固有の設定 if (Target.Platform == UnrealTargetPlatform.Win64) { // Dwmapi.lib をリンク PublicAdditionalLibraries.Add("Dwmapi.lib"); } } } |
これでソースコード内でWindowsAPIを使用することができるようになります。
2: 透過処理の実装
Windows APIを使用して、ゲームウィンドウのスタイルを変更し、透過を有効にします。
C++ヘッダーファイルのインクルード
透過処理を実装するC++クラスを作成します。 今回実装する処理はエンジンのグローバル変数を使用するためどのクラスからでも呼び出せます。(例: UObjectを継承したクラスなど)。 作成したクラスの.cppファイルに必要なヘッダーファイルをインクルードします。
ウィンドウ情報を取得するためにWidgets/SWindow.hとEngine/Engine.hが必要です。Windows API関連のヘッダーは#if PLATFORM_WINDOWSで囲み、UEの型システムとの衝突を避けるためにAllowWindowsPlatformTypes.h / HideWindowsPlatformTypes.hでラップします。
myClass.cpp
1 2 3 4 5 6 7 8 9 10 11 |
#include "Widgets/SWindow.h" #include "Engine/Engine.h" #if PLATFORM_WINDOWS #include "Windows/AllowWindowsPlatformTypes.h" #include #include #include "Windows/HideWindowsPlatformTypes.h" #pragma comment(lib, "Dwmapi.lib") #endif |
ウィンドウのハンドルを取得する
Windows APIでウィンドウを操作するには、対象ウィンドウのハンドル (HWND) が必要になります。エンジンのグローバル変数「GEngine」からゲームのウィンドウのハンドルを取得できます。
myClass.cpp
1 2 3 4 5 6 7 8 9 |
if (GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWindow().IsValid()) { TSharedPtr GameSWindow = GEngine->GameViewport->GetWindow(); if (GameSWindow.IsValid() && GameSWindow->GetNativeWindow().IsValid()) { void* Handle = GameSWindow->GetNativeWindow()->GetOSWindowHandle(); HWND hwnd = static_cast(Handle); } } |
後は通常のWindowsAPIと同じ使い方をすることができます。
ウィンドウに透過設定を適用
取得したウィンドウハンドル (hwnd) に対して、DWM APIまたは標準のWindows API関数を使用して透過設定を適用します。以下の2つの方法があります。
方法1: DwmExtendFrameIntoClientArea関数
DWM (Desktop Window Manager) APIのDwmExtendFrameIntoClientArea関数を使用します。この関数は、ウィンドウのフレームをクライアント領域に拡張し、ガラス効果(Aero Glass)を適用します。引数のMARGINS構造体に-1を設定すると、クライアント領域全体が透明になります。
参考: DwmExtendFrameIntoClientArea 関数 (dwmapi.h) – Win32 apps
myClass.cpp
1 2 3 |
// (hwnd取得後) MARGINS margins = { -1}; HRESULT hr = DwmExtendFrameIntoClientArea(hwnd, margins); |
方法2: SetWindowLongPtr関数とWS_EX_LAYEREDスタイル
ウィンドウの拡張ウィンドウスタイルにWS_EX_LAYEREDを追加することでも透過ウィンドウを実現できます。このスタイルを設定した後、SetLayeredWindowAttributes関数またはUpdateLayeredWindow関数で透過方法(特定の色を透明にする、アルファ値で半透明にするなど)を指定します。
参考:
myClass.cpp
1 2 3 4 |
// (hwnd取得後) // 既存の拡張スタイルにWS_EX_LAYEREDを追加 LONG_PTR currentExStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE); SetWindowLongPtr(hwnd, GWL_EXSTYLE, currentExStyle | WS_EX_LAYERED); |
これらの関数をゲーム開始時や特定のタイミング(例: ボタンクリック時)に実行するようにします。ブループリントから呼び出せるようにUFUNCTIONとして公開する方法については、サンプルコードを参照してください。
3: Unreal Engineのポストプロセス設定
C++側のウィンドウ設定が完了したら、Unreal Engine側でレンダリング結果のアルファ値を制御します。これはポストプロセスマテリアルを使用して行います。
まず、C++コードをビルドし、Unreal Editorを起動します。
-
- ポストプロセスマテリアルの作成:
- コンテンツブラウザで右クリックし、「マテリアル」を新規作成します。
- 作成したマテリアルを開き、詳細パネルの「Material」セクションで「Material Domain」を「Post Process」に変更します。
- 同じく詳細パネルの「Post Process Material」セクションで「Output Alpha」にチェックを入れます。
- マテリアルグラフの編集:
-
- マテリアルグラフで、最終的なアルファ値を「Opacity」入力に接続します。例えば、深度やCustomStencilなどをOpacityに設定します。
下の画像では深度を取得して一定距離以上なら透過するという簡単な例です。
-
- ポストプロセスマテリアルの作成:
透過部分にEmissiveColorが含まれているとOpacityが0でも半透明で描画されてしまうため注意。
- ポストプロセスボリュームへの適用:
- レベルに配置されている「Post Process Volume」を選択します。(ない場合は新規に配置します。)
- 詳細パネルの「Rendering Features」 > 「Post Process Materials」セクションで、「Array elements」に新しい要素を追加し、「Asset Reference」として作成したポストプロセスマテリアルを指定します。
- ボリュームがシーン全体に影響するように、「Infinite Extent (Unbound)」にチェックを入れるか、影響範囲を調整します。
4: 確認方法
上記の設定が完了したら、実際に透過されるか確認します。
- C++で実装した透過設定を有効にする関数を実行します。
- ゲームを「スタンドアローンゲーム (Standalone Game)」で起動します。PIE (Play In Editor) では、ウィンドウ透過は正しく機能しません。
正しく設定されていれば、ゲームウィンドウの背景が透過され、デスクトップや他のウィンドウが見えるようになります。
透過設定をする関数を呼び出すと透過されてデスクトップが見えました。
次回予告
今回は背景透過の基本的な実装方法を紹介しました。次回は、透過したウィンドウのクリックスルー(マウス操作を背後のウィンドウに透過させる)や、ウィンドウを常に最前面に表示する方法など、より高度なカスタマイズについて紹介する予定です。
補足事項
- 境界線がちらつく
- ラグインを作成してパッケージ化する際にエラーが出る
- 背景が透過されず真っ黒になる
境界線がちらつく
アンチエイリアシング(AA)を無効化する: 特にTAA (Temporal Anti-Aliasing) やTSR (Temporal Super Resolution) は、境界線のチラツキが目立ちます。プロジェクト設定でアンチエイリアシング方法を「None」や「FXAA」に変更するとある程度改善します。
プラグインを作成してパッケージ化する際にエラーが出る
ビルドはできてエディタ上では問題なく動作するが、プラグインをパッケージ化しようとするとビルドエラーが発生する。
考えられる原因
- プラットフォーム固有コードの処理不足: Windows API関連のコード (#include やAPI呼び出しなど) はWindows専用です。プラグインは他のプラットフォーム (Mac, Linuxなど) 向けにもビルドが試みられるため、これらのコードを #if PLATFORM_WINDOWS と #endif で囲む必要があります。ヘッダーのインクルードだけでなく、関連する処理全体を囲ってください。
- ヘッダーファイルやモジュールの不足:
- ヘッダーファイル: 使用している関数や型に必要なヘッダーファイルがインクルードされていない可能性があります。エラーメッセージをよく読み、どのファイルが見つからないか確認してください。UEの公式ドキュメントやGitHubソースコードで関数名を検索すると、必要なヘッダーパスがわかることがあります。
- モジュール依存関係: 使用しているクラスや機能が特定のモジュールに属している場合、.Build.csファイルでそのモジュールへの依存関係 (PublicDependencyModuleNames または PrivateDependencyModuleNames) を追加する必要があります。例えば、SWindow は SlateCore や Slate モジュールに依存します。