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;
|
[SerializeField] private bool useRealtimeBlend = true;
|
||||||
|
|
||||||
// 블렌드용 렌더 텍스처와 카메라
|
// 블렌드용 렌더 텍스처와 카메라
|
||||||
private RenderTexture blendRenderTexture;
|
private RenderTexture prevCameraRenderTexture; // 블렌딩용 이전 카메라 렌더 텍스처
|
||||||
private RenderTexture prevCameraRenderTexture; // 실시간 블렌딩용 이전 카메라 렌더 텍스처
|
|
||||||
private Camera blendCamera;
|
private Camera blendCamera;
|
||||||
|
|
||||||
private CinemachineCamera currentCamera;
|
private CinemachineCamera currentCamera;
|
||||||
@ -350,13 +349,11 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 블렌드 리소스 정리
|
// 블렌드 리소스 정리
|
||||||
CameraBlendController.Reset();
|
if (prevCameraRenderTexture != null)
|
||||||
|
|
||||||
if (blendRenderTexture != null)
|
|
||||||
{
|
{
|
||||||
blendRenderTexture.Release();
|
prevCameraRenderTexture.Release();
|
||||||
Destroy(blendRenderTexture);
|
Destroy(prevCameraRenderTexture);
|
||||||
blendRenderTexture = null;
|
prevCameraRenderTexture = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blendCamera != null)
|
if (blendCamera != null)
|
||||||
@ -785,7 +782,12 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
if (blendCoroutine != null)
|
if (blendCoroutine != null)
|
||||||
{
|
{
|
||||||
StopCoroutine(blendCoroutine);
|
StopCoroutine(blendCoroutine);
|
||||||
CameraBlendController.EndBlend();
|
// UI 오버레이 숨기기
|
||||||
|
var overlay = CameraBlendOverlay.Instance;
|
||||||
|
if (overlay != null)
|
||||||
|
{
|
||||||
|
overlay.Hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blendCoroutine = StartCoroutine(BlendTransitionCoroutine(index, customBlendTime ?? blendTime));
|
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>
|
||||||
/// 크로스 디졸브 블렌드 전환 코루틴
|
/// 크로스 디졸브 블렌드 전환 코루틴
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -971,16 +941,15 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 스냅샷 블렌딩 (이전 화면 정지)
|
/// 스냅샷 블렌딩 (이전 화면 정지) - UI 오버레이 방식
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IEnumerator SnapshotBlendTransitionCoroutine(int targetIndex, float duration)
|
private IEnumerator SnapshotBlendTransitionCoroutine(int targetIndex, float duration)
|
||||||
{
|
{
|
||||||
isBlending = true;
|
isBlending = true;
|
||||||
|
|
||||||
// 블렌드 텍스처 준비
|
// 메인 카메라의 현재 위치/회전/FOV를 저장
|
||||||
EnsureBlendRenderTexture();
|
Camera mainCamera = Camera.main;
|
||||||
|
if (mainCamera == null)
|
||||||
if (blendRenderTexture == null)
|
|
||||||
{
|
{
|
||||||
SetImmediate(targetIndex);
|
SetImmediate(targetIndex);
|
||||||
isBlending = false;
|
isBlending = false;
|
||||||
@ -988,80 +957,14 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 렌더 패스에서 현재 프레임(포스트 프로세싱 적용 후)을 캡처하도록 요청
|
Vector3 prevCameraPosition = mainCamera.transform.position;
|
||||||
CameraBlendController.RequestCapture(blendRenderTexture);
|
Quaternion prevCameraRotation = mainCamera.transform.rotation;
|
||||||
|
float prevCameraFOV = mainCamera.fieldOfView;
|
||||||
// 캡처가 완료될 때까지 대기 (다음 프레임 렌더링 후)
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 블렌드 텍스처 준비
|
// 블렌드 텍스처 준비
|
||||||
EnsureBlendRenderTexture();
|
|
||||||
EnsurePrevCameraRenderTexture();
|
EnsurePrevCameraRenderTexture();
|
||||||
|
|
||||||
if (blendRenderTexture == null || prevCameraRenderTexture == null)
|
if (prevCameraRenderTexture == null)
|
||||||
{
|
{
|
||||||
SetImmediate(targetIndex);
|
SetImmediate(targetIndex);
|
||||||
isBlending = false;
|
isBlending = false;
|
||||||
@ -1079,34 +982,122 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
yield break;
|
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.transform.SetPositionAndRotation(prevCameraPosition, prevCameraRotation);
|
||||||
blendCamera.fieldOfView = prevCameraFOV;
|
blendCamera.fieldOfView = prevCameraFOV;
|
||||||
blendCamera.targetTexture = prevCameraRenderTexture;
|
blendCamera.targetTexture = prevCameraRenderTexture;
|
||||||
|
|
||||||
// Camera.Render()로 렌더링 (URP에서도 포스트 프로세싱 적용됨)
|
|
||||||
blendCamera.Render();
|
blendCamera.Render();
|
||||||
Graphics.Blit(prevCameraRenderTexture, blendRenderTexture);
|
|
||||||
|
|
||||||
// 실시간 블렌딩 시작 (BlendAmount = 1에서 시작, 이전 카메라가 100% 보임)
|
// UI 오버레이 표시 (알파 1 = 완전 불투명)
|
||||||
CameraBlendController.StartRealtimeBlend(blendRenderTexture);
|
overlay.Show(prevCameraRenderTexture);
|
||||||
CameraBlendController.BlendAmount = 1f;
|
overlay.SetAlpha(1f);
|
||||||
|
|
||||||
// 새 카메라로 전환 (메인 카메라는 이제 새 위치에서 렌더링됨)
|
// 새 카메라로 전환 (메인 카메라는 이제 새 위치에서 렌더링됨)
|
||||||
SetImmediate(targetIndex);
|
SetImmediate(targetIndex);
|
||||||
|
|
||||||
// 블렌드 진행: 1 → 0 (이전 카메라 화면이 서서히 사라지고 새 카메라가 드러남)
|
// 블렌드 진행: 알파 1 → 0 (이전 카메라 화면이 서서히 사라짐)
|
||||||
float elapsed = 0f;
|
float elapsed = 0f;
|
||||||
while (elapsed < duration)
|
while (elapsed < duration)
|
||||||
{
|
{
|
||||||
// 이전 카메라 시점에서 먼저 렌더링 (메인 카메라 렌더링 전에)
|
// 이전 카메라 시점에서 렌더링
|
||||||
blendCamera.transform.SetPositionAndRotation(prevCameraPosition, prevCameraRotation);
|
blendCamera.transform.SetPositionAndRotation(prevCameraPosition, prevCameraRotation);
|
||||||
blendCamera.fieldOfView = prevCameraFOV;
|
blendCamera.fieldOfView = prevCameraFOV;
|
||||||
blendCamera.targetTexture = prevCameraRenderTexture;
|
blendCamera.targetTexture = prevCameraRenderTexture;
|
||||||
|
|
||||||
// Camera.Render()로 렌더링 (URP에서도 포스트 프로세싱 적용됨)
|
|
||||||
blendCamera.Render();
|
blendCamera.Render();
|
||||||
Graphics.Blit(prevCameraRenderTexture, blendRenderTexture);
|
|
||||||
|
|
||||||
// 다음 프레임까지 대기
|
// 다음 프레임까지 대기
|
||||||
yield return null;
|
yield return null;
|
||||||
@ -1115,14 +1106,12 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
float t = Mathf.Clamp01(elapsed / duration);
|
float t = Mathf.Clamp01(elapsed / duration);
|
||||||
// 부드러운 이징 적용 (SmoothStep)
|
// 부드러운 이징 적용 (SmoothStep)
|
||||||
t = t * t * (3f - 2f * t);
|
t = t * t * (3f - 2f * t);
|
||||||
// 1에서 0으로: 이전 카메라가 서서히 사라짐
|
// 알파를 1에서 0으로: 이전 카메라가 서서히 사라짐
|
||||||
CameraBlendController.BlendAmount = 1f - t;
|
overlay.SetAlpha(1f - t);
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraBlendController.BlendAmount = 0f;
|
|
||||||
|
|
||||||
// 블렌딩 종료
|
// 블렌딩 종료
|
||||||
CameraBlendController.EndBlend();
|
overlay.Hide();
|
||||||
|
|
||||||
isBlending = false;
|
isBlending = false;
|
||||||
blendCoroutine = null;
|
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
|
fileFormatVersion: 2
|
||||||
guid: 634dec0a07bb460468b7e86f02bbc5b9
|
guid: 055b59bee33be324c88ea76f3ea4786b
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
externalObjects: {}
|
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