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();
}
}