void PostList()

Simple Magic Circle





위 이미지에서 표현된 마법진에선 각기 다른 3가지 모양이
다른 회전 방향과 속도로 회전하고 있습니다.

이는 마법진 텍스쳐가 일체형이 아니라 3부분으로 나뉘어 있음을 의미합니다.
즉 일반적으로 생각하기엔 위 이미지를 표현하기 위해 각기 다른 3가지 텍스쳐를
만들어야 함을 의미하겠지만 다행히도 우리에겐 하나의 이미지 파일안에 각기 다른
정보를 구분해서 넣을 수 있는 방법이 있습니다.


본 글에서 마법진을 표현하기 위해서 위 텍스쳐를 만들어 사용하였습니다.

< 색상 채널 분할 >


R,G,B 각 채널에 각기 다른 문양을 사용하였습니다.
셰이더 코드에서 텍스쳐 색상을 샘플링할때 .r .g .b 를 사용하여 
빨간색 색상, 푸른색 색상, 초록색 색상 각각을 가져올 수 있습니다.
( 추가로 프로퍼티로부터 _Color 를 받아 곱해준다면 원하는 색상의 마법진을 만들어 줄 수 있습니다. )

FinalColor[0] = tex2D(_MainTex, new_uv[0]).* _Color;
FinalColor[1] = tex2D(_MainTex, new_uv[1]).* _Color;
FinalColor[2] = tex2D(_MainTex, new_uv[2]).* _Color;

< 마법진 회전 >


특정 좌표의 회전후 좌표를 계산하는 방법중 하나는 바로 회전행렬을 사용하는 것입니다.
x만큼 회전을 나타내는 회전행렬과 특정 좌표값을 곱하면 회전 결과 좌표값이 나오게 됩니다.

for (int x = 0; x < 3; ++x)
                {
                    rotation_angle[x] = RotationSpeed[x] * _Time.y;
                        rotation_Matrix[x] =
                        float2x2(
                            cos(radians(rotation_angle[x])), -sin(radians(rotation_angle[x])),
                            sin(radians(rotation_angle[x])), cos(radians(rotation_angle[x]))
                            );
                    new_uv[x] = mul(rotation_Matrix[x], i.uv );
                }

다음 코드는 각 채널에 대한 회전연산을 수행하는 코드입니다.
R,G,B 세 채널에 대해 각기 다른 회전량을 적용할 것이기 때문에 
각기 다른 행렬계산을 수행합니다.

그러나 위 코드만으로 계산한 결과는 위 이미지와 같이 마법진이 이미지 중심에서 회전하는 것이 아니라 마치 모서리를 기준으로 회전하는 모양새가 됩니다.
회전행렬을 통해 회전을 표현할 때 회전의 기준점은 (0,0) 이기 때문입니다.
현재 uv 좌표의 범위는 0~1 사이의 값을 가집니다. 
이미지의 중심을 기준으로 회전하고자 한다면 

1. (-0.5, -0.5 ) 를 UV 좌표와 더해 먼저 평행이동을 한 후
2. 회전행렬과 UV 좌표값을 곱연산해서 회전
3. 다시 (0.5, 0.5) 를 UV 좌표와 더해 원위치로 평행이동

의 순서대로 연산을 할 필요성이 있습니다.

따라서

new_uv[x] = mul(rotation_Matrix[x], i.uv );

다음 코드는 

new_uv[x] = mul(rotation_Matrix[x], i.uv - 0.5 );
new_uv[x] = (new_uv[x] + 0.5);

이와 같이 수정할 필요성이 있습니다


회전을 수행할 때 텍스쳐의 Warp Mode 가 Repeat 일 경우 모서리에
마법진 문양의 일부가 나타날 수 있습니다. 

이는 회전 결과 UV 좌표값이 0~1 사이의 값을 넘어갈 경우 다시 0~1 사이 반복된 좌표값에 있는 색상을 샘플링하기 때문입니다.
( 예를들어 UV 값이 (1.5, 1.5) 라면 (0.5 ,0.5 )에 위치한 텍셀의 색상을 샘플링 하는 것이죠 )

이는 Clamp 모드로 변경함으로써 해결할 수 있습니다.
Clamp 모드일 경우 0~1 범위를 넘어갈 경우 이미지의 경계선의 색상을 샘플링하기 때문이죠.

< 추가 조정 - 은은하게 빛나는 마법진 >

단순 회전만으로 마법진을 표현하기에는 좀 심심하죠
그래서 은은하게 빛나는 효과를 주고 싶었습니다.
본 셰이더에선 은은하게 빛나는 효과를 위해 Sin 곡선을 사용하였습니다.

< 그래프 출처 - Desmos.com >

위 그래프가 바로 Sin 곡선입니다.
만약 x축을 "시간", y축을 "밝기" 라고 본다면 
시간에 따라 밝기가 줄어들었다가 다시 밝아지는 모양새를 만들 수 있다는걸 의미하죠.

