352 lines
11 KiB
C#
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}");
|
|
}
|
|
}
|
|
}
|