- 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) <noreply@anthropic.com>
196 lines
6.6 KiB
C#
196 lines
6.6 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
/// <summary>
|
|
/// StreamDeck 단일 기능 버튼들을 통합 관리하는 시스템 컨트롤러
|
|
/// 각 기능은 서브매니저로 분리되어 있으며, 명령어 디스패치 파사드 역할
|
|
/// </summary>
|
|
public class SystemController : MonoBehaviour
|
|
{
|
|
[Header("OptiTrack")]
|
|
public OptiTrackManager optiTrack = new OptiTrackManager();
|
|
|
|
[Header("Facial Motion Capture")]
|
|
public FacialMotionManager facialMotion = new FacialMotionManager();
|
|
|
|
[Header("Motion Recording")]
|
|
public MotionRecordingManager motionRecording = new MotionRecordingManager();
|
|
|
|
[Header("Screenshot")]
|
|
public ScreenshotManager screenshot = new ScreenshotManager();
|
|
|
|
[Header("Cloth Simulation")]
|
|
public ClothSimulationManager clothSimulation = new ClothSimulationManager();
|
|
|
|
[Header("Avatar Head")]
|
|
public AvatarHeadManager avatarHead = new AvatarHeadManager();
|
|
|
|
[Header("Retargeting Remote")]
|
|
public RetargetingRemoteManager retargetingRemote = new RetargetingRemoteManager();
|
|
|
|
[Header("Runtime Control Panel")]
|
|
public RuntimeControlPanelManager runtimeControlPanel = new RuntimeControlPanelManager();
|
|
|
|
[Header("Debug")]
|
|
public bool enableDebugLog = true;
|
|
|
|
// 싱글톤 패턴
|
|
public static SystemController Instance { get; private set; }
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance == null)
|
|
{
|
|
Instance = this;
|
|
}
|
|
else
|
|
{
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
optiTrack.Initialize(Log, LogError);
|
|
facialMotion.Initialize(Log, LogError);
|
|
motionRecording.Initialize(optiTrack, Log, LogError);
|
|
screenshot.Initialize(this, Log, LogError);
|
|
clothSimulation.Initialize(Log, LogError);
|
|
avatarHead.Initialize(Log, LogError);
|
|
retargetingRemote.Initialize(Log, LogError);
|
|
runtimeControlPanel.Initialize(transform, Log, LogError);
|
|
|
|
Log("SystemController 초기화 완료");
|
|
Log($"OptiTrack 클라이언트: {(optiTrack.optitrackClient != null ? "설정됨" : "없음")}");
|
|
Log($"Motion Recorder 개수: {motionRecording.motionRecorders.Count}");
|
|
Log($"Facial Motion 클라이언트 개수: {facialMotion.facialMotionClients.Count}");
|
|
Log($"Screenshot 카메라: {(screenshot.screenshotCamera != null ? "설정됨" : "없음")}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 명령어 실행 - WebSocket에서 받은 명령을 적절한 매니저로 디스패치
|
|
/// </summary>
|
|
public void ExecuteCommand(string command, Dictionary<string, object> parameters)
|
|
{
|
|
Log($"명령어 실행: {command}");
|
|
|
|
switch (command)
|
|
{
|
|
// OptiTrack 마커
|
|
case "toggle_optitrack_markers":
|
|
optiTrack.ToggleOptitrackMarkers();
|
|
break;
|
|
case "show_optitrack_markers":
|
|
optiTrack.ShowOptitrackMarkers();
|
|
break;
|
|
case "hide_optitrack_markers":
|
|
optiTrack.HideOptitrackMarkers();
|
|
break;
|
|
case "reconnect_optitrack":
|
|
optiTrack.ReconnectOptitrack();
|
|
break;
|
|
case "spawn_optitrack_client":
|
|
optiTrack.SpawnOptitrackClientFromPrefab();
|
|
break;
|
|
|
|
// Facial Motion
|
|
case "reconnect_facial_motion":
|
|
facialMotion.ReconnectFacialMotion();
|
|
break;
|
|
case "refresh_facial_motion_clients":
|
|
facialMotion.RefreshFacialMotionClients();
|
|
break;
|
|
|
|
// Motion Recording
|
|
case "start_motion_recording":
|
|
motionRecording.StartMotionRecording();
|
|
break;
|
|
case "stop_motion_recording":
|
|
motionRecording.StopMotionRecording();
|
|
break;
|
|
case "toggle_motion_recording":
|
|
motionRecording.ToggleMotionRecording();
|
|
break;
|
|
case "refresh_motion_recorders":
|
|
motionRecording.RefreshMotionRecorders();
|
|
break;
|
|
|
|
// Screenshot
|
|
case "capture_screenshot":
|
|
screenshot.CaptureScreenshot();
|
|
break;
|
|
case "capture_alpha_screenshot":
|
|
screenshot.CaptureAlphaScreenshot();
|
|
break;
|
|
case "open_screenshot_folder":
|
|
screenshot.OpenScreenshotFolder();
|
|
break;
|
|
|
|
// Avatar Head
|
|
case "refresh_avatar_head_colliders":
|
|
avatarHead.RefreshAvatarHeadColliders();
|
|
break;
|
|
|
|
// MagicaCloth
|
|
case "refresh_magica_cloth":
|
|
case "reset_magica_cloth":
|
|
clothSimulation.ResetAllMagicaCloth();
|
|
break;
|
|
case "reset_magica_cloth_keep_pose":
|
|
clothSimulation.ResetAllMagicaClothKeepPose();
|
|
break;
|
|
|
|
// Retargeting Remote
|
|
case "start_retargeting_remote":
|
|
retargetingRemote.StartRetargetingRemote();
|
|
break;
|
|
case "stop_retargeting_remote":
|
|
retargetingRemote.StopRetargetingRemote();
|
|
break;
|
|
case "toggle_retargeting_remote":
|
|
retargetingRemote.ToggleRetargetingRemote();
|
|
break;
|
|
case "refresh_retargeting_characters":
|
|
retargetingRemote.AutoRegisterRetargetingCharacters();
|
|
break;
|
|
|
|
default:
|
|
LogError($"알 수 없는 명령어: {command}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
runtimeControlPanel.Tick();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
screenshot.Cleanup();
|
|
runtimeControlPanel.Cleanup();
|
|
}
|
|
|
|
// --- 하위 호환 프로퍼티 (StreamDeckServerManager 등 외부 참조용) ---
|
|
|
|
public OptitrackStreamingClient optitrackClient => optiTrack.optitrackClient;
|
|
public bool IsRecording() => motionRecording.IsRecording();
|
|
public List<Transform> GetAvatarHeadTargets() => avatarHead.GetAvatarHeadTargets();
|
|
public void RefreshAvatarHeadColliders() => avatarHead.RefreshAvatarHeadColliders();
|
|
public bool IsRetargetingRemoteRunning() => retargetingRemote.IsRetargetingRemoteRunning();
|
|
public string GetRetargetingRemoteUrl() => retargetingRemote.GetRetargetingRemoteUrl();
|
|
|
|
private void Log(string message)
|
|
{
|
|
if (enableDebugLog)
|
|
{
|
|
Debug.Log($"[SystemController] {message}");
|
|
}
|
|
}
|
|
|
|
private void LogError(string message)
|
|
{
|
|
Debug.LogError($"[SystemController] {message}");
|
|
}
|
|
}
|