using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Rendering; /// /// 투명 데스크톱 오버레이 검증에 필요한 프로젝트 설정을 일괄 적용한다. /// 일부 항목은 인스펙터에 노출되지 않아 스크립트로 설정한다. /// public static class DesktopOverlaySetup { const BuildTarget Target = BuildTarget.StandaloneWindows64; [MenuItem("Tools/Desktop Overlay/1. Apply Transparent Window Settings")] public static void Apply() { // useFlipModelSwapchain 은 D3D11 전용이므로 API 를 고정한다. PlayerSettings.SetUseDefaultGraphicsAPIs(Target, false); PlayerSettings.SetGraphicsAPIs(Target, new[] { GraphicsDeviceType.Direct3D11 }); PlayerSettings.useFlipModelSwapchain = false; // DWM 투명도의 전제 조건 PlayerSettings.fullScreenMode = FullScreenMode.Windowed; PlayerSettings.runInBackground = true; PlayerSettings.visibleInBackground = true; PlayerSettings.resizableWindow = false; PlayerSettings.forceSingleInstance = true; AssetDatabase.SaveAssets(); Debug.Log("[DesktopOverlaySetup] Player Settings 적용 완료."); LogCurrent(); } [MenuItem("Tools/Desktop Overlay/2. Log Current Settings")] public static void LogCurrent() { var apis = PlayerSettings.GetGraphicsAPIs(Target); var apiList = string.Join(", ", System.Array.ConvertAll(apis, a => a.ToString())); Debug.Log( "[DesktopOverlaySetup] 현재 값 (괄호 안이 기대값)\n" + $" Graphics APIs = [{apiList}] (Direct3D11)\n" + $" Auto Graphics API = {PlayerSettings.GetUseDefaultGraphicsAPIs(Target)} (False)\n" + $" useFlipModelSwapchain = {PlayerSettings.useFlipModelSwapchain} (False)\n" + $" fullScreenMode = {PlayerSettings.fullScreenMode} (Windowed)\n" + $" runInBackground = {PlayerSettings.runInBackground} (True)\n" + $" visibleInBackground = {PlayerSettings.visibleInBackground} (True)" ); } /// /// HDR 32비트 컬러 버퍼(R11G11B10_UFloat)에는 알파 채널이 없어서 /// 카메라를 알파 0으로 클리어해도 저장되지 않는다. HDR 을 끄고 /// URP 의 Alpha Processing 을 켠다. /// [MenuItem("Tools/Desktop Overlay/3. Fix URP Alpha Settings")] public static void FixUrpAlpha() { var guids = AssetDatabase.FindAssets("t:UniversalRenderPipelineAsset"); if (guids.Length == 0) { Debug.LogError("[DesktopOverlaySetup] URP Asset 을 찾지 못했습니다."); return; } foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); var asset = AssetDatabase.LoadAssetAtPath(path); if (asset == null) continue; var so = new SerializedObject(asset); bool changed = false; changed |= SetValue(so, "m_SupportsHDR", 0); // 알파 있는 R8G8B8A8 로 changed |= SetValue(so, "m_AllowPostProcessAlphaOutput", 1); // Alpha Processing 켜기 changed |= SetValue(so, "m_HDRColorBufferPrecision", 1); // HDR 쓸 경우 대비: 64bit(알파 있음) if (changed) { so.ApplyModifiedProperties(); EditorUtility.SetDirty(asset); Debug.Log($"[DesktopOverlaySetup] 수정됨: {path}"); } } AssetDatabase.SaveAssets(); Debug.Log("[DesktopOverlaySetup] URP 알파 설정 완료. 다시 빌드하세요."); } /// /// 런타임 CreatePrimitive 는 빌드에서 Built-in 기본 머티리얼을 잡아 분홍색이 된다. /// 에디터에서 URP 머티리얼을 가진 큐브를 씬에 넣어 빌드 포함을 보장한다. /// [MenuItem("Tools/Desktop Overlay/4. Add Test Cube To Scene")] public static void AddTestCube() { var existing = GameObject.Find("AlphaTestCube"); if (existing != null) Object.DestroyImmediate(existing); var shader = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Universal Render Pipeline/Unlit"); if (shader == null) { Debug.LogError("[DesktopOverlaySetup] URP 셰이더를 찾지 못했습니다."); return; } var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); cube.name = "AlphaTestCube"; var cam = Camera.main; cube.transform.position = cam != null ? cam.transform.position + cam.transform.forward * 5f : new Vector3(0f, 1f, 0f); const string matPath = "Assets/99_Settings/AlphaTestCubeMat.mat"; var mat = new Material(shader) { color = new Color(0.2f, 0.8f, 1f) }; AssetDatabase.CreateAsset(mat, matPath); cube.GetComponent().sharedMaterial = AssetDatabase.LoadAssetAtPath(matPath); EditorSceneManager.MarkSceneDirty(cube.scene); AssetDatabase.SaveAssets(); Debug.Log("[DesktopOverlaySetup] AlphaTestCube 추가됨. 씬을 저장(Ctrl+S)하세요."); } static bool SetValue(SerializedObject so, string propName, int value) { var p = so.FindProperty(propName); if (p == null) { Debug.LogWarning($"[DesktopOverlaySetup] 프로퍼티 없음: {propName}"); return false; } if (p.propertyType == SerializedPropertyType.Boolean) { if (p.boolValue == (value != 0)) return false; p.boolValue = value != 0; } else { if (p.intValue == value) return false; p.intValue = value; } return true; } }