2025-12-02 20:38:32 +09:00

352 lines
11 KiB
C#

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<SpoutSender>();
if (Sender == null)
{
Sender = gameObject.AddComponent<SpoutSender>();
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<NdiSender>();
if (NdiSender == null)
{
Debug.Log("NDI 송신 컴포넌트를 생성합니다.");
NdiSender = gameObject.AddComponent<NdiSender>();
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}");
}
}
}