技術ブログ - historia-Inc/WindowTransparency GitHub Wiki

公開予定のブログの内容と同じです。順番の関係上ブログの公開がだいぶ後になるためこちらで公開します。


透過処理の基本

Windowsにおけるウィンドウ透過処理の基本的な考え方は、DWM (Desktop Window Manager) APIなどを利用してウィンドウの特定領域を透明に描画させることです。以下の記事やリポジトリで解説されている内容が参考になります。

上記の記事を参考に今回は主にWindowsAPIの呼び出し方やレンダリング設定などUE特有の部分を中心に解説していきます。


1: セットアップ

まずは、WindowsAPIを使用できるようにUnreal Engineプロジェクトをセットアップします。 新規にC++プロジェクトを作成してください。

プロジェクト設定

Alpha Outputを有効化

プロジェクト設定(Edit > Project Settings...)を開き、「Engine」 > 「Rendering」セクションにある「Alpha Output」にチェックを入れます。これでバックバッファにAlphaチャンネルが書き込まれるようになります。

Alpha Output Setting

Note: RenderDocなどのグラフィックスデバッガを使用すれば、バックバッファにアルファが書き込まれているか確認できます。アルファチャンネルが書き込まれていてもウィンドウ設定が正しくないと透過されませんが、問題の切り分けに役立ちます。

Default RHI (レンダリングAPI)

プロジェクト設定の「Platforms」 > 「Windows」 > 「Targeted RHIs」セクションで、「Default RHI」をDirectX11に変更します。今回の手法ではDirectX12では背景を透過することができません。

Default RHI Setting

Warning: DirectX11を使用するため、LumenなどDirectX12 (SM6) の機能を必要とする一部のレンダリング機能は使用できなくなります。

Tearingの設定

Config/DefaultEngine.iniファイルを開き、[/Script/Engine.RendererSettings]セクションに以下の行を追加します。

DefaultEngine.ini

r.D3D11.UseAllowTearing=0

この設定はエディターのプロジェクト設定UIからは変更できないため、直接iniファイルを編集する必要があります。

Note: この設定をすることでDXGIを使用したフリップモードを無効化できます。DXGIフリップモードは透明度をサポートしていないため無効化する必要があります。Unityにおける「Use DXGI flip model swapchain for D3D11」設定に該当します。

参考:

モジュールのビルド設定 (Build.cs)

プロジェクトのソースフォルダ内にある[プロジェクト名].Build.csファイル(例: MyProject.Build.cs)に設定を追加します。

PrivateDependencyModuleNamesEngineSlateSlateCoreを追加します。 これらはアプリケーションのウィンドウ情報を取得するために必要です。

WindowsAPIを使用するために、PublicAdditionalLibraries.Add("Dwmapi.lib");をWindowsプラットフォーム限定で追加します。

myProjectName.Build.cs

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.hEngine/Engine.hが必要です。Windows API関連のヘッダーは#if PLATFORM_WINDOWSで囲み、UEの型システムとの衝突を避けるためにAllowWindowsPlatformTypes.h / HideWindowsPlatformTypes.hでラップします。

myClass.cpp

#include "Widgets/SWindow.h"
#include "Engine/Engine.h"

#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
#include <dwmapi.h>
#include <winuser.h> // SetWindowLongPtr などで必要
#include "Windows/HideWindowsPlatformTypes.h"

#pragma comment(lib, "Dwmapi.lib")
#endif

ウィンドウのハンドルを取得する

Windows APIでウィンドウを操作するには、対象ウィンドウのハンドル (HWND) が必要になります。エンジンのグローバル変数「GEngine」からゲームのウィンドウのハンドルを取得できます。

myClass.cpp

    if (GEngine && GEngine->GameViewport && GEngine->GameViewport->GetWindow().IsValid())
    {
        TSharedPtr<SWindow> GameSWindow = GEngine->GameViewport->GetWindow();
        if (GameSWindow.IsValid() && GameSWindow->GetNativeWindow().IsValid())
        {
            void* Handle = GameSWindow->GetNativeWindow()->GetOSWindowHandle();
            HWND hwnd = static_cast<HWND>(Handle);
            // ここで hwnd を使用して処理
        }
    }

後は通常の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

// (hwnd取得後)
    MARGINS margins = { -1 };
    HRESULT hr = DwmExtendFrameIntoClientArea(hwnd, &margins);
方法2: SetWindowLongPtr関数とWS_EX_LAYEREDスタイル

ウィンドウの拡張ウィンドウスタイルにWS_EX_LAYEREDを追加することでも透過ウィンドウを実現できます。このスタイルを設定した後、SetLayeredWindowAttributes関数またはUpdateLayeredWindow関数で透過方法(特定の色を透明にする、アルファ値で半透明にするなど)を指定します。

