using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using Klak.Spout; using Klak.Ndi; using System; // 출력 방식 열거형 public enum OutputMethod { None, Spout, NDI, Both } public class RenderStreamOutput : MonoBehaviour { [Header("출력 설정")] [Tooltip("출력 방식 선택 (Spout, NDI, 또는 둘 다)")] public OutputMethod outputMethod = OutputMethod.Spout; [Tooltip("알파 채널 유지")] public bool keepAlpha = true; [Header("카메라 설정")] [Tooltip("렌더링 소스로 사용할 카메라")] public Camera MainCam; [Header("Spout 설정")] [Tooltip("Spout 송신 컴포넌트")] public SpoutSender Sender; [Tooltip("Spout 송신 이름")] public string spoutSenderName = "Streamingle Spout Output"; [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; private AlphaRecodingRenderPass m_ScriptablePass; // 화면 크기 체크를 위한 변수 private int screenWidth; private int screenHeight; // 이전 출력 방식 저장 private OutputMethod previousOutputMethod; // 이전 알파 채널 설정 저장 private bool previousKeepAlpha; private void Awake() { // 컴포넌트 자동 할당 MainCam = MainCam == null ? Camera.main : MainCam; // 초기 설정 저장 previousOutputMethod = outputMethod; previousKeepAlpha = keepAlpha; // 출력 컴포넌트 초기화 InitializeOutputComponents(); InitializeTextures(); } 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 송신 컴포넌트 생성 실패."); } } } } } private void OnEnable() { InitializeScriptablePass(); RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering; } private void OnDisable() { RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering; } private void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera) { if (camera == MainCam) { var data = camera.GetUniversalAdditionalCameraData(); data.scriptableRenderer.EnqueuePass(m_ScriptablePass); } } 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) { ShaderTexture.Release(); DestroyImmediate(ShaderTexture); // Destroy 대신 DestroyImmediate 사용 } ShaderTexture = new CustomRenderTexture(screenWidth, screenHeight, TextureFormat); ShaderTexture.material = ShaderContral; ShaderTexture.updateMode = CustomRenderTextureUpdateMode.Realtime; ShaderTexture.antiAliasing = AntiAliasing; // ShaderCameraTexture 초기화 if (ShaderCameraTexture != null) { ShaderCameraTexture.Release(); DestroyImmediate(ShaderCameraTexture); // Destroy 대신 DestroyImmediate 사용 } ShaderCameraTexture = new RenderTexture(screenWidth, screenHeight, DepthBuffer, TextureFormat); ShaderCameraTexture.Create(); ShaderCameraTexture.antiAliasing = AntiAliasing; // ShaderCameraTexture를 ShaderContral의 _MainTex에 할당 if (ShaderContral != null) { ShaderContral.SetTexture("_MainTex", ShaderCameraTexture); UpdateShaderScreenSize(); } // 출력 방식에 따라 텍스처 할당 UpdateOutputSources(); // m_ScriptablePass 재초기화 InitializeScriptablePass(); } private void UpdateOutputSources() { // Spout 출력 설정 if (outputMethod == OutputMethod.Spout || outputMethod == OutputMethod.Both) { if (Sender != null) { Sender.enabled = true; Sender.sourceTexture = ShaderTexture; Sender.spoutName = spoutSenderName; Sender.keepAlpha = keepAlpha; Sender.captureMethod = Klak.Spout.CaptureMethod.Texture; } } else if (Sender != null) { Sender.enabled = false; } // NDI 출력 설정 if (outputMethod == OutputMethod.NDI || outputMethod == OutputMethod.Both) { if (NdiSender != null) { NdiSender.enabled = true; NdiSender.sourceTexture = ShaderTexture; NdiSender.ndiName = ndiSenderName; NdiSender.keepAlpha = keepAlpha; NdiSender.captureMethod = Klak.Ndi.CaptureMethod.Texture; } else { // NDI 송신 컴포넌트가 없으면 다시 초기화 시도 InitializeOutputComponents(); } } else if (NdiSender != null) { NdiSender.enabled = false; } } private void InitializeScriptablePass() { m_ScriptablePass = new AlphaRecodingRenderPass(ShaderCameraTexture, ShaderContral, ShaderTexture); m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; } 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; previousKeepAlpha = keepAlpha; InitializeOutputComponents(); UpdateOutputSources(); } } private void UpdateShaderScreenSize() { if (ShaderContral != null) { ShaderContral.SetVector("_Resolution", new Vector4(screenWidth, screenHeight, 0, 0)); } } private void OnDestroy() { // m_ScriptablePass 정리 (내부 텍스처 참조 해제) m_ScriptablePass = null; if (ShaderCameraTexture != null) { ShaderCameraTexture.Release(); DestroyImmediate(ShaderCameraTexture); ShaderCameraTexture = null; // 참조 제거 } if (ShaderTexture != null) { ShaderTexture.Release(); DestroyImmediate(ShaderTexture); ShaderTexture = null; // 참조 제거 } } } public class AlphaRecodingRenderPass : ScriptableRenderPass { private RenderTexture m_ShaderCameraTexture; private Material m_ShaderContral; private CustomRenderTexture m_ShaderTexture; public AlphaRecodingRenderPass(RenderTexture shaderCameraTexture, Material shaderContral, CustomRenderTexture shaderTexture) { m_ShaderCameraTexture = shaderCameraTexture; m_ShaderContral = shaderContral; m_ShaderTexture = shaderTexture; } [Obsolete("This method is obsolete. Use the new Render Graph API instead.")] public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { try { if (m_ShaderCameraTexture == null || m_ShaderContral == null || !m_ShaderCameraTexture.IsCreated()) return; CommandBuffer cmd = CommandBufferPool.Get("Alpha Recoding Pass"); try { // 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); } } catch (System.Exception e) { Debug.LogError($"Alpha Recoding 렌더 패스 실행 중 오류 발생: {e.Message}"); } } }