From 9ea5f2af2b3554cb3ed27535c174d0207589d487 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 8 May 2026 04:32:13 +0900 Subject: [PATCH] =?UTF-8?q?Refactor=20:=20Spout/NDI=20=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=B4=ED=94=84=EB=9D=BC=EC=9D=B8=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20+=20=EC=95=8C=ED=8C=8C=20=ED=95=A9=EC=84=B1=20+=20N?= =?UTF-8?q?ormalizer=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RenderStreamOutput 을 URP 17 RenderGraph API 로 마이그레이션 (옛 Execute() 도 Compatibility Mode 호환용으로 유지) - 알파 합성 셰이더 신규: Pre/Post 비교(블룸/글로우) + NiloToon Prepass G + 가우시안 블러 - 알파 채널 별도 Spout 송신 추가 ("Streamingle Spout Alpha Output") - 그레이스케일 RGB 마스크, A=1 - spout_ndi_normalizer.exe 외부 프로세스 자동 실행/종료 (SpoutNdiLauncher 병합) - Display 드롭다운 / Vsync / AlwaysOnTop / HideCursor / Realtime / NoActivate 옵션 - exe 가 있으면 강제 종료 후 단일 인스턴스 보장 - 내부 옵션(exe 경로, window size 등)은 [HideInInspector] - ScreenshotManager 가 RenderStreamOutput 의 합성 결과를 그대로 PNG 저장 - 자체 카메라 렌더/셰이더 관리 제거 → 알파 품질 라이브 출력과 동일 - captureWidth/Height 지정 시 한 프레임 임시 고해상도 렌더 후 원복 - spout_ndi_normalizer.exe 위치: Resources → StreamingAssets/SpoutNdiNormalizer - URP Asset: Allow Post Process Alpha Output 활성화 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Streamingle Render Pipeline Asset.asset | 2 +- .../SpoutOutputScript/RenderStreamOutput.cs | 595 +++++++++++++----- .../Shaders/StreamingleAlphaCompose.shader | 96 +++ .../StreamingleAlphaCompose.shader.meta | 9 + .../Shaders/StreamingleAlphaOnly.shader | 30 + .../Shaders/StreamingleAlphaOnly.shader.meta | 9 + .../Controllers/System/ScreenshotManager.cs | 241 ++++--- .../Controllers/SystemController.cs | 2 +- .../Editor/UXML/SystemControllerEditor.uxml | 10 +- Assets/StreamingAssets.meta | 8 + .../StreamingAssets/SpoutNdiNormalizer.meta | 8 + .../SpoutNdiNormalizer/README.md | 3 + .../SpoutNdiNormalizer/README.md.meta | 7 + .../spout_ndi_normalizer.exe | Bin 0 -> 137728 bytes .../spout_ndi_normalizer.exe.meta | 7 + 15 files changed, 737 insertions(+), 290 deletions(-) create mode 100644 Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaCompose.shader create mode 100644 Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaCompose.shader.meta create mode 100644 Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaOnly.shader create mode 100644 Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaOnly.shader.meta create mode 100644 Assets/StreamingAssets.meta create mode 100644 Assets/StreamingAssets/SpoutNdiNormalizer.meta create mode 100644 Assets/StreamingAssets/SpoutNdiNormalizer/README.md create mode 100644 Assets/StreamingAssets/SpoutNdiNormalizer/README.md.meta create mode 100644 Assets/StreamingAssets/SpoutNdiNormalizer/spout_ndi_normalizer.exe create mode 100644 Assets/StreamingAssets/SpoutNdiNormalizer/spout_ndi_normalizer.exe.meta diff --git a/Assets/Resources/Settings/Streamingle Render Pipeline Asset.asset b/Assets/Resources/Settings/Streamingle Render Pipeline Asset.asset index 11b1e2e8b..a5e4f654a 100644 --- a/Assets/Resources/Settings/Streamingle Render Pipeline Asset.asset +++ b/Assets/Resources/Settings/Streamingle Render Pipeline Asset.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f3eb69a4c83b17433d63428649d12ce4c3ea55ffcdd9888f4c106ca9b3d6aba +oid sha256:4d326ded8a0104f2468de0dd7395f91c8ec505783ac22e85295f0d9953b3dda5 size 4686 diff --git a/Assets/Scripts/SpoutOutputScript/RenderStreamOutput.cs b/Assets/Scripts/SpoutOutputScript/RenderStreamOutput.cs index 6d7209640..bb1ba1ae5 100644 --- a/Assets/Scripts/SpoutOutputScript/RenderStreamOutput.cs +++ b/Assets/Scripts/SpoutOutputScript/RenderStreamOutput.cs @@ -1,9 +1,14 @@ using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; +using UnityEngine.Rendering.RenderGraphModule; +using UnityEngine.Rendering.RenderGraphModule.Util; using Klak.Spout; using Klak.Ndi; using System; +using System.Diagnostics; +using System.IO; +using Debug = UnityEngine.Debug; // 출력 방식 열거형 public enum OutputMethod @@ -14,6 +19,16 @@ public enum OutputMethod Both } +// Normalizer 출력 디스플레이 (음수 = windowed, 0~ = borderless fullscreen monitor index) +public enum NormalizerDisplay +{ + Windowed = -1, + Display1 = 0, + Display2 = 1, + Display3 = 2, + Display4 = 3, +} + public class RenderStreamOutput : MonoBehaviour { [Header("출력 설정")] @@ -32,99 +47,212 @@ public class RenderStreamOutput : MonoBehaviour [Tooltip("Spout 송신 이름")] public string spoutSenderName = "Streamingle Spout Output"; + [Header("Spout 알파 별도 송신")] + [Tooltip("알파 전용 Spout 송신 컴포넌트 (자동 생성)")] + public SpoutSender AlphaSender; + [Tooltip("알파 전용 Spout 송신 이름")] + public string alphaSpoutSenderName = "Streamingle Spout Alpha Output"; + [Tooltip("Hidden/Streamingle/AlphaOnly 셰이더")] + public Shader alphaOnlyShader; + [Header("NDI 설정")] [Tooltip("NDI 송신 컴포넌트")] public NdiSender NdiSender; [Tooltip("NDI 송신 이름")] public string ndiSenderName = "Streamingle NDI Output"; - [Header("쉐이더 설정")] - [Tooltip("렌더링 결과를 처리할 쉐이더 머티리얼")] - public Material ShaderContral; - [Header("텍스처 설정")] [Tooltip("텍스처 포맷 설정")] public RenderTextureFormat TextureFormat = RenderTextureFormat.DefaultHDR; - [Tooltip("깊이 버퍼 비트 수")] - public int DepthBuffer = 24; [Tooltip("안티앨리어싱 레벨 (1, 2, 4, 8)")] [Range(1, 8)] public int AntiAliasing = 1; - [HideInInspector] public CustomRenderTexture ShaderTexture = null; - [HideInInspector] public RenderTexture ShaderCameraTexture = null; + [Header("프레임 제한")] + [Tooltip("타겟 프레임레이트 (-1: 제한 없음). VSync 는 자동으로 꺼짐")] + public int targetFrameRate = 75; - private AlphaRecodingRenderPass m_ScriptablePass; + [Header("Spout/NDI Normalizer")] + [Tooltip("외부 정규화 exe 자동 실행. Unity 출력의 페이싱 jitter 를 외부 프로세스로 정규화해 안정적 cadence 송신")] + public bool normalizerEnabled = true; + [Tooltip("출력 디스플레이 — 풀스크린 모니터 또는 Windowed")] + public NormalizerDisplay normalizerDisplay = NormalizerDisplay.Display2; + [Tooltip("Normalizer 출력 cadence (Hz)")] + public float normalizerFps = 60f; + [Tooltip("Normalizer VSync (런타임에 V 키로 토글 가능)")] + public bool normalizerVsync = true; + [Tooltip("Normalizer 윈도우를 항상 최상위로 유지 (런타임에 T 키로 토글 가능)")] + public bool normalizerAlwaysOnTop = false; + [Tooltip("Normalizer 윈도우 위에 커서가 있을 때 숨김 (런타임에 C 키로 토글 가능)")] + public bool normalizerHideCursor = false; + [Tooltip("⚠ 헤드리스 렌더팜 전용 — TIME_CRITICAL + Pro Audio MMCSS 스케줄링. Unity 가 foreground 일 때 켜면 Unity 자체가 starvation 됨. 평소엔 끄세요")] + public bool normalizerRealtime = false; + [Tooltip("Normalizer 창이 클릭으로 focus 를 가져가지 않게 함. Unity Editor 와 같이 작업할 때 권장 — 클릭이 통과되어 Unity 가 foreground 유지. 단, 런타임 핫키(F11/V/T/C/P/ESC 등) 비활성")] + public bool normalizerNoActivate = true; + + // ──────── 내부 옵션 (사용자가 만지지 않도록 숨김) ──────── + [HideInInspector] public string normalizerExeRelativePath = "SpoutNdiNormalizer/spout_ndi_normalizer.exe"; + [HideInInspector] public string normalizerExeAbsolutePath = ""; + [HideInInspector] public int normalizerWindowWidth = 1280; + [HideInInspector] public int normalizerWindowHeight = 720; + [HideInInspector] public bool normalizerShowConsole = false; + + [Header("알파 합성")] + [Tooltip("Hidden/Streamingle/AlphaCompose 셰이더")] + public Shader alphaComposeShader; + [Tooltip("cameraColor.a 를 알파로 사용하는 강도. NiloToon 환경에서는 cameraColor.a 가 1 로 차서 보통 0 권장")] + [Range(0f, 4f)] + public float sourceAlphaGain = 0.0f; + [Tooltip("후처리(블룸/글로우 등) 가 추가한 빛을 알파로 변환하는 강도")] + [Range(0f, 20f)] + public float bloomAlphaGain = 5.0f; + [Tooltip("NiloToon Prepass G 채널 알파 강도 (0 = 사용 안 함)")] + [Range(0f, 4f)] + public float prepassAlphaGain = 1.0f; + [Tooltip("Prepass 알파 외곽 가우시안 블러 반경 (텍셀)")] + [Range(0f, 5f)] + public float alphaBlurRadius = 1.0f; + + [HideInInspector] public RenderTexture CaptureTexture = null; + [HideInInspector] public RenderTexture AlphaOutputTexture = null; + private RenderTexture m_PreColorTexture = null; + + private RTHandle m_CaptureHandle; + private RTHandle m_PreColorHandle; + private RTHandle m_AlphaOutputHandle; + + private Material m_AlphaComposeMaterial; + private Material m_AlphaOnlyMaterial; + private PreCapturePass m_PreCapturePass; + private ComposePass m_ComposePass; + private AlphaOutputPass m_AlphaOutputPass; + + private Process m_NormalizerProc; - // 화면 크기 체크를 위한 변수 private int screenWidth; private int screenHeight; - // 이전 출력 방식 저장 private OutputMethod previousOutputMethod; - // 이전 알파 채널 설정 저장 private bool previousKeepAlpha; + private int previousTargetFrameRate; + + private static readonly int s_SourceAlphaGainID = Shader.PropertyToID("_SourceAlphaGain"); + private static readonly int s_BloomAlphaGainID = Shader.PropertyToID("_BloomAlphaGain"); + private static readonly int s_PrepassAlphaGainID = Shader.PropertyToID("_PrepassAlphaGain"); + private static readonly int s_AlphaBlurRadiusID = Shader.PropertyToID("_AlphaBlurRadius"); private void Awake() { - // 컴포넌트 자동 할당 MainCam = MainCam == null ? Camera.main : MainCam; - // 초기 설정 저장 previousOutputMethod = outputMethod; previousKeepAlpha = keepAlpha; + previousTargetFrameRate = targetFrameRate; - // 출력 컴포넌트 초기화 + ApplyFrameRate(); + EnsureMaterial(); + EnsureAlphaOnlyMaterial(); InitializeOutputComponents(); - InitializeTextures(); } + private void Start() + { + if (normalizerEnabled) + { + try { LaunchNormalizer(); } + catch (Exception e) { Debug.LogError($"[RenderStreamOutput] normalizer launch failed: {e}"); } + } + } + + private void OnApplicationQuit() { KillNormalizer(); } + + private void EnsureAlphaOnlyMaterial() + { + if (alphaOnlyShader == null) + alphaOnlyShader = Shader.Find("Hidden/Streamingle/AlphaOnly"); + + if (alphaOnlyShader == null) + { + Debug.LogError("AlphaOnly 셰이더를 찾을 수 없습니다."); + return; + } + + if (m_AlphaOnlyMaterial == null) + m_AlphaOnlyMaterial = new Material(alphaOnlyShader) { hideFlags = HideFlags.HideAndDontSave }; + } + + private void ApplyFrameRate() + { + if (targetFrameRate > 0) + { + QualitySettings.vSyncCount = 0; + Application.targetFrameRate = targetFrameRate; + } + else + { + Application.targetFrameRate = -1; + } + } + + private void EnsureMaterial() + { + if (alphaComposeShader == null) + alphaComposeShader = Shader.Find("Hidden/Streamingle/AlphaCompose"); + + if (alphaComposeShader == null) + { + Debug.LogError("AlphaCompose 셰이더를 찾을 수 없습니다. Always Included Shaders 에 추가하거나 인스펙터에 할당하세요."); + return; + } + + if (m_AlphaComposeMaterial == null) + m_AlphaComposeMaterial = new Material(alphaComposeShader) { hideFlags = HideFlags.HideAndDontSave }; + + PushMaterialParams(); + } + + private void PushMaterialParams() + { + if (m_AlphaComposeMaterial == null) return; + m_AlphaComposeMaterial.SetFloat(s_SourceAlphaGainID, sourceAlphaGain); + m_AlphaComposeMaterial.SetFloat(s_BloomAlphaGainID, bloomAlphaGain); + m_AlphaComposeMaterial.SetFloat(s_PrepassAlphaGainID, prepassAlphaGain); + m_AlphaComposeMaterial.SetFloat(s_AlphaBlurRadiusID, alphaBlurRadius); + } + private void InitializeOutputComponents() { - // Spout 송신 컴포넌트 초기화 if (outputMethod == OutputMethod.Spout || outputMethod == OutputMethod.Both) { if (Sender == null) { Sender = GetComponent(); if (Sender == null) - { Sender = gameObject.AddComponent(); - Sender.spoutName = spoutSenderName; - Sender.keepAlpha = keepAlpha; - Sender.captureMethod = Klak.Spout.CaptureMethod.Texture; - } } } - // NDI 송신 컴포넌트 초기화 + if (outputMethod == OutputMethod.NDI || outputMethod == OutputMethod.Both) { if (NdiSender == null) { NdiSender = GetComponent(); if (NdiSender == null) - { - Debug.Log("NDI 송신 컴포넌트를 생성합니다."); NdiSender = gameObject.AddComponent(); - if (NdiSender != null) - { - NdiSender.ndiName = ndiSenderName; - NdiSender.keepAlpha = keepAlpha; - NdiSender.captureMethod = Klak.Ndi.CaptureMethod.Texture; - } - else - { - Debug.LogError("NDI 송신 컴포넌트 생성 실패."); - } - } } } + + if (AlphaSender == null) + { + // 같은 GameObject 에 두 번째 SpoutSender 추가 + AlphaSender = gameObject.AddComponent(); + } } private void OnEnable() { - InitializeScriptablePass(); + InitializeScriptablePasses(); RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering; } @@ -135,77 +263,76 @@ public class RenderStreamOutput : MonoBehaviour private void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera) { - if (camera == MainCam) - { - var data = camera.GetUniversalAdditionalCameraData(); - data.scriptableRenderer.EnqueuePass(m_ScriptablePass); - } + if (camera != MainCam) + return; + + var data = camera.GetUniversalAdditionalCameraData(); + if (m_PreCapturePass != null) + data.scriptableRenderer.EnqueuePass(m_PreCapturePass); + if (m_ComposePass != null) + data.scriptableRenderer.EnqueuePass(m_ComposePass); + if (m_AlphaOutputPass != null) + data.scriptableRenderer.EnqueuePass(m_AlphaOutputPass); } private void InitializeTextures() { - // 카메라가 null인 경우 체크 if (MainCam == null) { Debug.LogError("MainCam이 설정되지 않았습니다."); return; } - // ShaderContral이 null인 경우 체크 - if (ShaderContral == null) - { - Debug.LogError("ShaderContral 머티리얼이 설정되지 않았습니다."); - return; - } - - // 카메라의 실제 해상도 사용 screenWidth = MainCam.pixelWidth; screenHeight = MainCam.pixelHeight; - // ShaderTexture 초기화 - 메모리 누수 방지를 위한 확실한 정리 - if (ShaderTexture != null) + var desc = new RenderTextureDescriptor(screenWidth, screenHeight, TextureFormat, 0) { - ShaderTexture.Release(); - DestroyImmediate(ShaderTexture); // Destroy 대신 DestroyImmediate 사용 - } - ShaderTexture = new CustomRenderTexture(screenWidth, screenHeight, TextureFormat); - ShaderTexture.material = ShaderContral; - ShaderTexture.updateMode = CustomRenderTextureUpdateMode.Realtime; - ShaderTexture.antiAliasing = AntiAliasing; + msaaSamples = Mathf.Max(1, AntiAliasing) + }; - // ShaderCameraTexture 초기화 - if (ShaderCameraTexture != null) - { - ShaderCameraTexture.Release(); - DestroyImmediate(ShaderCameraTexture); // Destroy 대신 DestroyImmediate 사용 - } - ShaderCameraTexture = new RenderTexture(screenWidth, screenHeight, DepthBuffer, TextureFormat); - ShaderCameraTexture.Create(); - ShaderCameraTexture.antiAliasing = AntiAliasing; + ReleaseRT(ref CaptureTexture, ref m_CaptureHandle); + CaptureTexture = new RenderTexture(desc); + CaptureTexture.Create(); + m_CaptureHandle = RTHandles.Alloc(CaptureTexture, transferOwnership: false); - // ShaderCameraTexture를 ShaderContral의 _MainTex에 할당 - if (ShaderContral != null) - { - ShaderContral.SetTexture("_MainTex", ShaderCameraTexture); - UpdateShaderScreenSize(); - } + ReleaseRT(ref m_PreColorTexture, ref m_PreColorHandle); + m_PreColorTexture = new RenderTexture(desc); + m_PreColorTexture.Create(); + m_PreColorHandle = RTHandles.Alloc(m_PreColorTexture, transferOwnership: false); + + ReleaseRT(ref AlphaOutputTexture, ref m_AlphaOutputHandle); + AlphaOutputTexture = new RenderTexture(desc); + AlphaOutputTexture.Create(); + m_AlphaOutputHandle = RTHandles.Alloc(AlphaOutputTexture, transferOwnership: false); - // 출력 방식에 따라 텍스처 할당 UpdateOutputSources(); + InitializeScriptablePasses(); + } - // m_ScriptablePass 재초기화 - InitializeScriptablePass(); + private static void ReleaseRT(ref RenderTexture rt, ref RTHandle handle) + { + if (handle != null) + { + handle.Release(); + handle = null; + } + if (rt != null) + { + rt.Release(); + DestroyImmediate(rt); + rt = null; + } } private void UpdateOutputSources() { - // Spout 출력 설정 if (outputMethod == OutputMethod.Spout || outputMethod == OutputMethod.Both) { if (Sender != null) { Sender.enabled = true; - Sender.sourceTexture = ShaderTexture; + Sender.sourceTexture = CaptureTexture; Sender.spoutName = spoutSenderName; Sender.keepAlpha = keepAlpha; Sender.captureMethod = Klak.Spout.CaptureMethod.Texture; @@ -216,20 +343,18 @@ public class RenderStreamOutput : MonoBehaviour Sender.enabled = false; } - // NDI 출력 설정 if (outputMethod == OutputMethod.NDI || outputMethod == OutputMethod.Both) { if (NdiSender != null) { NdiSender.enabled = true; - NdiSender.sourceTexture = ShaderTexture; + NdiSender.sourceTexture = CaptureTexture; NdiSender.ndiName = ndiSenderName; NdiSender.keepAlpha = keepAlpha; NdiSender.captureMethod = Klak.Ndi.CaptureMethod.Texture; } else { - // NDI 송신 컴포넌트가 없으면 다시 초기화 시도 InitializeOutputComponents(); } } @@ -237,27 +362,41 @@ public class RenderStreamOutput : MonoBehaviour { NdiSender.enabled = false; } + + // 알파 전용 Spout 송신 (항상 활성) + if (AlphaSender != null) + { + AlphaSender.enabled = true; + AlphaSender.sourceTexture = AlphaOutputTexture; + AlphaSender.spoutName = alphaSpoutSenderName; + AlphaSender.keepAlpha = false; + AlphaSender.captureMethod = Klak.Spout.CaptureMethod.Texture; + } } - private void InitializeScriptablePass() + private void InitializeScriptablePasses() { - m_ScriptablePass = new AlphaRecodingRenderPass(ShaderCameraTexture, ShaderContral, ShaderTexture); - m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; + m_PreCapturePass = new PreCapturePass(m_PreColorHandle) + { + renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing + }; + m_ComposePass = new ComposePass(m_CaptureHandle, m_PreColorHandle, m_PreColorTexture, m_AlphaComposeMaterial) + { + renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing + }; + m_AlphaOutputPass = new AlphaOutputPass(m_CaptureHandle, m_AlphaOutputHandle, m_AlphaOnlyMaterial) + { + renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing + 1 + }; } private void Update() { - // 화면 크기 변경 체크 - 매 프레임마다 Screen.width/height 접근은 비용이 있음 - // 변경이 자주 일어나지 않으므로 더 효율적인 방법으로 변경 if (MainCam != null && (screenWidth != MainCam.pixelWidth || screenHeight != MainCam.pixelHeight)) { - screenWidth = MainCam.pixelWidth; - screenHeight = MainCam.pixelHeight; InitializeTextures(); - UpdateShaderScreenSize(); } - // 설정이 변경되었는지 확인 if (previousOutputMethod != outputMethod || previousKeepAlpha != keepAlpha) { previousOutputMethod = outputMethod; @@ -265,87 +404,247 @@ public class RenderStreamOutput : MonoBehaviour InitializeOutputComponents(); UpdateOutputSources(); } - } - private void UpdateShaderScreenSize() - { - if (ShaderContral != null) + if (previousTargetFrameRate != targetFrameRate) { - ShaderContral.SetVector("_Resolution", new Vector4(screenWidth, screenHeight, 0, 0)); + previousTargetFrameRate = targetFrameRate; + ApplyFrameRate(); } + + PushMaterialParams(); } private void OnDestroy() { - // m_ScriptablePass 정리 (내부 텍스처 참조 해제) - m_ScriptablePass = null; + m_PreCapturePass = null; + m_ComposePass = null; + m_AlphaOutputPass = null; - if (ShaderCameraTexture != null) + ReleaseRT(ref CaptureTexture, ref m_CaptureHandle); + ReleaseRT(ref m_PreColorTexture, ref m_PreColorHandle); + ReleaseRT(ref AlphaOutputTexture, ref m_AlphaOutputHandle); + + if (m_AlphaComposeMaterial != null) { - ShaderCameraTexture.Release(); - DestroyImmediate(ShaderCameraTexture); - ShaderCameraTexture = null; // 참조 제거 + DestroyImmediate(m_AlphaComposeMaterial); + m_AlphaComposeMaterial = null; } - if (ShaderTexture != null) + if (m_AlphaOnlyMaterial != null) { - ShaderTexture.Release(); - DestroyImmediate(ShaderTexture); - ShaderTexture = null; // 참조 제거 + DestroyImmediate(m_AlphaOnlyMaterial); + m_AlphaOnlyMaterial = null; } + + KillNormalizer(); } -} -public class AlphaRecodingRenderPass : ScriptableRenderPass -{ - private RenderTexture m_ShaderCameraTexture; - private Material m_ShaderContral; - private CustomRenderTexture m_ShaderTexture; + // ───────────────────────── Spout/NDI Normalizer (외부 exe) ───────────────────────── - public AlphaRecodingRenderPass(RenderTexture shaderCameraTexture, Material shaderContral, CustomRenderTexture shaderTexture) + private string ResolveNormalizerExePath() { - m_ShaderCameraTexture = shaderCameraTexture; - m_ShaderContral = shaderContral; - m_ShaderTexture = shaderTexture; + if (!string.IsNullOrEmpty(normalizerExeAbsolutePath)) + return normalizerExeAbsolutePath; + var rel = normalizerExeRelativePath.Replace('/', Path.DirectorySeparatorChar); + return Path.Combine(Application.streamingAssetsPath, rel); } - [Obsolete("This method is obsolete. Use the new Render Graph API instead.")] - public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) + public void LaunchNormalizer() + { + var exe = ResolveNormalizerExePath(); + if (!File.Exists(exe)) + { + Debug.LogError($"[RenderStreamOutput] normalizer exe not found: {exe}"); + return; + } + + // 기존 자식 프로세스 + orphan 모두 종료 후 새로 실행 (단일 인스턴스 보장) + KillNormalizer(); + KillExternalNormalizers(exe); + + var args = BuildNormalizerArgs(); + var psi = new ProcessStartInfo(exe, args) + { + UseShellExecute = false, + CreateNoWindow = !normalizerShowConsole, + WindowStyle = normalizerShowConsole ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden, + WorkingDirectory = Path.GetDirectoryName(exe), + }; + + m_NormalizerProc = Process.Start(psi); + if (m_NormalizerProc == null) + { + Debug.LogError("[RenderStreamOutput] normalizer Process.Start returned null"); + return; + } + Debug.Log($"[RenderStreamOutput] normalizer launched PID {m_NormalizerProc.Id}\n exe={exe}\n args={args}"); + } + + public void KillNormalizer() { try { - if (m_ShaderCameraTexture == null || m_ShaderContral == null || !m_ShaderCameraTexture.IsCreated()) - return; - - CommandBuffer cmd = CommandBufferPool.Get("Alpha Recoding Pass"); - - try + if (m_NormalizerProc != null && !m_NormalizerProc.HasExited) { - // Update cameraColorTargetHandle usage - var colorTarget = renderingData.cameraData.renderer.cameraColorTargetHandle; - - // 최종 렌더링 결과를 가져옵니다 - RenderTargetIdentifier source = colorTarget; - cmd.Blit(source, m_ShaderCameraTexture); - - // ShaderCameraTexture를 ShaderContral의 _MainTex에 할당 - m_ShaderContral.SetTexture("_MainTex", m_ShaderCameraTexture); - - // ShaderTexture 업데이트 (필요한 경우) - if (m_ShaderTexture != null) - { - m_ShaderTexture.Update(); - } - - context.ExecuteCommandBuffer(cmd); - } - finally - { - CommandBufferPool.Release(cmd); + m_NormalizerProc.Kill(); + m_NormalizerProc.WaitForExit(2000); } } - catch (System.Exception e) + catch (Exception e) { Debug.LogWarning($"[RenderStreamOutput] normalizer kill: {e.Message}"); } + m_NormalizerProc = null; + } + + private string BuildNormalizerArgs() + { + string Q(string s) => "\"" + s.Replace("\"", "\\\"") + "\""; + + var parts = new System.Collections.Generic.List { - Debug.LogError($"Alpha Recoding 렌더 패스 실행 중 오류 발생: {e.Message}"); + "--sender", Q(spoutSenderName), + "--fps", normalizerFps.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture), + "--width", normalizerWindowWidth.ToString(), + "--height", normalizerWindowHeight.ToString(), + "--parent-pid", Process.GetCurrentProcess().Id.ToString(), + }; + int monitorIndex = (int)normalizerDisplay; + if (monitorIndex >= 0) { parts.Add("--monitor"); parts.Add(monitorIndex.ToString()); } + if (!normalizerVsync) parts.Add("--no-vsync"); + if (normalizerAlwaysOnTop) parts.Add("--topmost"); + if (normalizerHideCursor) parts.Add("--hide-cursor"); + if (normalizerRealtime) parts.Add("--realtime"); + if (normalizerNoActivate) parts.Add("--no-activate"); + return string.Join(" ", parts); + } + + private static void KillExternalNormalizers(string exePath) + { + var name = Path.GetFileNameWithoutExtension(exePath); + try + { + foreach (var p in Process.GetProcessesByName(name)) + { + try + { + if (!p.HasExited) + { + p.Kill(); + p.WaitForExit(2000); + Debug.Log($"[RenderStreamOutput] killed orphan normalizer PID {p.Id}"); + } + } + catch (Exception e) { Debug.LogWarning($"[RenderStreamOutput] kill orphan: {e.Message}"); } + finally { p.Dispose(); } + } + } + catch { /* best effort */ } + } +} + +// 후처리 전 cameraColor 를 PreColorTexture 로 백업 +public class PreCapturePass : ScriptableRenderPass +{ + private RTHandle m_PreColorHandle; + + public PreCapturePass(RTHandle preColorHandle) + { + m_PreColorHandle = preColorHandle; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + if (m_PreColorHandle == null) + return; + + var resourceData = frameData.Get(); + if (resourceData == null) + return; + + TextureHandle source = resourceData.activeColorTexture; + if (!source.IsValid()) + return; + + TextureHandle destination = renderGraph.ImportTexture(m_PreColorHandle); + using (var builder = renderGraph.AddBlitPass(source, destination, Vector2.one, Vector2.zero, + passName: "Streamingle Pre-PostProcess Capture", returnBuilder: true)) + { + builder.AllowPassCulling(false); + } + } +} + +// CaptureTexture 의 알파를 그레이스케일 RGB 로 변환해서 AlphaOutputTexture 로 출력 +public class AlphaOutputPass : ScriptableRenderPass +{ + private RTHandle m_SourceHandle; + private RTHandle m_DestHandle; + private Material m_Material; + + public AlphaOutputPass(RTHandle sourceHandle, RTHandle destHandle, Material material) + { + m_SourceHandle = sourceHandle; + m_DestHandle = destHandle; + m_Material = material; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + if (m_SourceHandle == null || m_DestHandle == null || m_Material == null) + return; + + TextureHandle source = renderGraph.ImportTexture(m_SourceHandle); + TextureHandle destination = renderGraph.ImportTexture(m_DestHandle); + + var blitParams = new RenderGraphUtils.BlitMaterialParameters(source, destination, m_Material, 0); + using (var builder = renderGraph.AddBlitPass(blitParams, + passName: "Streamingle Alpha-Only Output", returnBuilder: true)) + { + builder.AllowPassCulling(false); + } + } +} + +// 후처리 후 cameraColor + PreColorTexture + NiloToonPrepass.g 합성 → CaptureTexture +public class ComposePass : ScriptableRenderPass +{ + private RTHandle m_CaptureHandle; + private RTHandle m_PreColorHandle; + private RenderTexture m_PreColorRT; + private Material m_Material; + private static readonly int s_PreColorTexID = Shader.PropertyToID("_PreColorTex"); + + public ComposePass(RTHandle captureHandle, RTHandle preColorHandle, RenderTexture preColorRT, Material material) + { + m_CaptureHandle = captureHandle; + m_PreColorHandle = preColorHandle; + m_PreColorRT = preColorRT; + m_Material = material; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + if (m_CaptureHandle == null || m_PreColorHandle == null || m_Material == null) + return; + + var resourceData = frameData.Get(); + if (resourceData == null) + return; + + TextureHandle source = resourceData.activeColorTexture; + if (!source.IsValid()) + return; + + TextureHandle destination = renderGraph.ImportTexture(m_CaptureHandle); + TextureHandle preColor = renderGraph.ImportTexture(m_PreColorHandle); + + // 머티리얼에 PreColor 텍스처 직접 바인딩 (외부 RT 라 lifetime 안전). + // RenderGraph 는 UseTexture 로 PreCapturePass→ComposePass 순서만 보장. + m_Material.SetTexture(s_PreColorTexID, m_PreColorRT); + + var blitParams = new RenderGraphUtils.BlitMaterialParameters(source, destination, m_Material, 0); + using (var builder = renderGraph.AddBlitPass(blitParams, + passName: "Streamingle Alpha Compose", returnBuilder: true)) + { + builder.UseTexture(preColor); + builder.AllowPassCulling(false); } } } diff --git a/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaCompose.shader b/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaCompose.shader new file mode 100644 index 000000000..1f0e644e7 --- /dev/null +++ b/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaCompose.shader @@ -0,0 +1,96 @@ +// 후처리 후 RGB + 합성 알파를 출력하는 풀스크린 Blit 셰이더. +// +// 알파 = saturate(prepassAlpha + bloomAlpha) +// prepassAlpha = NiloToon Prepass G 채널 (캐릭터 외곽, AA 포함) +// bloomAlpha = saturate((postLum - preLum) * _BloomAlphaGain) — 후처리가 추가한 빛 +// +// _PreColorTex 가 비어있으면 bloomAlpha = 0 (Prepass 만 사용) +// _NiloToonPrepassBufferTex 가 비어있으면 prepassAlpha = 0 (Bloom diff 만 사용) +Shader "Hidden/Streamingle/AlphaCompose" +{ + Properties + { + _SourceAlphaGain ("Source Alpha Gain (cameraColor.a)", Range(0, 4)) = 1.0 + _BloomAlphaGain ("Bloom Alpha Gain", Range(0, 20)) = 5.0 + _PrepassAlphaGain ("Prepass Alpha Gain", Range(0, 4)) = 1.0 + _AlphaBlurRadius ("Alpha Blur Radius (texels)", Range(0, 5)) = 1.0 + } + + SubShader + { + Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } + + Pass + { + ZWrite Off ZTest Always Cull Off + Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment Frag + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl" + #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" + + // _BlitTexture 는 Blit.hlsl 가 이미 선언함 (후처리 후 cameraColor) + TEXTURE2D(_PreColorTex); // 후처리 전 cameraColor + SAMPLER(sampler_PreColorTex); + + TEXTURE2D(_NiloToonPrepassBufferTex); // NiloToon 글로벌 + SAMPLER(sampler_NiloToonPrepassBufferTex); + + float _SourceAlphaGain; + float _BloomAlphaGain; + float _PrepassAlphaGain; + float _AlphaBlurRadius; + + // 3x3 가우시안 가중치 (1/4 1/8 1/16) — Prepass G 외곽 부드럽게 + half SamplePrepassAlpha(float2 uv) + { + if (_AlphaBlurRadius <= 0.001) + return SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, + sampler_NiloToonPrepassBufferTex, uv).g; + + float2 t = (1.0 / _ScreenParams.xy) * _AlphaBlurRadius; + + half a = 0; + a += SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, sampler_NiloToonPrepassBufferTex, uv).g * 0.25; + a += SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, sampler_NiloToonPrepassBufferTex, uv + float2( 0, t.y)).g * 0.125; + a += SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, sampler_NiloToonPrepassBufferTex, uv + float2( 0, -t.y)).g * 0.125; + a += SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, sampler_NiloToonPrepassBufferTex, uv + float2( t.x, 0)).g * 0.125; + a += SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, sampler_NiloToonPrepassBufferTex, uv + float2(-t.x, 0)).g * 0.125; + a += SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, sampler_NiloToonPrepassBufferTex, uv + float2( t.x, t.y)).g * 0.0625; + a += SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, sampler_NiloToonPrepassBufferTex, uv + float2(-t.x, t.y)).g * 0.0625; + a += SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, sampler_NiloToonPrepassBufferTex, uv + float2( t.x, -t.y)).g * 0.0625; + a += SAMPLE_TEXTURE2D(_NiloToonPrepassBufferTex, sampler_NiloToonPrepassBufferTex, uv + float2(-t.x, -t.y)).g * 0.0625; + return a; + } + + half4 Frag(Varyings input) : SV_Target + { + float2 uv = input.texcoord; + + half4 postSample = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv); + half3 postRgb = postSample.rgb; + half sourceAlpha = postSample.a * _SourceAlphaGain; + + half3 preRgb = SAMPLE_TEXTURE2D(_PreColorTex, sampler_PreColorTex, uv).rgb; + + // 후처리가 추가한 luminance (블룸/글로우/플레어 등) + half postLum = Luminance(postRgb); + half preLum = Luminance(preRgb); + half bloomAlpha = saturate((postLum - preLum) * _BloomAlphaGain); + + // NiloToon Prepass G 채널 알파 (캐릭터 외곽, 가우시안 블러로 AA) + half prepassAlpha = SamplePrepassAlpha(uv) * _PrepassAlphaGain; + + half alpha = saturate(sourceAlpha + prepassAlpha + bloomAlpha); + + return half4(postRgb, alpha); + } + ENDHLSL + } + } + Fallback Off +} diff --git a/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaCompose.shader.meta b/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaCompose.shader.meta new file mode 100644 index 000000000..faaee268c --- /dev/null +++ b/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaCompose.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9d778847d83c98846bc8e7c220f88356 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaOnly.shader b/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaOnly.shader new file mode 100644 index 000000000..dc75544d3 --- /dev/null +++ b/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaOnly.shader @@ -0,0 +1,30 @@ +// CaptureTexture 의 알파를 그레이스케일 RGB 로 출력. 알파는 1 고정. +// 검정 = 알파 0, 흰색 = 알파 1 +Shader "Hidden/Streamingle/AlphaOnly" +{ + SubShader + { + Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } + + Pass + { + ZWrite Off ZTest Always Cull Off + Blend Off + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment Frag + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" + + half4 Frag(Varyings input) : SV_Target + { + half a = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, input.texcoord).a; + return half4(a, a, a, 1.0); + } + ENDHLSL + } + } + Fallback Off +} diff --git a/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaOnly.shader.meta b/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaOnly.shader.meta new file mode 100644 index 000000000..c9733a7f0 --- /dev/null +++ b/Assets/Scripts/SpoutOutputScript/Shaders/StreamingleAlphaOnly.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 80cfe82978045b341b4aa611843ae050 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/System/ScreenshotManager.cs b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/System/ScreenshotManager.cs index 030e3ae86..059c034c1 100644 --- a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/System/ScreenshotManager.cs +++ b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/System/ScreenshotManager.cs @@ -1,67 +1,54 @@ using UnityEngine; using System; +using System.Collections; using System.IO; /// -/// 스크린샷 캡처 관리 (RGB + 알파채널) +/// 스크린샷 캡처 관리. +/// RenderStreamOutput 이 매 프레임 갱신하는 CaptureTexture/AlphaOutputTexture 를 PNG 로 저장. +/// captureWidth/Height 가 지정되면 캡처 직전에 카메라 targetTexture 를 임시로 그 해상도로 +/// 변경해 한 프레임 고해상도 렌더 후 원복한다. +/// 트레이드오프: 그 한 프레임 동안 Spout/NDI 송신도 같은 해상도가 되어 수신측이 잠깐 깜빡임. /// [Serializable] public class ScreenshotManager { - [Tooltip("스크린샷 해상도 (기본: 4K)")] - public int screenshotWidth = 3840; + [Tooltip("RenderStreamOutput 컴포넌트 (비어있으면 씬에서 자동 검색)")] + public RenderStreamOutput renderStream; - [Tooltip("스크린샷 해상도 (기본: 4K)")] - public int screenshotHeight = 2160; - - [Tooltip("스크린샷 저장 경로 (비어있으면 바탕화면)")] + [Tooltip("스크린샷 저장 경로 (비어있으면 프로젝트 루트의 Screenshots/)")] public string screenshotSavePath = ""; [Tooltip("파일명 앞에 붙을 접두사")] public string screenshotFilePrefix = "Screenshot"; - [Tooltip("알파 채널 추출용 셰이더")] - public Shader alphaShader; + [Header("고해상도 캡처 (0 이면 카메라 해상도 사용)")] + [Tooltip("캡처 너비. 0 이면 카메라 해상도 그대로")] + public int captureWidth = 3840; + [Tooltip("캡처 높이. 0 이면 카메라 해상도 그대로")] + public int captureHeight = 2160; - [Tooltip("NiloToon Prepass 버퍼 텍스처 이름")] - public string niloToonPrepassBufferName = "_NiloToonPrepassBufferTex"; - - [Tooltip("촬영할 카메라 (비어있으면 메인 카메라 사용)")] - public Camera screenshotCamera; - - [Tooltip("알파 채널 블러 반경 (0 = 블러 없음, 1.0 = 약한 블러)")] - [Range(0f, 3f)] - public float alphaBlurRadius = 1.0f; - - [NonSerialized] - private Material alphaMaterial; + [Tooltip("(하위 호환) 외부 코드가 .screenshotCamera 로 접근하면 RenderStreamOutput.MainCam 반환")] + public Camera screenshotCamera => renderStream != null ? renderStream.MainCam : null; + private MonoBehaviour host; private Action log; private Action logError; - public void Initialize(Action log, Action logError) + public void Initialize(MonoBehaviour host, Action log, Action logError) { + this.host = host; this.log = log; this.logError = logError; - if (screenshotCamera == null) - { - screenshotCamera = Camera.main; - } + if (renderStream == null) + renderStream = UnityEngine.Object.FindFirstObjectByType(); - if (alphaShader == null) - { - alphaShader = Shader.Find("Hidden/AlphaFromNiloToon"); - if (alphaShader == null) - { - logError?.Invoke("알파 셰이더를 찾을 수 없습니다: Hidden/AlphaFromNiloToon"); - } - } + if (renderStream == null) + logError?.Invoke("RenderStreamOutput 컴포넌트를 찾을 수 없습니다 — 알파 합성 캡처 불가"); if (string.IsNullOrEmpty(screenshotSavePath)) - { screenshotSavePath = Path.Combine(Application.dataPath, "..", "Screenshots"); - } if (!Directory.Exists(screenshotSavePath)) { @@ -72,113 +59,75 @@ public class ScreenshotManager public void CaptureScreenshot() { - if (screenshotCamera == null) - { - logError?.Invoke("촬영할 카메라가 설정되지 않았습니다!"); - return; - } - - string fileName = GenerateFileName("png"); - string fullPath = Path.Combine(screenshotSavePath, fileName); - - try - { - RenderTexture rt = new RenderTexture(screenshotWidth, screenshotHeight, 24); - RenderTexture currentRT = screenshotCamera.targetTexture; - - screenshotCamera.targetTexture = rt; - screenshotCamera.Render(); - - RenderTexture.active = rt; - Texture2D screenshot = new Texture2D(screenshotWidth, screenshotHeight, TextureFormat.RGB24, false); - screenshot.ReadPixels(new Rect(0, 0, screenshotWidth, screenshotHeight), 0, 0); - screenshot.Apply(); - - byte[] bytes = screenshot.EncodeToPNG(); - File.WriteAllBytes(fullPath, bytes); - - screenshotCamera.targetTexture = currentRT; - RenderTexture.active = null; - rt.Release(); - UnityEngine.Object.Destroy(rt); - UnityEngine.Object.Destroy(screenshot); - - log?.Invoke($"스크린샷 저장 완료: {fullPath}"); - } - catch (Exception e) - { - logError?.Invoke($"스크린샷 촬영 실패: {e.Message}"); - } + if (!CanCapture()) return; + host.StartCoroutine(CaptureCoroutine(alphaOnly: false)); } public void CaptureAlphaScreenshot() { - if (screenshotCamera == null) + 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) { - logError?.Invoke("촬영할 카메라가 설정되지 않았습니다!"); - return; + logError?.Invoke("RenderStreamOutput / MainCam 이 준비되지 않았습니다"); + return false; } + return true; + } - if (alphaShader == null) + private IEnumerator CaptureCoroutine(bool alphaOnly) + { + var cam = renderStream.MainCam; + bool useTempResolution = captureWidth > 0 && captureHeight > 0; + + RenderTexture tempRT = null; + RenderTexture prevTarget = null; + + if (useTempResolution) { - logError?.Invoke("알파 셰이더가 설정되지 않았습니다!"); - return; - } - - string fileName = GenerateFileName("png", "_Alpha"); - string fullPath = Path.Combine(screenshotSavePath, fileName); - - try - { - RenderTexture rt = new RenderTexture(screenshotWidth, screenshotHeight, 24); - RenderTexture currentRT = screenshotCamera.targetTexture; - - screenshotCamera.targetTexture = rt; - screenshotCamera.Render(); - - Texture niloToonPrepassBuffer = Shader.GetGlobalTexture(niloToonPrepassBufferName); - - if (niloToonPrepassBuffer == null) + prevTarget = cam.targetTexture; + var desc = new RenderTextureDescriptor(captureWidth, captureHeight, + renderStream.TextureFormat, 24) { - logError?.Invoke($"NiloToon Prepass 버퍼를 찾을 수 없습니다: {niloToonPrepassBufferName}"); - screenshotCamera.targetTexture = currentRT; - UnityEngine.Object.Destroy(rt); - return; - } + msaaSamples = Mathf.Max(1, renderStream.AntiAliasing) + }; + tempRT = new RenderTexture(desc); + tempRT.Create(); - if (alphaMaterial == null) - { - alphaMaterial = new Material(alphaShader); - } + cam.targetTexture = tempRT; - RenderTexture alphaRT = new RenderTexture(screenshotWidth, screenshotHeight, 0, RenderTextureFormat.ARGB32); - alphaMaterial.SetTexture("_MainTex", rt); - alphaMaterial.SetTexture("_AlphaTex", niloToonPrepassBuffer); - alphaMaterial.SetFloat("_BlurRadius", alphaBlurRadius); - - Graphics.Blit(rt, alphaRT, alphaMaterial); - - RenderTexture.active = alphaRT; - Texture2D screenshot = new Texture2D(screenshotWidth, screenshotHeight, TextureFormat.RGBA32, false); - screenshot.ReadPixels(new Rect(0, 0, screenshotWidth, screenshotHeight), 0, 0); - screenshot.Apply(); - - byte[] bytes = screenshot.EncodeToPNG(); - File.WriteAllBytes(fullPath, bytes); - - screenshotCamera.targetTexture = currentRT; - RenderTexture.active = null; - rt.Release(); - UnityEngine.Object.Destroy(rt); - alphaRT.Release(); - UnityEngine.Object.Destroy(alphaRT); - UnityEngine.Object.Destroy(screenshot); - - log?.Invoke($"알파 스크린샷 저장 완료: {fullPath}"); + // 1프레임 — RenderStreamOutput.Update 가 새 pixelWidth 감지 → InitializeTextures(고해상도) + yield return null; + // 1프레임 — 고해상도 RT 에 우리 패스 결과 채워짐 + yield return new WaitForEndOfFrame(); } - catch (Exception e) + + var srcRT = alphaOnly ? renderStream.AlphaOutputTexture : renderStream.CaptureTexture; + if (srcRT != null) { - logError?.Invoke($"알파 스크린샷 촬영 실패: {e.Message}"); + 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; } } @@ -195,6 +144,33 @@ public class ScreenshotManager } } + private void SaveRtAsPng(RenderTexture rt, string fileName, TextureFormat format) + { + string fullPath = Path.Combine(screenshotSavePath, fileName); + RenderTexture prevActive = RenderTexture.active; + Texture2D tex = null; + + try + { + RenderTexture.active = rt; + tex = new Texture2D(rt.width, rt.height, format, false); + tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); + tex.Apply(); + + File.WriteAllBytes(fullPath, tex.EncodeToPNG()); + log?.Invoke($"스크린샷 저장 완료: {fullPath} ({rt.width}x{rt.height})"); + } + catch (Exception e) + { + logError?.Invoke($"스크린샷 저장 실패 ({fileName}): {e.Message}"); + } + finally + { + RenderTexture.active = prevActive; + if (tex != null) UnityEngine.Object.Destroy(tex); + } + } + private string GenerateFileName(string extension, string suffix = "") { string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); @@ -203,9 +179,6 @@ public class ScreenshotManager public void Cleanup() { - if (alphaMaterial != null) - { - UnityEngine.Object.Destroy(alphaMaterial); - } + // RenderStreamOutput 이 모든 RT/Material lifecycle 을 관리하므로 별도 정리 없음 } } diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Controllers/SystemController.cs b/Assets/Scripts/Streamingle/StreamingleControl/Controllers/SystemController.cs index 7235661d6..420acb612 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(Log, LogError); + screenshot.Initialize(this, 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 73632c412..99eeb3b40 100644 --- a/Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/SystemControllerEditor.uxml +++ b/Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/SystemControllerEditor.uxml @@ -29,14 +29,12 @@ - - - + + - - - + + diff --git a/Assets/StreamingAssets.meta b/Assets/StreamingAssets.meta new file mode 100644 index 000000000..f6639edae --- /dev/null +++ b/Assets/StreamingAssets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 125f274339c271348b3ac9e905d1b197 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/StreamingAssets/SpoutNdiNormalizer.meta b/Assets/StreamingAssets/SpoutNdiNormalizer.meta new file mode 100644 index 000000000..adaa6bae7 --- /dev/null +++ b/Assets/StreamingAssets/SpoutNdiNormalizer.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd5ef184cfe270f44b04b8d20b5362e5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/StreamingAssets/SpoutNdiNormalizer/README.md b/Assets/StreamingAssets/SpoutNdiNormalizer/README.md new file mode 100644 index 000000000..363b53c2e --- /dev/null +++ b/Assets/StreamingAssets/SpoutNdiNormalizer/README.md @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52075f10280b08adb4aa259a72479da0c6bbae7e80705b36346144d8bdb13feb +size 4242 diff --git a/Assets/StreamingAssets/SpoutNdiNormalizer/README.md.meta b/Assets/StreamingAssets/SpoutNdiNormalizer/README.md.meta new file mode 100644 index 000000000..918df58df --- /dev/null +++ b/Assets/StreamingAssets/SpoutNdiNormalizer/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 556dd080c71356944abc6a238e97d8cc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/StreamingAssets/SpoutNdiNormalizer/spout_ndi_normalizer.exe b/Assets/StreamingAssets/SpoutNdiNormalizer/spout_ndi_normalizer.exe new file mode 100644 index 0000000000000000000000000000000000000000..c202207766079b7337822cd8ecb7cd3a1f367a11 GIT binary patch literal 137728 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 M7jypp|Fz%$4LUwmegFUf literal 0 HcmV?d00001 diff --git a/Assets/StreamingAssets/SpoutNdiNormalizer/spout_ndi_normalizer.exe.meta b/Assets/StreamingAssets/SpoutNdiNormalizer/spout_ndi_normalizer.exe.meta new file mode 100644 index 000000000..35759c1fc --- /dev/null +++ b/Assets/StreamingAssets/SpoutNdiNormalizer/spout_ndi_normalizer.exe.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c169e09324be3404dbef88e305616fd4 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: