Microsoft MVP성태의 닷넷 이야기
Graphics: 21. shader - _Time 내장 변수를 이용한 UV 변동 효과 [링크 복사], [링크+제목 복사],
조회: 22615
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

shader - _Time 내장 변수를 이용한 UV 변동 효과

다음의 책을 보면,

유니티 쉐이더 스타트업
; http://www.yes24.com/24/goods/58495827

Part 6에서 불 이펙트를 만들기 위해 다음과 같은 shader 코드를 작성합니다.

Shader "Custom/fireShader" {

    Properties {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _MainTex2("Albedo (RGB)", 2D) = "white" {}
    }

    SubShader {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }

        CGPROGRAM
        #pragma surface surf Lambert alpha:fade noambient

        sampler2D _MainTex;
        sampler2D _MainTex2;

        struct Input {
            float2 uv_MainTex;
            float2 uv_MainTex2;
        };

        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 d = tex2D(_MainTex2, float2(IN.uv_MainTex2.x, IN.uv_MainTex2.y - _Time.y));
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex + d.r);

            o.Emission = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

직관적으로는 저 코드가 사실 어떤 것인지 잘 이해가 안 되었는데요, 한번 차근히 분석해봤습니다. ^^ 우선, 중요한 것은 다음의 값이 의미하는 게 뭐냐는 것입니다.

IN.uv_MainTex2.y - _Time.y

(Unity Editor에서 변경하지 않은 경우) Plane 객체에서 UV 좌표계는 0 ~ 1 범위의 값을 갖기 때문에 위의 코드에서 IN.uv_MainTex2.y의 값은 0 ~ 1 사이입니다. 그런데, Unity에서는 UV 매핑이 다음과 같기 때문에,

[그림: Unity - UV 좌표계 - 출처 http://lovelyseekerclaire.tistory.com/27]
unity_uv_1.png

Plane 객체의 상단에서 y는 1로 시작해 하단으로 오면서 0이 됩니다.

그다음, _Time.y의 값은 Scene이 시작된 이후 흐른 시간을 초 단위로 소수점 이하의 값을 유지하면서 반환합니다. 즉, 1.531초, 1.612초와 같은 식입니다. 따라서 "IN.uv_MainTex2.y - _Time.y" 코드의 의미는 1초마다 IN.uv_MainTex2의 texture를 위에서 아래로 훑게 됩니다. 만약 이상적으로 1 fps를 유지하며 화면이 업데이트 된다면 IN.uv_MainTex2.y의 값은 언제나 같은 UV 좌표 값을 반환하는 것이나 같습니다. 가령, IN.uv_MainTex2.y의 값이 0.4인 위치에 있었다면 각각의 시간마다 다음과 같은 값을 반환하게 됩니다.

IN.uv_MainTex2.y = 0.4의 위치에서,

0.1초 후: 0.3
0.3초 후: 0.1
0.5초 후: -0.1
0.8초 후: -0.4
1.0초 후: -0.6
1.5초 후: -1.1
2.0초 후: -1.6
3.0초 후: -2.6
4.0초 후: -3.6
....

테스트를 위해 MainTex2의 이미지를 다음과 같은 코드로 생성해 보겠습니다.

using System.Collections.Generic;
using System.Drawing;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            int w = 32;
            int h = 32;
            Bitmap img = new Bitmap(w, h);

            int r = 0;
            Sequence seq = new Sequence(0, 32);
            var rSeq = seq.Get();

            for (int y = 0; y < h; y++)
            {
                r = rSeq.Current;
                rSeq.MoveNext();
                for (int x = 0; x < w; x++)
                {
                    Color rInc = Color.FromArgb(r, 0, 0);
                    img.SetPixel(x, y, rInc);
                }
            }

            img.Save("test_" + w + "_" + h + ".png");
        }
    }

    class Sequence
    {
        int _start;
        int _end;
        int _current;
        bool _increment;

        public Sequence(int start, int end)
        {
            _start = start;
            _end = end;

            _current = _start;
            _increment = true;
        }

        public IEnumerator<int> Get()
        {
            while (true)
            {
                yield return _current;

                if (_increment == true)
                {
                    _current++;
                }
                else
                {
                    _current--;
                }

                if (_current == _end)
                {
                    _increment = false;
                }
                else if (_current == _start)
                {
                    _increment = true;
                }
            }
        }
    }
}

