체력/마나 구체(Globe) UI: 유체(Fluid) 느낌을 살린 셰이더 기반 구형 게이지 제작
디아블로 스타일의 체력 및 마나 구체 UI를 셰이더로 구현하는 핵심 수식과 HLSL 코드를 다룹니다. 구형 왜곡, 수면 파동 연산, 프레넬 입체감 합성을 다룹니다.

유체 느낌의 구체(Globe) UI가 필요한 이유
디아블로(Diablo) 시리즈를 비롯한 클래식 액션 RPG에서 자주 볼 수 있는 구형 체력 및 마나 게이지는 게임의 분위기와 가시성을 동시에 높이는 대표적인 UI 요소입니다.
단순히 텍스처 채우기 방식(Image Fill Amount)으로 구현하면 게이지가 단조롭게 단면으로 잘려나가 유체의 생동감이 떨어집니다. 반면 셰이더를 활용하면 액체 표면의 수평 파동, 가장자리의 렌즈 구형 왜곡, 유리 구체 표면에 반사되는 프레넬(Fresnel) 하이라이트를 실시간으로 합성할 수 있습니다. 렌더링 오버헤드를 낮추면서도 높은 퀄리티의 시각 효과를 낼 수 있는 것이 셰이더 기반 구체 게이지의 가장 큰 장점입니다.

셰이더 연산 파이프라인과 핵심 수식
유체 구체 UI를 구현하기 위해서는 2D 평면 텍스처 위에 3D 구체의 입체감과 액체 수면의 흔들림을 수학적으로 연출해야 합니다. 연산 흐름은 크게 4단계로 나뉩니다.
flowchart LR
A[UV 좌표 정규화 및 구형 마스킹] --> B[Spherize 왜곡 및 수면 파동 합성]
B --> C[체력 비율 기반 높이 판별 & 거품 마스크 연산]
C --> D[프레넬 효과 및 유체 색상 최종 렌더링]
구체 UI의 핵심 연산은 세 가지 요소로 구성됩니다.
1. 구형 왜곡(Spherize Distortion)
평면 UV 좌표 중심 를 기준으로 중심과의 거리 를 구하고 이를 구면 가상 법선 벡터로 전환하여 구체 중앙이 도톰하게 튀어나와 보이는 효과를 만듭니다.
중심점 정규화 UV 좌표를 라 할 때 구형 법선 축 성분 수식은 다음과 같습니다.
2. 동적 수면 파동(Fluid Wave)
액체 표면이 흔들리는 느낌을 주기 위해 시간 변수 와 Sine 파동 그리고 단순 노이즈 패턴을 조합합니다. 체력 비율 ()에 따른 수면 높이 수식은 아래와 같습니다.
여기서 는 파동의 높이(Amplitude), 는 주기(Frequency), 는 파동 속도를 나타냅니다.
3. 프레넬 효과(Fresnel Effect)
구체 유리 외각에 빛이 비치는 하이라이트를 연산하기 위해 시선 벡터 와 구면 법선 벡터 을 내적합니다.
이 세 수식을 셰이더 내에서 조합하면 2D 텍스처 하나만으로도 물리적인 입체감이 있는 유체 구체를 완성할 수 있습니다.
HLSL 기반 구체 셰이더 구현
Unity 또는 HLSL 환경에서 동작하는 단일 패스 프래그먼트 셰이더 코드 예시입니다. 2D UI Canvas 또는 Custom UI Material에 바로 적용할 수 있습니다.
Shader "UI/FluidGlobeGauge"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_FillAmount ("Fill Amount", Range(0, 1)) = 0.75
_BaseColor ("Base Liquid Color", Color) = (0.8, 0.1, 0.1, 1)
_TopColor ("Top Surface Color", Color) = (1.0, 0.4, 0.2, 1)
_FoamColor ("Foam Color", Color) = (1.0, 0.9, 0.8, 1)
_GlassColor ("Glass Highlight Color", Color) = (1, 1, 1, 0.6)
_WaveSpeed ("Wave Speed", Float) = 3.0
_WaveFrequency ("Wave Frequency", Float) = 10.0
_WaveAmplitude ("Wave Amplitude", Float) = 0.02
_FresnelPower ("Fresnel Power", Float) = 3.0
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float _FillAmount;
float4 _BaseColor;
float4 _TopColor;
float4 _FoamColor;
float4 _GlassColor;
float _WaveSpeed;
float _WaveFrequency;
float _WaveAmplitude;
float _FresnelPower;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 1. UV 중심 이동 (-0.5 ~ 0.5)
float2 centeredUV = i.uv - 0.5;
float dist = length(centeredUV);
// 원형 마스킹 (반경 0.5 초과 영역 제거)
if (dist > 0.5)
discard;
// 2. 가상 3D 법선 및 구형 왜곡 연산
float z = sqrt(max(0.0, 0.25 - dist * dist));
float3 normal = normalize(float3(centeredUV, z));
// 3. 수면 파동 및 높이 계산
float wave1 = sin(centeredUV.x * _WaveFrequency + _Time.y * _WaveSpeed) * _WaveAmplitude;
float wave2 = cos(centeredUV.x * (_WaveFrequency * 1.5) - _Time.y * (_WaveSpeed * 1.2)) * (_WaveAmplitude * 0.5);
float totalWave = wave1 + wave2;
float surfaceY = (_FillAmount - 0.5) + totalWave;
// 액체 채우기 영역 마스크
float fillMask = step(centeredUV.y, surfaceY);
// 수면 거품(Foam) 영역 연산
float foamRange = 0.015;
float foamMask = smoothstep(surfaceY - foamRange, surfaceY, centeredUV.y) * fillMask;
// 4. 프레넬 외각 하이라이트
float fresnel = pow(1.0 - saturate(dot(normal, float3(0, 0, 1))), _FresnelPower);
// 5. 최종 색상 조합
float colorGradient = saturate((centeredUV.y + 0.5));
float3 liquidColor = lerp(_BaseColor.rgb, _TopColor.rgb, colorGradient);
liquidColor = lerp(liquidColor, _FoamColor.rgb, foamMask);
// 프레넬 하이라이트 및 유리 질감 합성
float3 finalColor = lerp(liquidColor, _GlassColor.rgb, fresnel * 0.4);
float finalAlpha = fillMask * _BaseColor.a + fresnel * _GlassColor.a;
return float4(finalColor, saturate(finalAlpha));
}
ENDCG
}
}
}

