using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using UnityEngine.Rendering.RenderGraphModule; /// /// 카메라 전환 시 크로스 디졸브 블렌딩을 위한 URP Renderer Feature /// Unity 6 Render Graph API 사용 /// public class CameraBlendRendererFeature : ScriptableRendererFeature { [System.Serializable] public class Settings { // AfterRenderingPostProcessing - 모든 포스트 프로세싱 적용 후 블렌딩 public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; public Material blendMaterial; } public Settings settings = new Settings(); private CameraBlendRenderPass blendPass; public override void Create() { blendPass = new CameraBlendRenderPass(settings); blendPass.renderPassEvent = settings.renderPassEvent; } public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (settings.blendMaterial == null) return; // 게임 카메라만 처리 if (renderingData.cameraData.cameraType != CameraType.Game) return; // 캡처 요청이 있거나 블렌딩 중일 때 패스 추가 if (CameraBlendController.CaptureRequested || (CameraBlendController.IsBlending && CameraBlendController.BlendTexture != null)) { renderer.EnqueuePass(blendPass); } } protected override void Dispose(bool disposing) { blendPass?.Dispose(); } } public class CameraBlendRenderPass : ScriptableRenderPass { private CameraBlendRendererFeature.Settings settings; private static readonly int BlendAmountProperty = Shader.PropertyToID("_BlendAmount"); private static readonly int PrevTexProperty = Shader.PropertyToID("_PrevTex"); private class PassData { public Material blendMaterial; public float blendAmount; public RenderTexture blendTexture; public TextureHandle sourceTexture; public TextureHandle destinationTexture; } private class CapturePassData { public TextureHandle sourceTexture; public RenderTexture targetTexture; } public CameraBlendRenderPass(CameraBlendRendererFeature.Settings settings) { this.settings = settings; profilingSampler = new ProfilingSampler("Camera Blend Pass"); ConfigureInput(ScriptableRenderPassInput.Color); } public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { if (settings.blendMaterial == null) return; var resourceData = frameData.Get(); var cameraData = frameData.Get(); // 백버퍼인 경우 스킵 if (resourceData.isActiveTargetBackBuffer) { return; } var source = resourceData.activeColorTexture; // source가 유효한지 확인 if (!source.IsValid()) { return; } // 캡처 요청 처리 - 현재 프레임(포스트 프로세싱 적용 후)을 캡처 if (CameraBlendController.CaptureRequested && CameraBlendController.BlendTexture != null) { // UnsafePass를 사용하여 RenderTexture에 직접 복사 using (var builder = renderGraph.AddUnsafePass("Camera Blend Capture", out var captureData, profilingSampler)) { captureData.sourceTexture = source; captureData.targetTexture = CameraBlendController.BlendTexture; builder.UseTexture(source, AccessFlags.Read); builder.AllowPassCulling(false); builder.SetRenderFunc((CapturePassData data, UnsafeGraphContext context) => { // NativeCommandBuffer를 통해 Blit 수행 var nativeCmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd); nativeCmd.Blit(data.sourceTexture, data.targetTexture); CameraBlendController.CaptureReady = true; }); } return; // 캡처만 하고 블렌딩은 다음 프레임부터 } // 블렌딩이 활성화되지 않았으면 스킵 if (!CameraBlendController.IsBlending || CameraBlendController.BlendTexture == null) return; // cameraColorDesc 사용 - 카메라 색상 텍스처 설명자 var cameraTargetDesc = renderGraph.GetTextureDesc(resourceData.cameraColor); cameraTargetDesc.name = "_CameraBlendDestination"; cameraTargetDesc.clearBuffer = false; var destination = renderGraph.CreateTexture(cameraTargetDesc); // 블렌딩 패스 using (var builder = renderGraph.AddRasterRenderPass("Camera Blend", out var passData, profilingSampler)) { passData.blendMaterial = settings.blendMaterial; passData.blendAmount = CameraBlendController.BlendAmount; passData.blendTexture = CameraBlendController.BlendTexture; passData.sourceTexture = source; builder.UseTexture(source, AccessFlags.Read); builder.SetRenderAttachment(destination, 0, AccessFlags.Write); builder.AllowPassCulling(false); builder.SetRenderFunc((PassData data, RasterGraphContext context) => { data.blendMaterial.SetFloat(BlendAmountProperty, data.blendAmount); data.blendMaterial.SetTexture(PrevTexProperty, data.blendTexture); Blitter.BlitTexture(context.cmd, data.sourceTexture, new Vector4(1, 1, 0, 0), data.blendMaterial, 0); }); } // 결과를 source로 복사 using (var builder = renderGraph.AddRasterRenderPass("Camera Blend Copy Back", out var copyData, profilingSampler)) { copyData.sourceTexture = destination; builder.UseTexture(destination, AccessFlags.Read); builder.SetRenderAttachment(source, 0, AccessFlags.Write); builder.AllowPassCulling(false); builder.SetRenderFunc((PassData data, RasterGraphContext context) => { Blitter.BlitTexture(context.cmd, data.sourceTexture, new Vector4(1, 1, 0, 0), 0, false); }); } } [System.Obsolete("This rendering path is for compatibility mode only (when Render Graph is disabled). Use Render Graph API instead.", false)] public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) { // Legacy path } public void Dispose() { } } /// /// 카메라 블렌딩을 제어하는 정적 컨트롤러 /// public static class CameraBlendController { private static bool isBlending = false; private static float blendAmount = 1f; private static RenderTexture blendTexture; private static bool captureRequested = false; private static bool captureReady = false; private static bool isRealtimeMode = false; public static bool IsBlending { get => isBlending; set => isBlending = value; } public static float BlendAmount { get => blendAmount; set => blendAmount = Mathf.Clamp01(value); } public static RenderTexture BlendTexture { get => blendTexture; set => blendTexture = value; } public static bool CaptureRequested { get => captureRequested; set => captureRequested = value; } public static bool CaptureReady { get => captureReady; set => captureReady = value; } public static bool IsRealtimeMode { get => isRealtimeMode; set => isRealtimeMode = value; } /// /// 다음 프레임에서 현재 화면을 캡처하도록 요청합니다. (스냅샷 모드) /// public static void RequestCapture(RenderTexture targetTexture) { blendTexture = targetTexture; captureRequested = true; captureReady = false; isRealtimeMode = false; } /// /// 캡처가 완료된 후 블렌딩을 시작합니다. (스냅샷 모드) /// BlendAmount = 1에서 시작 (이전 카메라 100% 보임) /// public static void StartBlendAfterCapture() { if (captureReady && blendTexture != null) { blendAmount = 1f; isBlending = true; captureRequested = false; } } /// /// 실시간 블렌딩을 시작합니다. (매 프레임 이전 카메라 렌더링) /// BlendAmount = 1에서 시작 (이전 카메라 100% 보임) /// public static void StartRealtimeBlend(RenderTexture texture) { blendTexture = texture; blendAmount = 1f; isBlending = true; isRealtimeMode = true; captureRequested = false; captureReady = false; } public static void StartBlend(RenderTexture texture) { blendTexture = texture; blendAmount = 0f; isBlending = true; } public static void EndBlend() { isBlending = false; blendAmount = 1f; blendTexture = null; captureRequested = false; captureReady = false; isRealtimeMode = false; } public static void Reset() { EndBlend(); } }