위의 코드로 생성하면, y 좌표의 증가에 따라 R 값이 0, 1, 2, 3, ..., 30, 31까지 올라가는 이미지가 생성됩니다. 따라서 다음의 코드는,

fixed4 d = tex2D(_MainTex2, float2(IN.uv_MainTex2.x, IN.uv_MainTex2.y - _Time.y));

UV 좌표로 (0, 0) 위치에 있던 texture의 경우라면 1초 동안 R 성분이 0 ~ 31까지 변하는 "fixed4 d" 값을 반환하게 됩니다. 물론, 0 ~ 31은 바이트 범위의 R 컬러 값을 가정한 것이고, shader에서는 1바이트 값이 0 ~ 1로 정규화되기 때문에 만약 R == 5라면 "5:255 = x:1"의 비례식에 x = 5 / 255 = 0.01960... 값이 나옵니다. 일단 여기까지 이해했으면 다음의 코드는 쉽게 이해가 됩니다.

fixed4 c = tex2D(_MainTex, IN.uv_MainTex + d.r); // R 값이 5인 경우, d.r = 0.01960
                                                 //      255인 경우, d.r = 1.0
                                                 //        0인 경우, d.r = 0.0

위의 코드를 좀 더 쉽게 이해하려면 다음과 같이 y 좌표에 대해서만 영향을 주는 걸로 바꿔볼 수 있습니다.

fixed4 c = tex2D(_MainTex, float2(IN.uv_MainTex.x, IN.uv_MainTex.y + d.r));

이렇게 하면 texture 이미지에 담긴 R 값의 범위에 따라 _MainTex 이미지 크기에 비례해 다른 y 위치의 점에 해당하는 컬러를 c에 반환하게 됩니다. 예를 들어 예제로 생성한 0 ~ 31까지의 범위를 갖는 MainTex2 이미지인 경우, Height = 1024 pixel인 MainTex 이미지에 대해 저 shader 코드를 실행한다면, 원래의 점을 반환하는 좌표로부터 다음의 비례식에 따라,

0:255 = 0:1023
31:255 = y:1023

y = 124.3647058823529

최대 124 pixel까지의 이웃하는 점을 반환하는 것이 가능해집니다.




일단, 예제에 따라 0 ~ 31의 순차 값으로 채워진 32x32 이미지를 MainTex2로 지정하면 어떻게 될까요? 31까지 이동한 다음 곧바로 다시 0부터 시작하기 때문에 다음과 같이 순간적으로 제자리로 돌아가는 움직임이 발생하게 됩니다. (y 값만 변경하도록 바꾼 것입니다.)



따라서 부드럽게 진동을 하고 싶다면 0 ~ 16까지 증가한 다음 다시 16 ~ 0으로 내려오도록 R 값을 구성하면 됩니다.

static void Main(string[] args)
{
    int w = 32;
    int h = 32;
    Bitmap img = new Bitmap(w, h);

    int r = 0;
    Sequence seq = new Sequence(0, 16);
    ...[생략]...
    img.Save("test_" + w + "_" + h + ".png");
}

위와 같이 구성한 png 파일을 다시 _MainTex2 texture로 지정하고 실행하면 이번엔 다음과 같이 부드러운 진동을 볼 수 있습니다.





