HLSL / POST 52

(UnityShader) 07 Fragment Advanced : Flowmap

(UnityShader) 07 Fragment Advanced : Flowmap

(Hurricane)

FlowMap

저희는 기나긴 폭풍과 장마의 시련을 겪고 있습니다. Advanced 포스팅 이지만 오늘은 좀 쉽고, 멋진 걸 해볼겁니다. 플로우맵을 써서 위 이미지 처럼 텍스쳐가 원하는 방향으로 영원히 흐르게 해봅시다.

시작하기 전에 몇 가지 준비물이 필요합니다. 흐르게 할 메인 텍스쳐를 정하시고, 플로우맵을 직관적으로 그릴 수 있게 해주는 플로우맵 페인터(링크)을 다운 받습니다.

자세한 플로우맵 페인터 사용법은 마둠파님 블로그를 참고하십시오.

(https://blog.naver.com/mnpshino/221456311217)

FlowMapPainter.exe를 더블클릭하여 실행합시다. Configuration 창이 뜨는데, 여기서 프로그램 윈도우의 크기를 정해줍시다. (텍스쳐 해상도 아님) 그래픽 퀄리티는 제일 높은 fantastic으로 해주고

\[play!\]
를 눌러서 시작합니다.

기본적으로 빨간 네모처럼 R,G채널을 뒤집어(Flip) 줘야 합니다.

Custom Texture Path: 에 메인 텍스쳐의 경로를 직접 입력합시다. 그 다음

\[*Load Custum Tiling Texture\]
*를 눌러 이미지를 가져옵니다. 메인 텍스쳐에 플로우맵이 어떻게 적용될지 미리보는 용도입니다. 이미지가 180도 뒤집어져 있을 건데 포토샵으로 편집해서 방향을 맞춰줍시다.

참고로 커스텀 텍스쳐는 png로 저장해야 로드 오류가 없습니다. 유니티 엔진에서는 targa 포맷으로 할 거지만 말이죠.

적당히 동그랗게 그려준 뒤 Save to: 경로를 입력하고

\[*Bake to Texture\]
*를 눌러 플로우 맵을 구워(Bake) 줍시다.

플로우맵은 R,G채널만 사용하는 float3 데이터 텍스쳐입니다. 그러니 sRGB 체크 해제하고 사용하며 압축도 하지 않습니다. 맵 사이즈는 작을수록 아티팩트(픽셀 찌꺼기)가 적게 생깁니다.

플로우맵 텍스쳐를 보면서 색을 숫자로 생각해봅시다. RGB 값이 (128,128,0) 입니다. 그래서 그리지 않아서 UV가 변동이 없는 부분(디폴트)도 짙은 노란색으로 나옵니다. 따라서 코딩으로 플로우맵을 적용 할 때 0.5만큼 값을 빼주면 될 것 같습니다.

hlsl

 sampler2D _MainTex;
 float4 _MainTex_ST;

 sampler2D _FlowTex;
 float4 _FlowTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.uv1 = TRANSFORM_TEX(v.uv1, _FlowTex);
 return o;
            }

메인 텍스쳐와 플로우 텍스쳐를 샘플러 변수로 받아 줍시다. 버텍스 쉐이더에서 TRANSFORM_TEX(uv, tex); 매크로로 프래그먼트 쉐이더에서 tex2D를 사용할 수 있게 해줍시다.

hlsl

 float4 FlowTex = tex2D(_FlowTex, i.uv1);
 float4 MainTex = tex2D(_MainTex, i.uv + float2(FlowTex.rg) - 0.5);
 
                col.rgb = MainTex.rgb;
                col.a = 1;

 return col;

플로우맵을 Maintex의 UV에 더해서 오프셋 이동합니다. 그리지 않은 부분에 오프셋이 적용되지 않도록 FlowTex.rg에서 0.5만큼 값을 뺍니다.

(result)

제대로 적용되었습니다. 다음은 UV가 반복해서 흐르도록 만들어줄 차례입니다. 그러려면 uv에 타임 함수를 사용해야 할 것입니다.

hlsl

                _TimeSpeed = _Time.y * _TimeSpeed;

 float4 FlowTex = tex2D(_FlowTex, i.uv1);
 float4 MainTex = tex2D(_MainTex, i.uv + (float2(FlowTex.rg) - 0.5) * frac(_TimeSpeed));
 
                col.rgb = MainTex.rgb;
                col.a = 1;

 return col;

frac(_Time.y);

frac(); 함수는 변수의 소수점 부분만 반환하는 단순한 함수입니다. 하지만 _Time 함수 처럼 영원히 값이 증가하는 함수에 쓰이게 된다면 0 =< n =< 0.999... n값을 계속 반환하게 됩니다.

(세로축은 값, 가로축은 시간. 0~0.999...를 무한히 반복함)

(result)

무한히 흐르게 만드는건 성공했지만 시작과 끝이 연결되지 않고 끊어집니다. frac으로 _Time 함수가 0.999,,,가 다시 0에서 시작하기 때문입니다.

y = Sin(θ*|β|+γ)*α+δ

이것을 해결 하려면 삼각함수의 Sine 그래프처럼 루프의 시작점과 끝점의 값이 똑같아야 합니다.

hlsl

 float4 MainTex = tex2D(_MainTex, i.uv + (float2(FlowTex.rg) - 0.5) * frac(_TimeSpeed));
 float4 MainTex_loop = tex2D(_MainTex, i.uv + (float2(FlowTex.rg) - 0.5) * frac(_TimeSpeed + 0.5));

중학교 수학시간에 배웠던 대로 함수 그래프에 상수를 더하면 원하는 값 만큼 그래프를 수평이동 시킬 수 있습니다. 저희는 0.5만큼 수평이동한 _Time.y 함수로 플로우맵이 0.5초 만큼 빠르게 진행되는 메인 텍스쳐를 하나 더 만들 것입니다.

lerp(MainTex, MainTex_loop, lerpvalue);

두가지의 메인 텍스쳐를 합치면 위와 같은 모양이 됩니다. 저희는 선형보간을 통해 메인텍스쳐가 끊어지지 않는 부분을 검출 해낼 것입니다. 보간에 사용할 인자인 lerpvalue의 값은 아래와 같습니다.

먼저 frac(_Time.y)로 똑같이 씁니다.

2를 곱하면 그래프의 높이가 2배 증가합니다. 0~0.999...는 **0~1.999...**가 됩니다.

-1을 하면 그래프는 수직이동합니다. 이제 그래프의 범위는 음수를 포함하여 -0.999... =< n =< 0.999... 입니다.

hlsl
float lerpvalue = abs(frac(_TimeSpeed) * 2 - 1);

절대값을 반환하는 abs 함수를 사용해 음수 부분을 양수 부분으로 꺾어줍니다. 이제 그래프는 0과 0.999... 사이를 왕복하게 됩니다.

완성입니다. 읽어주셔서 감사합니다.