WinCNT

UniTask를 이용한 스크립트로 포스트 프로세싱이 아닌 Fade In, Fade Out를 구현해보기 본문

Unity/URP or Shader 관련

UniTask를 이용한 스크립트로 포스트 프로세싱이 아닌 Fade In, Fade Out를 구현해보기

WinCNT_SSS 2023. 12. 15. 11:45

서론

이번에는 UniTask를 이용한 스크립트로 Fade In, Fade Out를 구현해보려고 한다

물론 딱히 UniTask가 아니라 Coroutine도 사용할 수 있지만 모처럼 UniTask를 인스톨했으니 겸사겸사 사용해봤다


Fade In, Fade Out의 구현에 대해

Fade In, Fade Out은 포스트 프로세싱을 이용한 구현이 일반적일 것이다

하지만 그 방법만 있는 것이 아니다!!

다른 방법이 있는데 바로 카메라의 거의 바로 앞에 Quad를 두고, 그 Qaud만 Fade In, Fade Out시키는 방법(…)이다

원리는 매우 간단하다

 

이 방법은 구현이 간단하고 좀 더 직관적으로 Fade In, Fade Out를 사용할 수 있다는 메리트가 있다

하지만 화면의 비율이나 카메라의 FOV 등에 따라서는 커버가 안 되는 부분이 생길 수 있다는 디메리트가 있다

 

성능적인 관점에서는 글쎄?

처음에는 Quad를 이용한 것이 더 좋을 것 같았지만 잘 생각해보니 그다지 차이가 안 날 거 같기도 하고…

이 부분은 기기에 따라 차이나 나기도 할 테니 따로 검증하진 않으려고 한다


우선은 셰이더의 샘플 코드부터

다음은 이번에 사용한 셰이더의 샘플 코드이다

 

App/ScreenFade

Shader "App/ScreenFade"
{
    Properties
    {
        _Color("Color", Color) = (0,0,0,0)
    }
    
    SubShader  
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
        }

        Blend SrcAlpha OneMinusSrcAlpha
        ZTest Always
        
        Pass  
        {
            HLSLPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            CBUFFER_START(UnityPerMaterial)
            float4 _Color;
            CBUFFER_END
            
            struct appdata_img
            {
                float4 vertex : POSITION;
                half2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            
            struct v2f_img
            {
                float4 pos : SV_POSITION;
                half2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
                UNITY_VERTEX_OUTPUT_STEREO
            };
            
            v2f_img vert_img( appdata_img v )
            {
                v2f_img o = (v2f_img)0;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.pos = TransformObjectToHClip(v.vertex.xyz);
                o.uv = v.texcoord;
                return o;
            }
            
            half4 frag (v2f_img i) : SV_Target
            {
                return _Color;
            }
            ENDHLSL
        }
    } 
}

 

단순히 프로퍼티의 색을 출력하는 반투명 셰이더이다

이번에는 프로퍼티의 설정을 스크립트에서 할 예정이라 이 정도면 충분하다

버텍스 셰이더는 UnityCG.cginc의 vert_img를 가져왔다


Fade In, Fade Out를 실행하는 스크립트

 

ScreenFadeView.cs

