본문 바로가기

[unity] 2D Rounded Mask Shader

앤디가이 2022. 5. 27.

Unity에서 모서리가 Round 된 Masking을 처리하는 Shader를 만들어 보자.

 

1. 기존 Masking 사용 방법

모서리 라운드 같은 경우도, 기존에는 마스킹을 주로 사용하였다.

Mask 컴포넌트를 사용하면 퍼포먼스나 깨끗하게 마스킹이 안 되는 현상이 있기 때문에 최근에는 셰이더를 많이 이용한다.

 

유니티의 마스크를 사용하는 방법은 기존 글을 참고해주기 바란다.

2022.05.27 - [unity3d/Shader] - [unity] 2D Circle Mask Shader

 

[unity] 2D Circle Mask Shader

Unity에서 Circle 형태의 Masking 을 처리하는 Shader에 대해서 알아보자. 앱 개발 중 동그란 썸네일 및 동그란 아이콘 이미지를 구현해야 되는 상황이 생겼다. 1. 유니티에서 가장 기본적인 방법은 유니

wonjuri.tistory.com

 

2. Shader Masking 사용 방법

그럼 라운드 처리되는 Shader를 제작해보자.

프로젝트에 폴더를 생성 후 셰이더와 메터리얼을 하나씩 만들어 준다.

UIRounded Shader
UIRoundedShader

RoundedCorners Shader를 더블 클릭 후 코드를 아래와 같이 변경해준다.

Shader "UI/RoundedMask" {
    Properties {
        [HideInInspector] _MainTex ("Texture", 2D) = "white" {}

        // --- Mask support ---
        [HideInInspector] _StencilComp ("Stencil Comparison", Float) = 8
        [HideInInspector] _Stencil ("Stencil ID", Float) = 0
        [HideInInspector] _StencilOp ("Stencil Operation", Float) = 0
        [HideInInspector] _StencilWriteMask ("Stencil Write Mask", Float) = 255
        [HideInInspector] _StencilReadMask ("Stencil Read Mask", Float) = 255
        [HideInInspector] _ColorMask ("Color Mask", Float) = 15
        [HideInInspector] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
        
        _WidthHeightRadius ("WidthHeightRadius", Vector) = (100,100,30,0)
    }
    
    SubShader
    {
        Tags {
            "RenderType"="Transparent"
            "Queue"="Transparent"
        }

        // --- Mask support ---
        Stencil {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }
        Cull Off
        Lighting Off
        ZTest [unity_GUIZTestMode]
        ColorMask [_ColorMask]
        
        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite Off

        

        Pass {
            CGPROGRAM
            
            #include "UnityCG.cginc"
            
            #pragma vertex vert
            #pragma fragment frag

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 color : COLOR;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 color : COLOR;
            };

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.color = v.color;
                return o;
            }

            inline fixed4 mixAlpha(fixed4 mainTexColor, fixed4 color, float sdfAlpha){
                fixed4 col = mainTexColor * color;
                col.a = min(col.a, sdfAlpha);
                return col;
            }

            float rectangle(float2 samplePosition, float2 halfSize){
                float2 distanceToEdge = abs(samplePosition) - halfSize;
                float outsideDistance = length(max(distanceToEdge, 0));
                float insideDistance = min(max(distanceToEdge.x, distanceToEdge.y), 0);
                return outsideDistance + insideDistance;
            }

            float roundedRectangle(float2 samplePosition, float absoluteRound, float2 halfSize){ 

                return rectangle(samplePosition, halfSize - absoluteRound) - absoluteRound;
            }

            float AntialiasedCutoff(float distance){
                float distanceChange = fwidth(distance) * 0.5;
                return smoothstep(distanceChange, -distanceChange, distance);
            }

            float CalcAlpha(float2 samplePosition, float2 size, float radius){
                float2 samplePositionTranslated = (samplePosition - .5) * size;
                float distToRect = roundedRectangle(samplePositionTranslated, radius * .5, size * .5);
                return AntialiasedCutoff(distToRect);
            }

            inline float2 translate(float2 samplePosition, float2 offset){
                return samplePosition - offset;
            }

            float intersect(float shape1, float shape2){
                return max(shape1, shape2);
            }

            float2 rotate(float2 samplePosition, float rotation){
                const float PI = 3.14159;
                float angle = rotation * PI * 2 * -1;
                float sine, cosine;
                sincos(angle, sine, cosine);
                return float2(cosine * samplePosition.x + sine * samplePosition.y, cosine * samplePosition.y - sine * samplePosition.x);
            }

            float circle(float2 position, float radius){
                return length(position) - radius;
            }

            float CalcAlphaForIndependentCorners(float2 samplePosition, float2 halfSize, float4 rect2props, float4 r){

                samplePosition = (samplePosition - .5) * halfSize * 2;

                float r1 = rectangle(samplePosition, halfSize);
                
                float2 r2Position = rotate(translate(samplePosition, rect2props.xy), .125);
                float r2 = rectangle(r2Position, rect2props.zw);
    
                float2 circle0Position = translate(samplePosition, float2(-halfSize.x + r.x, halfSize.y - r.x));
                float c0 = circle(circle0Position, r.x);
    
                float2 circle1Position = translate(samplePosition, float2(halfSize.x - r.y, halfSize.y - r.y));
                float c1 = circle(circle1Position, r.y);
    
                float2 circle2Position = translate(samplePosition, float2(halfSize.x - r.z, -halfSize.y + r.z));
                float c2 = circle(circle2Position, r.z);
    
                float2 circle3Position = translate(samplePosition, -halfSize + r.w);
                float c3 = circle(circle3Position, r.w);
        
                float dist = max(r1,min(min(min(min(r2, c0), c1), c2), c3));
                return AntialiasedCutoff(dist);
            }
            
            float4 _WidthHeightRadius;
            sampler2D _MainTex;

            fixed4 frag (v2f i) : SV_Target {
                float alpha = CalcAlpha(i.uv, _WidthHeightRadius.xy, _WidthHeightRadius.z);
                return mixAlpha(tex2D(_MainTex, i.uv), i.color, alpha);
            }
            ENDCG
        }
    }
}

 

해당 셰이더를 컴파일 후 

RoundedCorners 메터리얼의 셰이더를 UI/RoundedMask로 변경해준다.

씬에 캔버스와 이미지를 만든 후, 셰이더를 적용할 UIImage에 해당 메터리얼을 연결해준다.

Shader Rounded Mask
Shader Rounded Mask

하단 WidhHeightRadius 의 Z 값을 조절해 주면, 라운드 되는 값을 조절할 수 있다.

 

짜잔!

Rounded shader 적용된 모습

상당히 깔끔하게 처리됨은 물론, 퍼포먼스도 좋다.

아. 참고로 해당 셰이더는 유니티 2019.3 이하 버전에서는 제대로 동작 안 할 수 있으니 참고~

 

댓글