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

비밀번호

댓글 작성자
 




... 16  17  18  [19]  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13147정성태10/25/20224760오류 유형: 823. Visual Studio 2022 - Unable to attach to CoreCLR. The debugger's protocol is incompatible with the debuggee.
13146정성태10/24/20225590.NET Framework: 2060. C# - Java의 Xmx와 유사한 힙 메모리 최댓값 제어 옵션 HeapHardLimit
13145정성태10/21/20225855오류 유형: 822. db2 - Password validation for user db2inst1 failed with rc = -2146500508
13144정성태10/20/20225698.NET Framework: 2059. ClrMD를 이용해 윈도우 환경의 메모리 덤프로부터 닷넷 모듈을 추출하는 방법파일 다운로드1
13143정성태10/19/20226222오류 유형: 821. windbg/sos - Error code - 0x000021BE
13142정성태10/18/20224937도서: 시작하세요! C# 12 프로그래밍
13141정성태10/17/20226708.NET Framework: 2058. [in,out] 배열을 C#에서 C/C++로 넘기는 방법 - 세 번째 이야기파일 다운로드1
13140정성태10/11/20226076C/C++: 159. C/C++ - 리눅스 환경에서 u16string 문자열을 출력하는 방법 [2]
13139정성태10/9/20225909.NET Framework: 2057. 리눅스 환경의 .NET Core 3/5+ 메모리 덤프로부터 모든 닷넷 모듈을 추출하는 방법파일 다운로드1
13138정성태10/8/20227189.NET Framework: 2056. C# - await 비동기 호출을 기대한 메서드가 동기로 호출되었을 때의 부작용 [1]
13137정성태10/8/20225585.NET Framework: 2055. 리눅스 환경의 .NET Core 3/5+ 메모리 덤프로부터 닷넷 모듈을 추출하는 방법
13136정성태10/7/20226159.NET Framework: 2054. .NET Core/5+ SDK 설치 없이 dotnet-dump 사용하는 방법
13135정성태10/5/20226403.NET Framework: 2053. 리눅스 환경의 .NET Core 3/5+ 메모리 덤프를 분석하는 방법 - 두 번째 이야기
13134정성태10/4/20225134오류 유형: 820. There is a problem with AMD Radeon RX 5600 XT device. For more information, search for 'graphics device driver error code 31'
13133정성태10/4/20225452Windows: 211. Windows - (commit이 아닌) reserved 메모리 사용량 확인 방법 [1]
13132정성태10/3/20225321스크립트: 42. 파이썬 - latexify-py 패키지 소개 - 함수를 mathjax 식으로 표현
13131정성태10/3/20227982.NET Framework: 2052. C# - Windows Forms의 데이터 바인딩 지원(DataBinding, DataSource) [2]파일 다운로드1
13130정성태9/28/20225090.NET Framework: 2051. .NET Core/5+ - 에러 로깅을 위한 Middleware가 동작하지 않는 경우파일 다운로드1
13129정성태9/27/20225386.NET Framework: 2050. .NET Core를 IIS에서 호스팅하는 경우 .NET Framework CLR이 함께 로드되는 환경
13128정성태9/23/20227948C/C++: 158. Visual C++ - IDL 구문 중 "unsigned long"을 인식하지 못하는 #import파일 다운로드1
13127정성태9/22/20226409Windows: 210. WSL에 systemd 도입
13126정성태9/15/20227011.NET Framework: 2049. C# 11 - 정적 메서드에 대한 delegate 처리 시 cache 적용
13125정성태9/14/20227202.NET Framework: 2048. C# 11 - 구조체 필드의 자동 초기화(auto-default structs)
13124정성태9/13/20226948.NET Framework: 2047. Golang, Python, C#에서의 CRC32 사용
13123정성태9/8/20227387.NET Framework: 2046. C# 11 - 멤버(속성/필드)에 지정할 수 있는 required 예약어 추가
13122정성태8/26/20227397.NET Framework: 2045. C# 11 - 메서드 매개 변수에 대한 nameof 지원
... 16  17  18  [19]  20  21  22  23  24  25  26  27  28  29  30  ...