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

비밀번호

댓글 작성자
 




... 76  77  [78]  79  80  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11983정성태7/8/201914878오류 유형: 556. nodemon - 'mocha' is not recognized as an internal or external command, operable program or batch file.
11982정성태7/8/201914960오류 유형: 555. Visual Studio 빌드 오류 - result: unexpected exception occured (-1002 - 0xfffffc16)
11981정성태7/7/201918048Math: 64. C# - 3층 구조의 신경망(분류)파일 다운로드1
11980정성태7/7/201928162개발 환경 구성: 450. Visual Studio Code의 Java 확장을 이용한 간단한 프로젝트 구축파일 다운로드1
11979정성태7/7/201918399개발 환경 구성: 449. TFS에서 gitlab/github등의 git 서버로 마이그레이션하는 방법
11978정성태7/6/201917683Windows: 161. 계정 정보가 동일하지 않은 PC 간의 인증을 수행하는 방법 [1]
11977정성태7/6/201922266오류 유형: 554. git push - error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413 Request Entity Too Large
11976정성태7/4/201916584오류 유형: 553. (잘못 인증 한 후) 원격 git repo 재인증 시 "remote: HTTP Basic: Access denied" 오류 발생
11975정성태7/4/201925402개발 환경 구성: 448. Visual Studio Code에서 콘솔 응용 프로그램 개발 시 "입력"받는 방법
11974정성태7/4/201921107Linux: 22. "Visual Studio Code + Remote Development"로 윈도우 환경에서 리눅스(CentOS 7) C/C++ 개발
11973정성태7/4/201919868Linux: 21. 리눅스에서 공유 라이브러리가 로드되지 않는다면?
11972정성태7/3/201923622.NET Framework: 847. JAVA와 .NET 간의 AES 암호화 연동 [1]파일 다운로드1
11971정성태7/3/201919936개발 환경 구성: 447. Visual Studio Code에서 OpenCvSharp 개발 환경 구성
11970정성태7/2/201918545오류 유형: 552. 웹 브라우저에서 파일 다운로드 후 "Running security scan"이 끝나지 않는 문제
11969정성태7/2/201918992Math: 63. C# - 3층 구조의 신경망파일 다운로드1
11968정성태7/1/201925712오류 유형: 551. Visual Studio Code에서 Remote-SSH 연결 시 "Opening Remote..." 단계에서 진행되지 않는 문제 [1]
11967정성태7/1/201919753개발 환경 구성: 446. Synology NAS를 Windows 10에서 iSCSI로 연결하는 방법
11966정성태6/30/201918676Math: 62. 활성화 함수에 따른 뉴런의 출력을 그리드 맵으로 시각화파일 다운로드1
11965정성태6/30/201919284.NET Framework: 846. C# - 2차원 배열을 1차원 배열로 나열하는 확장 메서드파일 다운로드1
11964정성태6/30/201920844Linux: 20. C# - Linux에서의 Named Pipe를 이용한 통신
11963정성태6/29/201920575Linux: 19. C# - .NET Core Unix Domain Socket 사용 예제
11962정성태6/27/201918278Math: 61. C# - 로지스틱 회귀를 이용한 선형분리 불가능 문제의 분류파일 다운로드1
11961정성태6/27/201917796Graphics: 37. C# - PLplot - 출력 모음(Family File Output)
11960정성태6/27/201918834Graphics: 36. C# - PLplot의 16색 이상을 표현하는 방법과 subpage를 이용한 그리드 맵 표현
11959정성태6/27/201920001Graphics: 35. matplotlib와 PLplot의 한글 처리
11958정성태6/25/201924463Linux: 18. C# - .NET Core Console로 리눅스 daemon 프로그램 만드는 방법 [6]
... 76  77  [78]  79  80  81  82  83  84  85  86  87  88  89  90  ...