From 09413ccb3bbebef226d7fa8676bb6f0bca7e1088 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 8 May 2026 04:49:20 +0900 Subject: [PATCH] =?UTF-8?q?Refactor=20:=20=EC=8A=A4=ED=81=AC=EB=A6=B0?= =?UTF-8?q?=EC=83=B7=20=EC=BA=A1=EC=B2=98=20RGB(A=3D1)=20/=20RGBA=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20+=20sRGB=20=EC=83=89=EA=B3=B5=EA=B0=84=20?= =?UTF-8?q?=EC=9D=BC=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - capture_screenshot → RGB24 PNG (알파 정보 자동 제거, 일반 사진) - capture_alpha_screenshot → RGBA32 PNG (배경 투명, OBS/합성 소스용) - HDR/Linear RT → sRGB RT 로 Blit 한 후 ReadPixels — Spout(OBS) 결과와 동일 톤 - 코루틴 기반 임시 고해상도 캡처(captureWidth/Height) 제거 — 카메라 해상도 그대로 → Spout 송신 한 프레임 깜빡임 부작용 사라짐 - screenshot.Initialize() 시그니처 host 인자 제거(코루틴 불필요) - UXML 인스펙터: captureWidth/Height 필드 제거 + 안내 갱신 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Controllers/System/ScreenshotManager.cs | 105 +++++------------- .../Controllers/SystemController.cs | 2 +- .../Editor/UXML/SystemControllerEditor.uxml | 4 +- 3 files changed, 28 insertions(+), 83 deletions(-) diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/System/ScreenshotManager.cs b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/System/ScreenshotManager.cs index 059c034c1..155d162c1 100644 --- a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/System/ScreenshotManager.cs +++ b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/System/ScreenshotManager.cs @@ -1,14 +1,13 @@ using UnityEngine; using System; -using System.Collections; using System.IO; /// /// 스크린샷 캡처 관리. -/// RenderStreamOutput 이 매 프레임 갱신하는 CaptureTexture/AlphaOutputTexture 를 PNG 로 저장. -/// captureWidth/Height 가 지정되면 캡처 직전에 카메라 targetTexture 를 임시로 그 해상도로 -/// 변경해 한 프레임 고해상도 렌더 후 원복한다. -/// 트레이드오프: 그 한 프레임 동안 Spout/NDI 송신도 같은 해상도가 되어 수신측이 잠깐 깜빡임. +/// RenderStreamOutput.CaptureTexture (후처리 + 알파 합성 결과) 를 PNG 로 저장. +/// - CaptureScreenshot() : RGB 만 (알파 = 1, 일반 사진) +/// - CaptureAlphaScreenshot() : RGBA 그대로 (배경 투명, 합성 소스용) +/// 카메라 해상도 그대로 ReadPixels — 별도 임시 RT/카메라 변경 없음. /// [Serializable] public class ScreenshotManager @@ -22,22 +21,14 @@ public class ScreenshotManager [Tooltip("파일명 앞에 붙을 접두사")] public string screenshotFilePrefix = "Screenshot"; - [Header("고해상도 캡처 (0 이면 카메라 해상도 사용)")] - [Tooltip("캡처 너비. 0 이면 카메라 해상도 그대로")] - public int captureWidth = 3840; - [Tooltip("캡처 높이. 0 이면 카메라 해상도 그대로")] - public int captureHeight = 2160; - [Tooltip("(하위 호환) 외부 코드가 .screenshotCamera 로 접근하면 RenderStreamOutput.MainCam 반환")] public Camera screenshotCamera => renderStream != null ? renderStream.MainCam : null; - private MonoBehaviour host; private Action log; private Action logError; - public void Initialize(MonoBehaviour host, Action log, Action logError) + public void Initialize(Action log, Action logError) { - this.host = host; this.log = log; this.logError = logError; @@ -57,78 +48,26 @@ public class ScreenshotManager } } + /// RGB 만, 알파는 1 로 저장하는 일반 PNG (평범한 사진) public void CaptureScreenshot() { - if (!CanCapture()) return; - host.StartCoroutine(CaptureCoroutine(alphaOnly: false)); + if (renderStream == null || renderStream.CaptureTexture == null) + { + logError?.Invoke("RenderStreamOutput.CaptureTexture 가 준비되지 않았습니다"); + return; + } + SaveRtAsPng(renderStream.CaptureTexture, GenerateFileName("png"), TextureFormat.RGB24); } + /// RGBA 모두 저장 — 배경 투명, OBS/합성 소스용 public void CaptureAlphaScreenshot() { - if (!CanCapture()) return; - host.StartCoroutine(CaptureCoroutine(alphaOnly: true)); - } - - private bool CanCapture() - { - if (host == null) { logError?.Invoke("ScreenshotManager host(MonoBehaviour) 가 없습니다"); return false; } - if (renderStream == null || renderStream.MainCam == null) + if (renderStream == null || renderStream.CaptureTexture == null) { - logError?.Invoke("RenderStreamOutput / MainCam 이 준비되지 않았습니다"); - return false; - } - return true; - } - - private IEnumerator CaptureCoroutine(bool alphaOnly) - { - var cam = renderStream.MainCam; - bool useTempResolution = captureWidth > 0 && captureHeight > 0; - - RenderTexture tempRT = null; - RenderTexture prevTarget = null; - - if (useTempResolution) - { - prevTarget = cam.targetTexture; - var desc = new RenderTextureDescriptor(captureWidth, captureHeight, - renderStream.TextureFormat, 24) - { - msaaSamples = Mathf.Max(1, renderStream.AntiAliasing) - }; - tempRT = new RenderTexture(desc); - tempRT.Create(); - - cam.targetTexture = tempRT; - - // 1프레임 — RenderStreamOutput.Update 가 새 pixelWidth 감지 → InitializeTextures(고해상도) - yield return null; - // 1프레임 — 고해상도 RT 에 우리 패스 결과 채워짐 - yield return new WaitForEndOfFrame(); - } - - var srcRT = alphaOnly ? renderStream.AlphaOutputTexture : renderStream.CaptureTexture; - if (srcRT != null) - { - string fileName = GenerateFileName("png", alphaOnly ? "_Alpha" : ""); - SaveRtAsPng(srcRT, fileName, alphaOnly ? TextureFormat.RGB24 : TextureFormat.RGBA32); - } - else - { - logError?.Invoke("캡처 RT 가 준비되지 않았습니다"); - } - - if (useTempResolution) - { - cam.targetTexture = prevTarget; - if (tempRT != null) - { - tempRT.Release(); - UnityEngine.Object.Destroy(tempRT); - } - // 한 프레임 더 대기 — RenderStreamOutput.Update 가 원래 해상도로 복귀 - yield return null; + logError?.Invoke("RenderStreamOutput.CaptureTexture 가 준비되지 않았습니다"); + return; } + SaveRtAsPng(renderStream.CaptureTexture, GenerateFileName("png", "_Alpha"), TextureFormat.RGBA32); } public void OpenScreenshotFolder() @@ -148,11 +87,18 @@ public class ScreenshotManager { string fullPath = Path.Combine(screenshotSavePath, fileName); RenderTexture prevActive = RenderTexture.active; + RenderTexture sRgbRT = null; Texture2D tex = null; try { - RenderTexture.active = rt; + // HDR/Linear RT → sRGB RT 로 Blit. ReadPixels 는 색공간 변환을 안 하므로 이걸 거치지 + // 않으면 PNG 가 Linear 값을 sRGB 슬롯에 그대로 담아 OBS(Spout 결과)와 다르게 보임. + sRgbRT = RenderTexture.GetTemporary(rt.width, rt.height, 0, + RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); + Graphics.Blit(rt, sRgbRT); + + RenderTexture.active = sRgbRT; tex = new Texture2D(rt.width, rt.height, format, false); tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); tex.Apply(); @@ -167,6 +113,7 @@ public class ScreenshotManager finally { RenderTexture.active = prevActive; + if (sRgbRT != null) RenderTexture.ReleaseTemporary(sRgbRT); if (tex != null) UnityEngine.Object.Destroy(tex); } } diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/SystemController.cs b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/SystemController.cs index 420acb612..7235661d6 100644 --- a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/SystemController.cs +++ b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/SystemController.cs @@ -54,7 +54,7 @@ public class SystemController : MonoBehaviour optiTrack.Initialize(Log, LogError); facialMotion.Initialize(Log, LogError); motionRecording.Initialize(optiTrack, Log, LogError); - screenshot.Initialize(this, Log, LogError); + screenshot.Initialize(Log, LogError); clothSimulation.Initialize(Log, LogError); avatarHead.Initialize(Log, LogError); retargetingRemote.Initialize(Log, LogError); diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/SystemControllerEditor.uxml b/Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/SystemControllerEditor.uxml index 99eeb3b40..62a7de4be 100644 --- a/Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/SystemControllerEditor.uxml +++ b/Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/SystemControllerEditor.uxml @@ -29,12 +29,10 @@ - + - -