Microsoft MVP성태의 닷넷 이야기
Graphics: 21. shader - _Time 내장 변수를 이용한 UV 변동 효과 [링크 복사], [링크+제목 복사],
조회: 22700
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 136  137  138  139  140  141  142  143  144  145  146  147  [148]  149  150  ...
NoWriterDateCnt.TitleFile(s)
1353정성태9/17/201225764.NET Framework: 337. Python의 생성기와 코루틴을 C#으로 표현하면. [2]파일 다운로드1
1352정성태9/13/201223816.NET Framework: 336. .NET Profiler가 COM 개체일까?
1351정성태9/13/201228241디버깅 기술: 49. windbg - .NET Framework 스레드 개체의 COM Apartment 유형 확인하는 방법
1350정성태9/12/201228904개발 환경 구성: 167. (실은) 무료가 아니었던 AWS EC2 서비스 [4]
1349정성태9/11/201260638VS.NET IDE: 74. Visual Studio의 '새 파일'을 UTF-8 인코딩으로 지정하는 방법 [4]
1348정성태9/11/201228083오류 유형: 164. Active Directory - Functional Level 승격이 안 되는 문제
1347정성태9/10/201230528Windows: 62. 윈도우 서버 2012 - Hyper-V 서버 마이그레이션 [1]
1346정성태9/10/201231410Windows: 61. 윈도우 서버 2012 - Active Directory 서버 마이그레이션
1345정성태9/10/201235450스크립트: 12. 파이썬 - Win32 DLL 연동 [2]
1344정성태9/10/201228575오류 유형: 163. .NET Framework 4.5 제거 후 Visual Studio 2010 실행 시 Unknown Error
1343정성태9/8/201242341스크립트: 11. 파이썬(Python) 윈도우 개발 환경 [7]
1342정성태9/6/201226514VS.NET IDE: 73. Visual Studio 2012 - XmlCodeGenerator 마이그레이션
1341정성태9/4/201235843Windows: 60. Hyper-V에서 RemoteFX 없이 DirectX 11 제공 [12]
1340정성태9/4/201228037개발 환경 구성: 166. DOS - ping 결과에서 평균 응답 시간값 추출하기 [3]
1339정성태9/4/201230469개발 환경 구성: 165. 새로운 Visual Studio 2012 원격 디버깅 툴 [5]
1338정성태9/4/201232293.NET Framework: 335. C# - (핸들을 이용하여) 모든 열린 파일을 열람 [6]파일 다운로드1
1337정성태8/30/201222072Phone: 7. 디버거로 실습해 보는 윈도우 폰의 Tombstone 상태파일 다운로드1
1336정성태8/30/201240134.NET Framework: 334. 스레드 비정상 종료로 발생하는 CLOSE_WAIT 소켓 상태 [2]파일 다운로드1
1335정성태8/30/201228883Windows: 59. Hyper-V Internal 네트워크 VM의 인터넷 접속
1334정성태8/29/201248140.NET Framework: 333. 코드로 재현하는 소켓 상태(FIN_WAIT1, FIN_WAIT2, TIME_WAIT, CLOSE_WAIT, LAST_WAIT) [6]
1333정성태8/27/201251617개발 환경 구성: 164. system32 폴더에 있는 파일의 권한 조정 [2]
1332정성태8/23/201223513Team Foundation Server: 48. TFS - Team Project Collection 이전하는 방법
1331정성태8/23/201226656오류 유형: 162. Database '...' already exists. Choose a different database name. (Microsoft SQL Server, Error: 1801)
1330정성태8/22/201227414Team Foundation Server: 47. 5인 이내의 팀, 또는 개인 로컬 소스 관리를 위한 무료 TFS Express
1329정성태8/21/201222889오류 유형: 161. Azure - Storage 삭제가 안되는 경우 [1]
1328정성태8/20/201233294개발 환경 구성: 163. IIS 7 - "MIME Types" 설정 아이콘이 없는 경우
... 136  137  138  139  140  141  142  143  144  145  146  147  [148]  149  150  ...