WinCNT

Fill Amount 기능이 있는 간단 Bar Gauge UI 셰이더 만들어보기! 본문

Unity/URP or Shader 관련

Fill Amount 기능이 있는 간단 Bar Gauge UI 셰이더 만들어보기!

WinCNT_SSS 2024. 4. 11. 10:27

서론

매우 슬프게도 VR에서는 Canvas나 관련 컴포넌트(Image 등)의 성능이 좀 많이 떨어진다

하지만 UI가 없는 건 아니라 열심히 구현해야 한다는 게 또 힘든 점

 

아무튼 이번에는 HP Bar 등의 Gauge UI!

Alpha Test를 이용해서 일반적인 Qaud에 Gauge UI 셰이더를 만들어봤기에 정리해보고자 한다


Bar Gauge UI Shader의 목표

여기서 Bar Gauge란 HP 같은 상하, 혹은 좌우로 움직이는 UI를 말한다

이번 태스크의 목표는 머티리얼의 Fill Amount란 값을 조정하는 것만으로 움직이게 하는 것였다

일단 다음과 같이 Bar Gauge의 Backgound가 있고, 조정된 위치에 Bar Gauge의 텍스처도 있기 때문에 별 다른 위치 조정도 하지 않는 것도 중요했다


Bar Gauge UI Shader의 샘플

우선 샘플부터!

Shader "CustomUI/BarGauge"
{
    Properties
    {
        [Header(Base Settings)][Space(5)]
        _BaseMap ("Base Map", 2D) = "white" { }
        _BaseColor("Base Color", Color) = (1,1,1,1)
        _Cutoff  ("Cutoff", Range(0.0, 1.0)) = 0.5
        
        [Header(Gauge Settings)][Space(5)]
        _FillAmount ("Fill Amount", Range(0, 1)) = 1.0
        [Toggle] _Reverse ("Reverse", float) = 0
        [Toggle] _UpDown ("Up-Down", float) = 0
        _FillStart ("Fill Start", Range(0, 1)) = 0
        _FillEnd ("Fill End", Range(0, 1)) = 1
        
        [Header(Advanced Settings)][Space(5)]
        [HideInInspector][Toggle] _ALPHATEST  ("Alpha Test", float) = 1.0
        [Enum(UnityEngine.Rendering.CullMode)] _CullMode ("Cull Mode", Float) = 2 // Back
        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("ZTest", Float) = 4 // LEqual
        [Enum(Off, 0, On, 1)] _ZWrite("ZWrite", Float) = 0 // Off
    }
    
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
        }

        Pass
        {
            Name "Universal Forward"
            Tags
            {
                "LightMode" = "UniversalForward"
            }

            Cull [_CullMode]
            ZTest [_ZTest]
            ZWrite [_ZWrite]
            AlphaToMask On
            
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #pragma shader_feature_local_fragment _ALPHATEST_ON
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            
            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);
            
            CBUFFER_START(UnityPerMaterial)
                half4 _BaseColor;
                float _FillAmount;
                float _Reverse;
                float _UpDown;
                float _FillStart;
                float _FillEnd;
                half _Cutoff;
                float4 _BaseMap_ST;
            CBUFFER_END
            
            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 UV           : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID 
            };

            struct Varyings
            {
                float4 PositionHCS  : SV_POSITION;
                float2 UV           : TEXCOORD0;
                float2 GaugeUV      : TEXCOORD1;
                UNITY_VERTEX_INPUT_INSTANCE_ID
                UNITY_VERTEX_OUTPUT_STEREO 
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                ZERO_INITIALIZE(Varyings, OUT);
                
                UNITY_SETUP_INSTANCE_ID(IN);
                UNITY_TRANSFER_INSTANCE_ID(IN, OUT);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);

                OUT.PositionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.UV = TRANSFORM_TEX(IN.UV, _BaseMap);

                // Fill Ammount
                // Remap 0~1 => Start~End 
                _FillAmount = _FillStart + _FillAmount * (_FillEnd - _FillStart);
                OUT.GaugeUV = lerp(
                    -IN.UV + _FillAmount,
                    IN.UV - (1.0 - _FillAmount),
                    _Reverse
                );
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(IN);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(IN);
                
                half4 outColor = half4(0.0, 0.0, 0.0, 1.0);
                
                const half4 baseTex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.UV);
                outColor.rgb = baseTex.rgb * _BaseColor.rgb;
                outColor.a = AlphaDiscard(baseTex.a, _Cutoff);
                clip(lerp(IN.GaugeUV.x, IN.GaugeUV.y, _UpDown));
                
                return outColor;
            }
            ENDHLSL
        }
    }
    FallBack "Hidden/Universal Render Pipeline/FallbackError"
}

Bar Gauge UI Shader에 대략적인 설명

사실 기본적인 방식은 Fill Amount 값에 맞춰서 Alpha Test을 하면 된다

여기서 궁리한 것은 HP Bar 텍스처의 빈 공간(위치 조정을 안 하기 위해 어쩔 수 없는)을 어떻게 처리할까 정도이다

 

이리저리 삽질한 결과, Start와 End 값을 받아서 Fill Amount를 Remapping하니 잘 작동했다

그리고 처음에는 모두 프래그먼트 셰이더에 구현했지만, 최적화를 위해 대부분의 계산을 버텍스 셰이더로 옮겼다

그 외에는 lerp를 이용해서 Fill Amount의 방향을 반전시키거나(_Reverse) 상하/좌우를 바꾸는(_UpDown) 것도 가능하게 했다


결과

아무튼 짜잔~!


마무리

이걸로 태스크 끝!

이라고 생각했던 시기가 저에게도 있었습니다…

 

잘 보니 업무 중에는 타이머 같이 한바퀴 도는 UI도 있었다

 

흑흑…이건 완전히 다른 방식을 취해야 할 거 같은데…

이건 이어서 다른 글에서 정리해보고자 한다


참고 사이트

이번엔 딱히 없는 듯?