일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- VR
- ASW(Application SpaceWarp)
- 작업 집합
- 벡터
- 프로그래밍 기초
- Three(Two) Tone Shading
- URP
- Rim Light
- Cartoon Rendering
- 3d
- working set
- 메모리 누수
- Cell Look
- Private Bytes
- AppSW
- OculusMotionVectorPass
- URP로 변경
- C언어
- ColorGradingLutPass
- 개인 바이트
- 가상 바이트
- Windows Build
- Specular
- 게임 수학
- Cell Shader
- Virtual Byte
- Toon Shader
- Today
- Total
WinCNT
Fill Amount 기능이 있는 간단(?) Dial Gauge UI(가칭) 셰이더 만들어보기! 본문
Fill Amount 기능이 있는 간단(?) Dial Gauge UI(가칭) 셰이더 만들어보기!
WinCNT_SSS 2024. 6. 16. 18:23서론
저번에 간단 Bar Gauge UI 셰이더를 만들었으나 태스크는 그게 끝이 아니었다
이어서 만들어 볼 건 이런 느낌의 돌면서 채워지는 Gauge UI
이걸 뭐라 부르는지는 몰라서 일단 Dial Gauge UI라고 부르기로 하면서 정리해봤다
다 만들고 커밋한 후 Radial Gauge라고 부르는 게 더 맞다고 느꼈으나 뭘 어쩌겠어
Dial Gauge UI Shader의 목표
목표 자체는 Bar Gauge 때와 크게 다르지 않았다
마찬가지로 Fill Amount를 조정하는 것만으로 게이지가 움직이도록 하면 됐다
이번에도 Backgound가 있고, 조정된 위치에 Gauge의 텍스처가 있기에 별 다른 위치 조정이 없도록 하는 것은 같았지만, 회전하는 피벗은 조정하게 해야 한다는 이리저리 어려운 문제가…
Dial Gauge UI Shader의 샘플
다행히 딴 프로젝트의 어딘가에 굴러다니는 Shader Graph 샘플이 있다는 걸 기억하고 그걸 분석해서 만들어봤다
아무튼 코드 샘플부터!
Shader "CustomUI/DialGauge"
{
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(Fill Amount Settings)][Space(5)]
_FillAmount ("Fill Amount", Range(0, 1)) = 1.0
[Toggle] _Reverse ("Reverse", float) = 0
_CenterX ("Center X", Range(0, 1)) = 0.5
_CenterY ("Center Y", Range(0, 1)) = 0.5
_Rotation ("Rotation Angle", Range(-360, 360)) = 0.0
_FillRangeAngle ("Fill Range Angle", Range(0.001, 360)) = 360.0
[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 _CenterX;
float _CenterY;
float _Rotation;
float _FillRangeAngle;
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);
// Dial Gauge Fill Ammount
// Rotate
float2 uv = IN.UV;
uv.x = lerp(uv.x, 1.0 - uv.x, _Reverse);
float rotation = _Rotation * (PI/180.0f);
float2 center = float2(_CenterX, _CenterY);
float2 rotateUV = uv - center;
float s = sin(rotation);
float c = cos(rotation);
float2x2 rMatrix = float2x2(c, -s, s, c);
rMatrix *= 0.5;
rMatrix += 0.5;
rMatrix = rMatrix * 2 - 1;
rotateUV.xy = mul(rotateUV.xy, rMatrix);
rotateUV += center;
// Polar Coordinates Delta(Compute radius and angle at fragment Shader)
OUT.GaugeUV = rotateUV - center;
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);
half4 baseTex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.UV);
outColor.rgb = baseTex.rgb * _BaseColor.rgb;
outColor.a = AlphaDiscard(baseTex.a, _Cutoff);
// Polar Coordinates
float2 delta = IN.GaugeUV;
// float radius = length(delta);
float angle = atan2(delta.x, delta.y) * 1.0/6.28 + 0.5; //atan2(uv.y, uv.x) / UNITY_PI * 0.5 + 0.5;
// Fill Ammount
const float gaugeAlpha = min(max(angle * (360.0 / _FillRangeAngle), 0.001), 0.999);
clip(-gaugeAlpha + _FillAmount);
return outColor;
}
ENDHLSL
}
}
FallBack "Hidden/Universal Render Pipeline/FallbackError"
}
Dial Gauge UI Shader에 대략적인 설명
Dial Gauge에서 가장 중요한 건 역시 극좌표이다(Polar Coordinate)
UV 좌표에서 현재 픽셀이 어떤 각도인지를 알아야 Alpha Test를 하든 말든 하기 때문…
(UnityShader) Polar Coordinate Part 2
그리고 경우에 따라서는 게이지가 차는 시작 지점을 회전해서 조정하고 싶은 경우도 있으리라 생각해 Roate 처리를 추가했다
또 원의 일부분까지만 게이지를 채우고 싶은 경우도 있을테니 _FillRangeAngle로 조정할 수 있도록 하기도 하고…
다행히도 Ratate나 Polar Coordinate 변환은 Shader Graph에 노드가 있으며, 이 노드를 클릭하고 F1를 누르면 그 샘플 코드를 볼 수 있다(Shader Graph의 최고의 기능 아닐까?)
Rotate Node | Shader Graph | 6.9.2
Polar Coordinates Node | Shader Graph | 6.9.2
마지막으로 최적화를 위해 가능한 범위에서 계산 일부를 버텍스 셰이더로 옮겼다
근데 이제보니 Rotate만 옮겼어도 됐을 거 같네…
결과
아무튼 짜잔~!
마무리
이걸로 Gauge UI의 작업이 끝났다
Bar Gauge는 쉬웠지만 Dail Gauge는 샘플이 없었으면 좀 더 헤맸을 거 같다
뭐 샘플 찾는 것도 능력 중 하나라고 치자!
참고 사이트
(UnityShader) Polar Coordinate Part 2
'Unity > URP or Shader 관련' 카테고리의 다른 글
Visual Effect Graph(VFX Graph)의 도입 체험기! (0) | 2024.07.09 |
---|---|
Alpha 값으로 Blue Noise를 이용한 Dither를 해보자! (0) | 2024.06.24 |
Fill Amount 기능이 있는 간단 Bar Gauge UI 셰이더 만들어보기! (0) | 2024.04.11 |
Vignette를 불투명 Mesh와 반투명 Mesh로 나눠서 구현해보자! (0) | 2024.04.08 |
URP에서 간단하게 Stencil Buffer를 사용해서 Magic Card 만들어보기! (0) | 2024.03.13 |