(첨부 파일은 이 글의 C# 프로젝트를 포함합니다.)




자, 이제 그럼 대충 _Time과 함께 어떤 식으로 _MainTex에 반영되는지 알았다면 "유니티 쉐이더 스타트업" 책에서 낸 실습 과제를 풀어낼 수 있을 것입니다.

첫 번째 문제는 "구겨지는 정도"인데, 그것은 _MainTex2에 지정한 texture에 담긴 R 값의 범위가 255까지 사용하면 구겨짐이 심하고 0에 가깝게만 구성하면 구겨짐이 약해질 것입니다. 물론 texture 이미지를 그렇게 바꾸는 것도 답일 수 있겠지만 shader 측에서 다음과 같이 r 값을 조절해 주는 것도 가능합니다.

void surf (Input IN, inout SurfaceOutput o) {
    fixed4 d = tex2D(_MainTex2, float2(IN.uv_MainTex2.x, IN.uv_MainTex2.y - _Time.y));
    fixed4 c = tex2D(_MainTex, IN.uv_MainTex + d.r / 2);

    o.Emission = c.rgb;
    o.Alpha = c.a;
}

위에서는 2로 나눴지만 저 값을 Properties에 지정해서 아티스트가 지정하도록 바꾸면 됩니다. 나누는 값이 커질수록 변위가 작아지기 때문에 구겨짐이 약해집니다.

두 번째 문제는 전체적으로 약간 왼쪽 아래로 내려간 이미지를 다시 오른쪽 위로 올릴 수 있느냐는 것입니다. 위치 0에 있던 점이 0 ~ 31까지의 + 위치에 대해서만 진동을 하기 때문에 Unity의 UV 좌표계에 따라 왼쪽 아래로 진동을 하게 되는 것입니다. 게다가 아래로 이동하는 문제로 인해 상단의 일정 영역이 타일링되어 불꽃이 다음과 같이 뜨는 현상이 발생합니다.

unity_uv_4.png

이 글에서 살펴본 바에 따르면 R 값이 반환하는 범위가 0 ~ 31이라면 15를 빼서 -15 ~ +15 사이로 진동하도록 바꾸는 것이 가능합니다. 그럼 어느 쪽에도 기울지 않고 원래의 texture 이미지를 기준으로 좌/우로 진동할 것입니다. 그런데 shader에서 그 값을 구하는 코드를 넣을 수는 없습니다.

일반화를 제외하고, 수동으로 조절하는 것이라면 약간의 offset 값을 주는 것으로 가능합니다. 책을 쓴 저자가 어떤 답을 기대하고 있었는지는 알 수 없지만, 다음과 같이 일정 수의 값을 임의로 Properties를 통해 지정할 수 있다면 불을 중앙으로 다시 옮길 수 있습니다. (예제의 경우 0.1을 빼면 됩니다.)

void surf (Input IN, inout SurfaceOutput o) {
    fixed4 d = tex2D(_MainTex2, float2(IN.uv_MainTex2.x, IN.uv_MainTex2.y - _Time.y));
    fixed4 c = tex2D(_MainTex, IN.uv_MainTex + (d.r - 0.1) / 2);

    o.Emission = c.rgb;
    o.Alpha = c.a;
}




그런데, noise2.png 파일로 불 효과를 구현하는 해당 예제에는 또 다른 문제가 있습니다.

unity_uv_5.png

보는 바와 같이 끊김 선이 보인다는 것입니다. 저 선이 있는 이유는 예제에서 사용하고 있는 noise2.png 파일의 가장 하단과 가장 상단의 값이 부드럽게 이어지지 않기 때문에 발생하는 것입니다. 일례로, 제가 만든 C# 프로그램에서 생성한 0, 1, 2, ..., 16까지 증가하고 다시 감소하는 32x32 이미지를 예제로 사용하면 저런 현상이 없습니다.

따라서 이런 줄무늬가 생성되는 현상을 방지하려면 noise2.png를 만드는 디자이너에게 하단의 값을 상단의 값으로 부드럽게 이어지는 노이즈를 생성해 달라고 해야 합니다. 아니면 직접 ^^ GIMP 같은 도구에서 "Filters" / "Map" / "Tile Seamless..." 메뉴를 실행하면 그런 작업을 자동으로 해줍니다.)

해당 이미지를 사용하면 위에서 보였던 줄 무늬가 더 이상 보이지 않게 됩니다.




애셋이 로컬에 설치된 경우 다음의 경로에서 찾을 수 있습니다.

%APPDATA%\Unity\Asset Store-5.x

간혹, 애셋을 Unity에서 import하려는 경우 다음과 같은 오류가 발생하면서 안 될 때가 있습니다.