최적화 및 확장 고려사항
유체 구체 셰이더를 실제 게임 프로젝트에 적용할 때 점검해야 할 최적화 항목입니다.
-
Discard 명령 최소화 또는 알파 블렌딩 마스크 활용
프래그먼트 셰이더 내의discard구문은 모바일 GPU(Tile-Based Deferred Renderer)에서 Early-Z 최적화를 비활성화시켜 오버헤드를 유발할 수 있습니다. 픽셀을 버리는 대신 반경 마스크 Alpha를 곱해 단순 알파 블렌딩으로 처리하면 성능 향상에 도움이 됩니다. -
Sine 연산의 노이즈 텍스처 대체
삼각함수(sin,cos) 연산이 다수 포함되면 파이프라인 부담이 증가합니다. 더 사실적이고 거친 유체 표면이 필요하다면 작은 사이즈의 타일링 노이즈 텍스처(Noise Texture)의 UV를 동적으로 스크롤시키는 방식으로 파동 연산을 대체할 수 있습니다. -
피격 효과 및 동적 파동 감쇄(Smooth Damping)
체력이 갑자기 줄어들 때 게이지 수면이 크게 출렁이는 효과를 원한다면 C# 스크립트에서 파동의 높이(_WaveAmplitude) 변수 값을 충격량에 따라 가변적으로 조절하도록 작성하면 훨씬 생동감 있는 유체 연출이 가능합니다.