using System.Collections;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace App.Common.Views
{
    public class ScreenFadeView : MonoBehaviour
    {
        [SerializeField] private float _fadeTime = 1.0f;
        [SerializeField] private Color _fadeColor = new Color(0.01f, 0.01f, 0.01f, 1.0f);
        [SerializeField] private int _renderQueue = 3500;

        private MeshRenderer _fadeRenderer;
        private MeshFilter _fadeMesh;
        private Material _fadeMaterial = null;
        private bool _isFading = false;
        private float _currentAlpha;

        void Start()
        {
            // Fade용 머티리얼 작성(셰이더는 별도 기재)
            _fadeMaterial = new Material(Shader.Find("App/ScreenFade"));
            _fadeMesh = gameObject.AddComponent<MeshFilter>();
            _fadeRenderer = gameObject.AddComponent<MeshRenderer>();
            _fadeRenderer.material = _fadeMaterial;

            // 카메라를 덮는 Fade용 Mesh를 작성
            var mesh = new Mesh();
            _fadeMesh.mesh = mesh;
            Vector3[] vertices = new Vector3[4];
            float width = 2f;
            float height = 2f;
            float depth = 1f;
            // 버텍스
            vertices[0] = new Vector3(-width, -height, depth);
            vertices[1] = new Vector3(width, -height, depth);
            vertices[2] = new Vector3(-width, height, depth);
            vertices[3] = new Vector3(width, height, depth);
            mesh.vertices = vertices;
            // 인덱스
            int[] indices = new int[6];
            indices[0] = 0; indices[1] = 2; indices[2] = 1;
            indices[3] = 2; indices[4] = 3; indices[5] = 1;
            mesh.triangles = indices;
            // 노멀
            Vector3[] normals = new Vector3[4];
            normals[0] = -Vector3.forward;
            normals[1] = -Vector3.forward;
            normals[2] = -Vector3.forward;
            normals[3] = -Vector3.forward;
            mesh.normals = normals;
            // UV
            Vector2[] uv = new Vector2[4];
            uv[0] = new Vector2(0, 0);
            uv[1] = new Vector2(1, 0);
            uv[2] = new Vector2(0, 1);
            uv[3] = new Vector2(1, 1);
            mesh.uv = uv;

            // Fade 알파
            _currentAlpha = 0.0f;
        }

        private async UniTask FadeInAsync()
        {
            await Fade(1f, 0f);
        }

        public void FadeIn()
        {
            FadeInAsync().Forget();
        }

        private async UniTask FadeOutAsync()
        {
            await Fade(0f, 1f);
        }

        public void FadeOut()
        {
            FadeOutAsync().Forget();
        }

        /// <summary>
        /// 머티리얼의 알파를 조정해서 Fade시키는 함수
        /// 1.0 ~ 0.0으로 설정하면 Fade out
        /// 0.0 ~ 1.0으로 설정하면 Fade in
        /// </summary>
        private async UniTask Fade(float startAlpha, float endAlpha)
        {
            var elapsedTime = 0.0f;
            while (elapsedTime < _fadeTime)
            {
                elapsedTime += Time.deltaTime;
                _currentAlpha = Mathf.Lerp(startAlpha, endAlpha, Mathf.Clamp01(elapsedTime / _fadeTime));
                SetMaterial();
                await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
            }
            _currentAlpha = endAlpha;
            SetMaterial();
        }

        /// <summary>
        /// 머티리얼의 알파 값을 조정해서 Fade를 실시
        /// </summary>
        private void SetMaterial()
        {
            Color color = _fadeColor;
            color.a = _currentAlpha;
            _isFading = color.a > 0;
            if (_fadeMaterial != null)
            {
                _fadeMaterial.color = color;
                _fadeMaterial.renderQueue = _renderQueue;
                _fadeRenderer.material = _fadeMaterial;
                _fadeRenderer.enabled = _isFading;
            }
        }
    }
}

 

이번의 주연인 스크립트

요는 쿼드를 만들고, UniTask를 이용해 쿼드의 알파 값을 컨드롤하는 스크립트다


결과

카메라나 카메라의 자식 오브젝트에 위의 스크립트를 붙여주면 준비 OK!

그리고 적당한 실행용 스크립트로 필요할 때 ScreenFadeView의 메소드를 호출하면 된다

아래는 간단한 샘플 코드

 

ScreenFadeController.cs

using App.Common.Views;
using UnityEngine;

public class ScreenFadeController : MonoBehaviour
{
    public ScreenFadeView screenFadeView;
    
    // Start is called before the first frame update
    void Start()
    {
        screenFadeView.FadeIn();
    }
}

 

이건 샘플 코드의 실행 예제이다


마무리

URP 14 이전에는 포스트 프로세싱 구현이 귀찮복잡했기 때문에 이 방법으로 구현했던 것 같다

현재 프로젝트는 URP 14이고 이 버전부터는 포스트 프로세싱 구현이 간단해졌다고 하니 슬슬 써도 좋을지도 모르겠다