saturate(_GlowMinIntensity + sin(_Time.y*_GlowSpeed)*_GlowAmplitude)* _Intensity;

본 셰이더에서 사용한 은은하게 밝아졌다 어두워지는 코드는 이부분입니다.
최종 색상을 계산할 때 위 식을 곱하는 방법을 사용하였죠.

_Time.y 에 sin 을 씌워 위 그래프처럼 밝기가 부드럽게 증감하는것을 표현합니다.
여기에 _GlowSpeed 를 곱해 x 의 증가량, 
즉 그래프의 주기 ( 얼마나 빠르게 깜빡이는지 ) 를 조절할 수 있게 해주었습니다.

그리고 sin() 식에  _GlowAmplitude 를 곱해주어 그래프의 진폭을 조절할 수 있게 해주었습니다. 여기서 진폭을 조절해 최대, 최소 밝기를 조절할 수 있습니다.

Sin 곡선을 보시면 아시겠지만 -1 과 1 사이의 값을 가지고 있습니다.
이는 밝기 값이 음수로 내려갈 수 있다는것을 의미하죠 따라서 saturate 를 씌워
0 이하로 내려가는것을 막아주었습니다.

마지막으로 _Intensity 를 곱해 마법진의 전체 밝기를 조절할 수 있게 해줍니다.

< 전체 코드 >

Shader "Unlit/MagicCircle"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _RotationSpeedR("RotationSpeed R", Range(-90,90)) = 0.0
        _RotationSpeedG("RotationSpeed G", Range(-90,90)) = 0.0
        _RotationSpeedB("RotationSpeed B", Range(-90,90)) = 0.0
 
        _Intensity("Intensity", Range(0,12)) = 0.0
 
        _GlowMinIntensity("Glow Min Intensity", Range(0,2)) = 0.0
        _GlowSpeed ("Glow Speed", Range(0.1, 5)) = 0.0
        _GlowAmplitude ("Glow Amplitude", Range(0.1, 5)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
        LOD 100
        Blend One One
        Cull off
        ZWrite off
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
 
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float4 vertColor : COLOR;
            };
 
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 vertColor : COLOR;
            };
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
            half4 _Color;
            half _RotationSpeedR;
            half _RotationSpeedG;
            half _RotationSpeedB;
            half _Intensity;
 
            half _GlowMinIntensity;
            half _GlowSpeed;
            half _GlowAmplitude;
 
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.vertColor = v.vertColor;
                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
 
                float RotationSpeed[3];
                float rotation_angle[3];
                float2x2 rotation_Matrix[3];
                float2 new_uv[3];
                half3 FinalColor[3];
 
                // Get Rotation Speed for each color channel.
                // 프로퍼티로부터 각 채널에 대한 회전속도를 가져온다.
                RotationSpeed[0] = _RotationSpeedR;
                RotationSpeed[1] = _RotationSpeedG;
                RotationSpeed[2] = _RotationSpeedB;
                
                // Calculate new UV coordinate with the 2x2 Rotation matrix.
                // 2x2 회전 행렬을 사용해 새로운 UV 좌표를 계산한다.
                for (int x = 0; x < 3; ++x)
                {
                    rotation_angle[x] = RotationSpeed[x] * _Time.y;
                        rotation_Matrix[x] =
                        float2x2(
                            cos(radians(rotation_angle[x])), -sin(radians(rotation_angle[x])),
                            sin(radians(rotation_angle[x])), cos(radians(rotation_angle[x]))
                            );
 
                    new_uv[x] = mul(rotation_Matrix[x], i.uv - 0.5 );
                    new_uv[x] = (new_uv[x] + 0.5);
                }
 
                FinalColor[0] = tex2D(_MainTex, new_uv[0]).* _Color;
                FinalColor[1] = tex2D(_MainTex, new_uv[1]).* _Color;
                FinalColor[2] = tex2D(_MainTex, new_uv[2]).* _Color;
 
                // Calculate Final Color
                // 최종 색상을 계산한다.
                half3 ResultColor = (FinalColor[0] + FinalColor[1] + FinalColor[2])
                    *i.vertColor.rgb
                    * saturate(_GlowMinIntensity + sin(_Time.y*_GlowSpeed)*_GlowAmplitude)* _Intensity;
                half FinalAlpha = i.vertColor.a;
 
                return half4(ResultColor,FinalAlpha);
 
            }
            ENDCG
        }
    }
}


ps.
마법진의 bloom 효과는 post-processing stack 1버전을 사용하였습니다.

댓글

이 블로그의 인기 게시물

Array Modifier 로 오브젝트를 원형으로 나열하기

회전 루프 애니메이션 만들기 ( Graph Editor F-Curve Modifiers )

원형으로 뚫려있는 구멍을 토폴로지 흐름에 맞게 채우기.