Refactor: 카메라 블렌딩을 UI 오버레이 방식으로 변경
- 렌더 패스 기반 블렌딩 제거 (CameraBlendRendererFeature, CameraBlend.shader) - UI RawImage 기반 CameraBlendOverlay 추가 - 스냅샷/실시간 블렌딩 모두 UI 오버레이 방식으로 통합 - 메인 카메라의 실제 위치를 저장하여 화면 튀는 문제 수정 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8179f17b0d
commit
5087ebc5af
@ -280,8 +280,7 @@ public class CameraManager : MonoBehaviour, IController
|
||||
[SerializeField] private bool useRealtimeBlend = true;
|
||||
|
||||
// 블렌드용 렌더 텍스처와 카메라
|
||||
private RenderTexture blendRenderTexture;
|
||||
private RenderTexture prevCameraRenderTexture; // 실시간 블렌딩용 이전 카메라 렌더 텍스처
|
||||
private RenderTexture prevCameraRenderTexture; // 블렌딩용 이전 카메라 렌더 텍스처
|
||||
private Camera blendCamera;
|
||||
|
||||
private CinemachineCamera currentCamera;
|
||||
@ -350,13 +349,11 @@ public class CameraManager : MonoBehaviour, IController
|
||||
}
|
||||
|
||||
// 블렌드 리소스 정리
|
||||
CameraBlendController.Reset();
|
||||
|
||||
if (blendRenderTexture != null)
|
||||
if (prevCameraRenderTexture != null)
|
||||
{
|
||||
blendRenderTexture.Release();
|
||||
Destroy(blendRenderTexture);
|
||||
blendRenderTexture = null;
|
||||
prevCameraRenderTexture.Release();
|
||||
Destroy(prevCameraRenderTexture);
|
||||
prevCameraRenderTexture = null;
|
||||
}
|
||||
|
||||
if (blendCamera != null)
|
||||
@ -785,7 +782,12 @@ public class CameraManager : MonoBehaviour, IController
|
||||
if (blendCoroutine != null)
|
||||
{
|
||||
StopCoroutine(blendCoroutine);
|
||||
CameraBlendController.EndBlend();
|
||||
// UI 오버레이 숨기기
|
||||
var overlay = CameraBlendOverlay.Instance;
|
||||
if (overlay != null)
|
||||
{
|
||||
overlay.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
blendCoroutine = StartCoroutine(BlendTransitionCoroutine(index, customBlendTime ?? blendTime));
|
||||
@ -923,38 +925,6 @@ public class CameraManager : MonoBehaviour, IController
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 블렌드용 렌더 텍스처 생성/갱신
|
||||
/// </summary>
|
||||
private void EnsureBlendRenderTexture()
|
||||
{
|
||||
int width = Screen.width;
|
||||
int height = Screen.height;
|
||||
|
||||
// 이미 적절한 크기의 텍스처가 있으면 재사용
|
||||
if (blendRenderTexture != null &&
|
||||
blendRenderTexture.width == width &&
|
||||
blendRenderTexture.height == height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 기존 텍스처 해제
|
||||
if (blendRenderTexture != null)
|
||||
{
|
||||
blendRenderTexture.Release();
|
||||
Destroy(blendRenderTexture);
|
||||
}
|
||||
|
||||
// 새 렌더 텍스처 생성 - 색상 전용 (depth 없음), 리니어 색공간에서 올바른 블렌딩
|
||||
var descriptor = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32, 0); // depth = 0
|
||||
descriptor.sRGB = true; // sRGB 텍스처로 설정하여 리니어 파이프라인과 일치
|
||||
blendRenderTexture = new RenderTexture(descriptor);
|
||||
blendRenderTexture.name = "CameraBlendRT";
|
||||
blendRenderTexture.Create();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 크로스 디졸브 블렌드 전환 코루틴
|
||||
/// </summary>
|
||||
@ -971,16 +941,15 @@ public class CameraManager : MonoBehaviour, IController
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스냅샷 블렌딩 (이전 화면 정지)
|
||||
/// 스냅샷 블렌딩 (이전 화면 정지) - UI 오버레이 방식
|
||||
/// </summary>
|
||||
private IEnumerator SnapshotBlendTransitionCoroutine(int targetIndex, float duration)
|
||||
{
|
||||
isBlending = true;
|
||||
|
||||
// 블렌드 텍스처 준비
|
||||
EnsureBlendRenderTexture();
|
||||
|
||||
if (blendRenderTexture == null)
|
||||
// 메인 카메라의 현재 위치/회전/FOV를 저장
|
||||
Camera mainCamera = Camera.main;
|
||||
if (mainCamera == null)
|
||||
{
|
||||
SetImmediate(targetIndex);
|
||||
isBlending = false;
|
||||
@ -988,80 +957,14 @@ public class CameraManager : MonoBehaviour, IController
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 렌더 패스에서 현재 프레임(포스트 프로세싱 적용 후)을 캡처하도록 요청
|
||||
CameraBlendController.RequestCapture(blendRenderTexture);
|
||||
|
||||
// 캡처가 완료될 때까지 대기 (다음 프레임 렌더링 후)
|
||||
yield return new WaitForEndOfFrame();
|
||||
yield return null; // 렌더 패스가 실행될 때까지 한 프레임 더 대기
|
||||
|
||||
// 캡처 완료 확인
|
||||
if (!CameraBlendController.CaptureReady)
|
||||
{
|
||||
CameraBlendController.EndBlend();
|
||||
SetImmediate(targetIndex);
|
||||
isBlending = false;
|
||||
blendCoroutine = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 블렌딩 시작 - BlendAmount = 1 (이전 카메라만 보임)
|
||||
CameraBlendController.StartBlendAfterCapture();
|
||||
CameraBlendController.BlendAmount = 1f;
|
||||
|
||||
// 카메라 전환 (이제 BlendAmount=1이므로 캡처된 이전 화면만 보임)
|
||||
SetImmediate(targetIndex);
|
||||
|
||||
// 블렌드 진행: 1 → 0 (이전 카메라가 서서히 사라지고 새 카메라가 드러남)
|
||||
float elapsed = 0f;
|
||||
while (elapsed < duration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
// 부드러운 이징 적용 (SmoothStep)
|
||||
t = t * t * (3f - 2f * t);
|
||||
// 1에서 0으로: 이전 카메라가 서서히 사라짐
|
||||
CameraBlendController.BlendAmount = 1f - t;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
CameraBlendController.BlendAmount = 0f;
|
||||
|
||||
// 블렌딩 종료
|
||||
CameraBlendController.EndBlend();
|
||||
|
||||
isBlending = false;
|
||||
blendCoroutine = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 실시간 블렌딩 (두 카메라 동시 렌더링)
|
||||
/// 이전 카메라 위치를 저장해두고 그 위치에서 렌더링, 서서히 사라지면서 새 카메라가 드러남
|
||||
/// </summary>
|
||||
private IEnumerator RealtimeBlendTransitionCoroutine(int targetIndex, float duration)
|
||||
{
|
||||
isBlending = true;
|
||||
|
||||
// 이전 카메라 프리셋 저장
|
||||
var prevPreset = currentPreset;
|
||||
if (prevPreset == null || prevPreset.virtualCamera == null)
|
||||
{
|
||||
SetImmediate(targetIndex);
|
||||
isBlending = false;
|
||||
blendCoroutine = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// SetImmediate 호출 전에 이전 카메라의 위치/회전/FOV를 저장
|
||||
Vector3 prevCameraPosition = prevPreset.virtualCamera.transform.position;
|
||||
Quaternion prevCameraRotation = prevPreset.virtualCamera.transform.rotation;
|
||||
float prevCameraFOV = prevPreset.virtualCamera.Lens.FieldOfView;
|
||||
Vector3 prevCameraPosition = mainCamera.transform.position;
|
||||
Quaternion prevCameraRotation = mainCamera.transform.rotation;
|
||||
float prevCameraFOV = mainCamera.fieldOfView;
|
||||
|
||||
// 블렌드 텍스처 준비
|
||||
EnsureBlendRenderTexture();
|
||||
EnsurePrevCameraRenderTexture();
|
||||
|
||||
if (blendRenderTexture == null || prevCameraRenderTexture == null)
|
||||
if (prevCameraRenderTexture == null)
|
||||
{
|
||||
SetImmediate(targetIndex);
|
||||
isBlending = false;
|
||||
@ -1079,34 +982,122 @@ public class CameraManager : MonoBehaviour, IController
|
||||
yield break;
|
||||
}
|
||||
|
||||
// UI 오버레이 준비
|
||||
var overlay = CameraBlendOverlay.GetOrCreate();
|
||||
|
||||
// 블렌드 카메라를 현재 메인 카메라 위치로 설정하고 렌더링 (스냅샷 캡처)
|
||||
blendCamera.transform.SetPositionAndRotation(prevCameraPosition, prevCameraRotation);
|
||||
blendCamera.fieldOfView = prevCameraFOV;
|
||||
blendCamera.targetTexture = prevCameraRenderTexture;
|
||||
blendCamera.Render();
|
||||
|
||||
// UI 오버레이 표시 (알파 1 = 완전 불투명)
|
||||
overlay.Show(prevCameraRenderTexture);
|
||||
overlay.SetAlpha(1f);
|
||||
|
||||
// 새 카메라로 전환
|
||||
SetImmediate(targetIndex);
|
||||
|
||||
// 블렌드 진행: 알파 1 → 0 (스냅샷이 서서히 사라지고 새 카메라가 드러남)
|
||||
float elapsed = 0f;
|
||||
while (elapsed < duration)
|
||||
{
|
||||
// 다음 프레임까지 대기
|
||||
yield return null;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
// 부드러운 이징 적용 (SmoothStep)
|
||||
t = t * t * (3f - 2f * t);
|
||||
// 알파를 1에서 0으로: 스냅샷이 서서히 사라짐
|
||||
overlay.SetAlpha(1f - t);
|
||||
}
|
||||
|
||||
// 블렌딩 종료
|
||||
overlay.Hide();
|
||||
|
||||
isBlending = false;
|
||||
blendCoroutine = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 실시간 블렌딩 (UI 오버레이 방식)
|
||||
/// 이전 카메라 위치를 저장해두고 그 위치에서 렌더링, UI로 표시하고 서서히 사라지게 함
|
||||
/// </summary>
|
||||
private IEnumerator RealtimeBlendTransitionCoroutine(int targetIndex, float duration)
|
||||
{
|
||||
isBlending = true;
|
||||
|
||||
// 이전 카메라 프리셋 저장
|
||||
var prevPreset = currentPreset;
|
||||
if (prevPreset == null || prevPreset.virtualCamera == null)
|
||||
{
|
||||
SetImmediate(targetIndex);
|
||||
isBlending = false;
|
||||
blendCoroutine = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 메인 카메라의 현재 위치/회전/FOV를 저장 (Cinemachine이 적용한 실제 위치)
|
||||
Camera mainCamera = Camera.main;
|
||||
if (mainCamera == null)
|
||||
{
|
||||
SetImmediate(targetIndex);
|
||||
isBlending = false;
|
||||
blendCoroutine = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
Vector3 prevCameraPosition = mainCamera.transform.position;
|
||||
Quaternion prevCameraRotation = mainCamera.transform.rotation;
|
||||
float prevCameraFOV = mainCamera.fieldOfView;
|
||||
|
||||
// 블렌드 텍스처 준비
|
||||
EnsurePrevCameraRenderTexture();
|
||||
|
||||
if (prevCameraRenderTexture == null)
|
||||
{
|
||||
SetImmediate(targetIndex);
|
||||
isBlending = false;
|
||||
blendCoroutine = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// 블렌드 카메라 준비
|
||||
EnsureBlendCamera();
|
||||
if (blendCamera == null)
|
||||
{
|
||||
SetImmediate(targetIndex);
|
||||
isBlending = false;
|
||||
blendCoroutine = null;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// UI 오버레이 준비
|
||||
var overlay = CameraBlendOverlay.GetOrCreate();
|
||||
|
||||
// 블렌드 카메라를 이전 카메라 위치로 설정하고 첫 프레임 렌더링
|
||||
blendCamera.transform.SetPositionAndRotation(prevCameraPosition, prevCameraRotation);
|
||||
blendCamera.fieldOfView = prevCameraFOV;
|
||||
blendCamera.targetTexture = prevCameraRenderTexture;
|
||||
|
||||
// Camera.Render()로 렌더링 (URP에서도 포스트 프로세싱 적용됨)
|
||||
blendCamera.Render();
|
||||
Graphics.Blit(prevCameraRenderTexture, blendRenderTexture);
|
||||
|
||||
// 실시간 블렌딩 시작 (BlendAmount = 1에서 시작, 이전 카메라가 100% 보임)
|
||||
CameraBlendController.StartRealtimeBlend(blendRenderTexture);
|
||||
CameraBlendController.BlendAmount = 1f;
|
||||
// UI 오버레이 표시 (알파 1 = 완전 불투명)
|
||||
overlay.Show(prevCameraRenderTexture);
|
||||
overlay.SetAlpha(1f);
|
||||
|
||||
// 새 카메라로 전환 (메인 카메라는 이제 새 위치에서 렌더링됨)
|
||||
SetImmediate(targetIndex);
|
||||
|
||||
// 블렌드 진행: 1 → 0 (이전 카메라 화면이 서서히 사라지고 새 카메라가 드러남)
|
||||
// 블렌드 진행: 알파 1 → 0 (이전 카메라 화면이 서서히 사라짐)
|
||||
float elapsed = 0f;
|
||||
while (elapsed < duration)
|
||||
{
|
||||
// 이전 카메라 시점에서 먼저 렌더링 (메인 카메라 렌더링 전에)
|
||||
// 이전 카메라 시점에서 렌더링
|
||||
blendCamera.transform.SetPositionAndRotation(prevCameraPosition, prevCameraRotation);
|
||||
blendCamera.fieldOfView = prevCameraFOV;
|
||||
blendCamera.targetTexture = prevCameraRenderTexture;
|
||||
|
||||
// Camera.Render()로 렌더링 (URP에서도 포스트 프로세싱 적용됨)
|
||||
blendCamera.Render();
|
||||
Graphics.Blit(prevCameraRenderTexture, blendRenderTexture);
|
||||
|
||||
// 다음 프레임까지 대기
|
||||
yield return null;
|
||||
@ -1115,14 +1106,12 @@ public class CameraManager : MonoBehaviour, IController
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
// 부드러운 이징 적용 (SmoothStep)
|
||||
t = t * t * (3f - 2f * t);
|
||||
// 1에서 0으로: 이전 카메라가 서서히 사라짐
|
||||
CameraBlendController.BlendAmount = 1f - t;
|
||||
// 알파를 1에서 0으로: 이전 카메라가 서서히 사라짐
|
||||
overlay.SetAlpha(1f - t);
|
||||
}
|
||||
|
||||
CameraBlendController.BlendAmount = 0f;
|
||||
|
||||
// 블렌딩 종료
|
||||
CameraBlendController.EndBlend();
|
||||
overlay.Hide();
|
||||
|
||||
isBlending = false;
|
||||
blendCoroutine = null;
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: CameraBlend
|
||||
m_Shader: {fileID: 4800000, guid: 76944497fc2bcdd479fc435a5d4d8447, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _PrevTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _BlendAmount: 0.000075280666
|
||||
m_Colors: []
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f563d7f0c3fc17647ad56388fb936427
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,57 +0,0 @@
|
||||
Shader "Streamingle/CameraBlend"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_PrevTex ("Previous Camera", 2D) = "white" {}
|
||||
_BlendAmount ("Blend Amount", Range(0, 1)) = 0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"RenderType" = "Opaque"
|
||||
"RenderPipeline" = "UniversalPipeline"
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "CameraBlend"
|
||||
ZTest Always
|
||||
ZWrite Off
|
||||
Cull Off
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma vertex Vert
|
||||
#pragma fragment Frag
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
|
||||
|
||||
TEXTURE2D(_PrevTex);
|
||||
SAMPLER(sampler_PrevTex);
|
||||
|
||||
float _BlendAmount;
|
||||
|
||||
float4 Frag(Varyings input) : SV_Target
|
||||
{
|
||||
float2 uv = input.texcoord;
|
||||
|
||||
// 현재 카메라 (Blitter에서 전달됨) - 동일한 샘플러 사용
|
||||
float4 currentColor = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv);
|
||||
|
||||
// 이전 카메라 (캡처된 텍스처) - 동일한 Linear 샘플러 사용
|
||||
float4 prevColor = SAMPLE_TEXTURE2D(_PrevTex, sampler_LinearClamp, uv);
|
||||
|
||||
// 리니어 공간에서 블렌딩 (BlendAmount: 1 = 이전, 0 = 현재)
|
||||
// 이전 카메라(B)가 위에 덮이고 서서히 사라짐
|
||||
float4 finalColor = lerp(currentColor, prevColor, _BlendAmount);
|
||||
|
||||
return finalColor;
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
|
||||
FallBack Off
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76944497fc2bcdd479fc435a5d4d8447
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,290 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.Rendering.RenderGraphModule;
|
||||
|
||||
/// <summary>
|
||||
/// 카메라 전환 시 크로스 디졸브 블렌딩을 위한 URP Renderer Feature
|
||||
/// Unity 6 Render Graph API 사용
|
||||
/// </summary>
|
||||
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<UniversalResourceData>();
|
||||
var cameraData = frameData.Get<UniversalCameraData>();
|
||||
|
||||
// 백버퍼인 경우 스킵
|
||||
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<CapturePassData>("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<PassData>("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<PassData>("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()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 카메라 블렌딩을 제어하는 정적 컨트롤러
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 다음 프레임에서 현재 화면을 캡처하도록 요청합니다. (스냅샷 모드)
|
||||
/// </summary>
|
||||
public static void RequestCapture(RenderTexture targetTexture)
|
||||
{
|
||||
blendTexture = targetTexture;
|
||||
captureRequested = true;
|
||||
captureReady = false;
|
||||
isRealtimeMode = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 캡처가 완료된 후 블렌딩을 시작합니다. (스냅샷 모드)
|
||||
/// BlendAmount = 1에서 시작 (이전 카메라 100% 보임)
|
||||
/// </summary>
|
||||
public static void StartBlendAfterCapture()
|
||||
{
|
||||
if (captureReady && blendTexture != null)
|
||||
{
|
||||
blendAmount = 1f;
|
||||
isBlending = true;
|
||||
captureRequested = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 실시간 블렌딩을 시작합니다. (매 프레임 이전 카메라 렌더링)
|
||||
/// BlendAmount = 1에서 시작 (이전 카메라 100% 보임)
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14b4e395bb7cf744d87b2c1885014709
|
||||
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 634dec0a07bb460468b7e86f02bbc5b9
|
||||
guid: 055b59bee33be324c88ea76f3ea4786b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@ -0,0 +1,126 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Streamingle
|
||||
{
|
||||
/// <summary>
|
||||
/// 카메라 블렌딩용 UI 오버레이
|
||||
/// RawImage 하나로 이전 카메라 화면을 표시하고 서서히 사라지게 함
|
||||
/// </summary>
|
||||
public class CameraBlendOverlay : MonoBehaviour
|
||||
{
|
||||
private static CameraBlendOverlay instance;
|
||||
public static CameraBlendOverlay Instance => instance;
|
||||
|
||||
private Canvas canvas;
|
||||
private RawImage rawImage;
|
||||
private CanvasGroup canvasGroup;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (instance != null && instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
SetupUI();
|
||||
}
|
||||
|
||||
private void SetupUI()
|
||||
{
|
||||
// Canvas 설정
|
||||
canvas = gameObject.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 9999; // 최상단에 표시
|
||||
|
||||
// CanvasScaler 추가 (화면 크기 맞춤)
|
||||
var scaler = gameObject.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920, 1080);
|
||||
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
||||
scaler.matchWidthOrHeight = 0.5f;
|
||||
|
||||
// GraphicRaycaster는 추가하지 않음 (클릭 방해 안함)
|
||||
|
||||
// RawImage 생성
|
||||
GameObject imageObj = new GameObject("BlendImage");
|
||||
imageObj.transform.SetParent(transform, false);
|
||||
|
||||
rawImage = imageObj.AddComponent<RawImage>();
|
||||
rawImage.raycastTarget = false; // 레이캐스트 비활성화
|
||||
|
||||
// 화면 전체를 덮도록 설정
|
||||
RectTransform rectTransform = rawImage.rectTransform;
|
||||
rectTransform.anchorMin = Vector2.zero;
|
||||
rectTransform.anchorMax = Vector2.one;
|
||||
rectTransform.offsetMin = Vector2.zero;
|
||||
rectTransform.offsetMax = Vector2.zero;
|
||||
|
||||
// CanvasGroup 추가 (알파 조절용)
|
||||
canvasGroup = imageObj.AddComponent<CanvasGroup>();
|
||||
canvasGroup.blocksRaycasts = false;
|
||||
canvasGroup.interactable = false;
|
||||
|
||||
// 초기에는 숨김
|
||||
Hide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 오버레이 표시 및 텍스처 설정
|
||||
/// </summary>
|
||||
public void Show(RenderTexture texture)
|
||||
{
|
||||
if (rawImage == null) return;
|
||||
|
||||
rawImage.texture = texture;
|
||||
rawImage.enabled = true;
|
||||
canvasGroup.alpha = 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 알파값 설정 (0 = 투명, 1 = 불투명)
|
||||
/// </summary>
|
||||
public void SetAlpha(float alpha)
|
||||
{
|
||||
if (canvasGroup == null) return;
|
||||
canvasGroup.alpha = alpha;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 오버레이 숨김
|
||||
/// </summary>
|
||||
public void Hide()
|
||||
{
|
||||
if (rawImage == null) return;
|
||||
|
||||
rawImage.enabled = false;
|
||||
rawImage.texture = null;
|
||||
if (canvasGroup != null)
|
||||
{
|
||||
canvasGroup.alpha = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 싱글톤 인스턴스 생성 (없으면 자동 생성)
|
||||
/// </summary>
|
||||
public static CameraBlendOverlay GetOrCreate()
|
||||
{
|
||||
if (instance != null) return instance;
|
||||
|
||||
GameObject obj = new GameObject("CameraBlendOverlay");
|
||||
return obj.AddComponent<CameraBlendOverlay>();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (instance == this)
|
||||
{
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c072c526296e95d4bb6a11943cb2018b
|
||||
Loading…
x
Reference in New Issue
Block a user