top of page
  • 작성자 사진Nobody

(UnityShader) Flowmap

최종 수정일: 2022년 6월 12일

(UnityShader) 07 Fragment Advanced : Flowmap


(Hurricane)

FlowMap


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


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


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



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만큼 값을 빼주면 될 것 같습니다.


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를 사용할 수 있게 해줍시다.


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에 타임 함수를 사용해야 할 것입니다.


                _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 그래프처럼 루프의 시작점과 끝점의 값이 똑같아야 합니다.


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... 입니다.



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


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




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



조회수 609회댓글 0개

최근 게시물

전체 보기
bottom of page