Nothing to import!
All assets from this package are already in your project.

이런 경우 Unity를 다시 시작하면 정상적으로 애셋을 가져올 수 있습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 9/3/2018]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1780정성태10/15/201424163오류 유형: 249. The application-specific permission settings do not grant Local Activation permission for the COM Server application with CLSID
1779정성태10/15/201419678오류 유형: 248. Active Directory에서 OU가 지워지지 않는 경우
1778정성태10/10/201418126오류 유형: 247. The Netlogon service could not create server share C:\Windows\SYSVOL\sysvol\[도메인명]\SCRIPTS.
1777정성태10/10/201421213오류 유형: 246. The processing of Group Policy failed. Windows attempted to read the file \\[도메인]\sysvol\[도메인]\Policies\{...GUID...}\gpt.ini
1776정성태10/10/201418266오류 유형: 245. 이벤트 로그 - Name resolution for the name _ldap._tcp.dc._msdcs.[도메인명]. timed out after none of the configured DNS servers responded.
1775정성태10/9/201419390오류 유형: 244. Visual Studio 디버깅 (2) - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
1774정성태10/9/201426594개발 환경 구성: 246. IIS 작업자 프로세스의 20분 자동 재생(Recycle)을 끄는 방법
1773정성태10/8/201429764.NET Framework: 471. 웹 브라우저로 다운로드가 되는 파일을 왜 C# 코드로 하면 안되는 걸까요? [1]
1772정성태10/3/201418545.NET Framework: 470. C# 3.0의 기본 인자(default parameter)가 .NET 1.1/2.0에서도 실행될까? [3]
1771정성태10/2/201428056개발 환경 구성: 245. 실행된 프로세스(EXE)의 명령행 인자를 확인하고 싶다면 - Sysmon [4]
1770정성태10/2/201421675개발 환경 구성: 244. 매크로 정의를 이용해 파일 하나로 C++과 C#에서 공유하는 방법 [1]파일 다운로드1
1769정성태10/1/201424098개발 환경 구성: 243. Scala 개발 환경 구성(JVM, 닷넷) [1]
1768정성태10/1/201419520개발 환경 구성: 242. 배치 파일에서 Thread.Sleep 효과를 주는 방법 [5]
1767정성태10/1/201424624VS.NET IDE: 94. Visual Studio 2012/2013에서의 매크로 구현 - Visual Commander [2]
1766정성태10/1/201422441개발 환경 구성: 241. 책 "프로그래밍 클로저: Lisp"을 읽고 나서. [1]
1765정성태9/30/201426038.NET Framework: 469. Unity3d에서 transform을 변수에 할당해 사용하는 특별한 이유가 있을까요?
1764정성태9/30/201422267오류 유형: 243. 파일 삭제가 안 되는 경우 - The action can't be comleted because the file is open in System
1763정성태9/30/201423845.NET Framework: 468. PDB 파일을 연동해 소스 코드 라인 정보를 알아내는 방법파일 다운로드1
1762정성태9/30/201424545.NET Framework: 467. 닷넷에서 EIP/RIP 레지스터 값을 구하는 방법 [1]파일 다운로드1
1761정성태9/29/201421555.NET Framework: 466. 윈도우 운영체제의 보안 그룹 이름 및 설명 문자열을 바꾸는 방법파일 다운로드1
1760정성태9/28/201419821.NET Framework: 465. ICorProfilerInfo::GetILToNativeMapping 메서드가 0x80131358을 반환하는 경우
1759정성태9/27/201430968개발 환경 구성: 240. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법파일 다운로드1
1758정성태9/23/201437847개발 환경 구성: 239. 원격 데스크톱 접속(RDP)을 기존의 콘솔 모드처럼 사용하는 방법 [1]
1757정성태9/23/201418390오류 유형: 242. Lync로 모임 참여 시 소리만 들리지 않는 경우 - 두 번째 이야기
1756정성태9/23/201427390기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434188오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
... 121  122  123  124  125  126  127  128  129  130  [131]  132  133  134  135  ...