参考:

myClass.cpp

// (hwnd取得後)
    // 既存の拡張スタイルにWS_EX_LAYEREDを追加
    LONG_PTR currentExStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
    SetWindowLongPtr(hwnd, GWL_EXSTYLE, currentExStyle | WS_EX_LAYERED);

Note: これらの関数をゲーム開始時や特定のタイミング(例: ボタンクリック時)に実行するようにします。ブループリントから呼び出せるようにUFUNCTIONとして公開する方法については、サンプルコードを参照してください。


3: Unreal Engineのポストプロセス設定

C++側のウィンドウ設定が完了したら、Unreal Engine側でレンダリング結果のアルファ値を制御します。これはポストプロセスマテリアルを使用して行います。

まず、C++コードをビルドし、Unreal Editorを起動します。

  1. ポストプロセスマテリアルの作成:

    • コンテンツブラウザで右クリックし、「マテリアル」を新規作成します。
    • 作成したマテリアルを開き、詳細パネルの「Material」セクションで「Material Domain」を「Post Process」に変更します。
    • 同じく詳細パネルの「Post Process Material」セクションで「Output Alpha」にチェックを入れます。
  2. マテリアルグラフの編集:

    • マテリアルグラフで、最終的なアルファ値を「Opacity」入力に接続します。例えば、深度やCustomStencilなどをOpacityに設定します。

    下の画像では深度を取得して一定距離以上なら透過するという簡単な例です。

    Post Process Material Example

    Warning: 透過部分にEmissiveColorが含まれているとOpacityが0でも半透明で描画されてしまうため注意。

  3. ポストプロセスボリュームへの適用:

    • レベルに配置されている「Post Process Volume」を選択します。(ない場合は新規に配置します。)
    • 詳細パネルの「Rendering Features」 > 「Post Process Materials」セクションで、「Array elements」に新しい要素を追加し、「Asset Reference」として作成したポストプロセスマテリアルを指定します。
    • ボリュームがシーン全体に影響するように、「Infinite Extent (Unbound)」にチェックを入れるか、影響範囲を調整します。

4: 確認方法

上記の設定が完了したら、実際に透過されるか確認します。

  • C++で実装した透過設定を有効にする関数を実行します。
  • ゲームを**「スタンドアローンゲーム (Standalone Game)」**で起動します。PIE (Play In Editor) では、ウィンドウ透過は正しく機能しません。

正しく設定されていれば、ゲームウィンドウの背景が透過され、デスクトップや他のウィンドウが見えるようになります。

動画: 確認結果 2025-05-27-12-54-36.mp4

透過設定をする関数を呼び出すと透過されてデスクトップが見えました。


補足事項

境界線がちらつく

アンチエイリアシング(AA)を無効化する: 特にTAA (Temporal Anti-Aliasing) やTSR (Temporal Super Resolution) は、境界線のチラツキが目立ちます。プロジェクト設定でアンチエイリアシング方法を「None」や「FXAA」に変更するとある程度改善します。

プラグインを作成してパッケージ化する際にエラーが出る

ビルドはできてエディタ上では問題なく動作するが、プラグインをパッケージ化しようとするとビルドエラーが発生する。

考えられる原因

  • プラットフォーム固有コードの処理不足: Windows API関連のコード (#include <windows.h> やAPI呼び出しなど) はWindows専用です。プラグインは他のプラットフォーム (Mac, Linuxなど) 向けにもビルドが試みられるため、これらのコードを #if PLATFORM_WINDOWS#endif で囲む必要があります。ヘッダーのインクルードだけでなく、関連する処理全体を囲ってください。
  • ヘッダーファイルやモジュールの不足:
    • ヘッダーファイル: 使用している関数や型に必要なヘッダーファイルがインクルードされていない可能性があります。エラーメッセージをよく読み、どのファイルが見つからないか確認してください。UEの公式ドキュメントやGitHubソースコードで関数名を検索すると、必要なヘッダーパスがわかることがあります。
    • モジュール依存関係: 使用しているクラスや機能が特定のモジュールに属している場合、.Build.csファイルでそのモジュールへの依存関係 (PublicDependencyModuleNames または PrivateDependencyModuleNames) を追加する必要があります。例えば、SWindowSlateCoreSlate モジュールに依存します。

背景が透過されず真っ黒になる

MFAAの設定: NVIDIA製のGPUを使用している場合、NVIDIAコントロールパネルの「3D設定の管理」で「マルチフレームサンプリングAA (MFAA)」がオンになっていると、透過が正しく機能しないことがあります。MFAAをオフに設定変更して試してみてください。

NVIDIA MFAA Setting

⚠️ **GitHub.com Fallback** ⚠️