void PostList()

날아다니는 나비 만들기



몇가지 일이 계속 쌓여서 미루고 미루다 드디어 글을 마무리 짓는군요.

버텍스 셰이더에서의 정점 위치 오프셋 조정과, Alphatest를 이용해 나비의 날갯짓을 만들어보았습니다.

< 준비물 >

1 ) 텍스쳐


먼저 나비텍스쳐가 필요합니다. 저는 http://pngimg.com/download/1037 에 포스트된 알파값이 포함된 나비 이미지를 사용했습니다.


2 ) Mesh


이번 나비 셰이더에서 사용하기 위해 평면 Mesh 를 만들었습니다.
이때 텍스쳐 매핑을 고려해서 Mesh의 정면과 나비의 머리가 동일한 방향에 위치하도록 UV를 설정하는 것이 좋습니다.


Blender 나 3dsmax 등으로 직접 Mesh를 제작하지 않더라도, 엔진을 사용할 경우 기본 Mesh 몇가지를 제공합니다.


이 때 Unity Quad 의 경우 Plane 과 달리 Local XY 평면으로 서있는 형태를 띕니다.
보통 게임 오브젝트의 정면 방향을 +Z 축 방향으로 잡는데, 이 정면 방향으로 나비 오브젝트를 움직이려고 한다면 나비가 벌떡 서서 날아가는 모양새가 되겠죠.


보통 우리가 나비가 날아가는 모습을 볼때 서서 날아가는 모습을 보지는 않았을 겁니다.

그러므로 눕혀져있는 Plane을 사용하거나 눕혀져있는 Quad Mesh 를 사용하는 것이 적절할 것 같습니다.

< 애니메이션 >

날개짓에 사용할만한 그래프 형태의 경우 구글 검색창에 Math Dance 같은 키워드로 검색해보면 몇가지 아이디어를 찾을 수 있습니다.

다음은 몇가지 그래프 예시입니다.
x값은 texcoord의 x좌표, y값은 버텍스가 이동할 높이, t 는 시간값을 대입하면 됩니다.


밑의 그래프를 통해 나비를 정면에서 보았을때 나비의 날갯짓이 어떤 모양을 가질지를 연상하시면 좋습니다.





단순히 2차방정식을 응용한 식을 사용해도 될것이고



혹은 절대값x + t 에 sin 을 씌워 위와 같은 날갯짓 모양을 만들 수도 있을 것입니다.



이때 나비의 몸통에서부터 날개로 갈수록 점점 더 많이 팔랑거리게 만들고 싶다면 위 식에서 abs(x) 를 추가로 곱해 위 모양을 만들수도 있을 것입니다.

< 셰이더 작성하기 >

1) Alphatest

알파 컷아웃 셰이더는 주로 불투명한 오브젝트를 그려야 하는데 이 오브젝트가 투명한 부분과 불투명한 부분이 구분되어있을 경우 사용합니다.

이는 구멍이 뚫린 철망이나 잡초같은 것들을 렌더링 해야 할 때를 예로 들 수 있습니다.

블랜딩을 통해 알파 계산을 하지 않고 알파 컷아웃을 사용할 경우 블랜딩 연산을 수행하지 않아 더 가볍고, 또 반투명 셰이더 같은 곳에서 주로 발생하는 렌더링 순서에 따른 결과의 변화 또한 일어나지 않는다는 장점이 있습니다.

유니티 셰이더에서 알파 컷아웃을 사용하기 위해 먼저 SubShader 블럭에서 다음 태그를 추가합니다.

Tags {  "RenderType" = "TransparentCutout" "Queue" = "AlphaTest" "IgnoreProjector" = "True" }

다음 프로퍼티에서 _Cutoff float 변수를 추가합니다.
이는 그리고자 하는 픽셀의 알파값이 얼마 이상일때만 그릴것인지 판정하기 위해 사용합니다.

_Cutoff("Alpha cutoff", Range(0,1)) = 0.5

다음 셰이더 전처리기에서 alphatest:_Cutoff 옵션을 추가하면 됩니다.

#pragma surface surf Lambert alphatest:_Cutoff

* 참조 *

또는 alphatest:_Cutoff 전처리기를 사용하지 않고 픽셀셰이더에서 직접 처리하는 방법도 있습니다.

CG, GLSL, HLSL 상관없이 셰이더 언어에서는 clip(x) 함수를 제공합니다.
clip 함수는 매개변수로 입력된 x 가 0보다 작을 경우 ( x < 0 ) 현재 픽셀을 폐기해버립니다.
만약 clip( 현재픽셀의 알파값 - 0.5 ); 를 호출한다면 알파값이 0.5 미만이 될 경우 모두 버려버리게 되겠죠.

clip(o.Alpha - _Cutoff);

만약 픽셀 셰이더에서 clip 함수를 사용해서 알파테스트를 처리할 경우 되도록이면 빨리 호출하는 것이 좋습니다. 빨리빨리 알파 검사를 해서 걸러낼 수 있는 픽셀들을 걸러내면 이후 연산을 줄일 수 있어서 그렇습니다.

Clip 함수에 대한 서술은 아래 링크를 참조하시면 좋습니다.
https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-clip

> 뒷면 그리기


나비 셰이더를 설정한 재질은 Mesh 에 연결한 다음 테스트를 해보면 앞면에서는 나비가 잘 보이는데 뒤에서 보면 나비가 투명해져서 안보이는 것을 확인할 수 있을 것입니다.

이는 기본적으로 셰이더에서 컬링 옵션을 만지지 않을 경우 기본적으로 뒷면을 컬링하도록 동작하기 때문입니다.
따라서 셰이더의 SubShader 블럭에서

Cull Off

를 작성하여 컬링을 비활성화 해주어야 합니다.


그러면 뒷면에서 나비를 보거나, 또는 애니메이션을 진행할 경우 앞 뒷면이 문제없이 제대로 나오는 것을 볼 수 있습니다.

컴퓨터 그래픽스에서의 컬링에 대한 설명은 다음 링크를 참조할 수 있습니다.
https://en.wikipedia.org/wiki/Back-face_culling

> 그림자 이슈


이 단계에서 나비 텍스쳐를 재질에 연결하고 그림자를 드리우게 해보면 위 이미지와 같이 그림자가 나비 모양을 받지 않고 사각형으로 드리우는 것을 확인할 수 있습니다.

#pragma surface surf Lambert addshadow alphatest:_Cutoff

이는 셰이더 전처리기에서 addshadow 구문을 추가하는 방법으로 해결할 수 있습니다.


유니티 서페이스 셰이더에서의 관련 내용은 아래 링크의 Shadows and Tessellation 항목을 참조하시면 좋습니다.
https://docs.unity3d.com/Manual/SL-SurfaceShaders.html

2) Vertex Animation

먼저 유니티 표면 셰이더에서 버텍스 셰이더를 사용하기 위해선 vertex:(버텍스 셰이더 이름) 을 전처리기에서 사용하면 됩니다. 여기서는 날개 애니메이션을 위한 셰이더이기에 winganim 이라는 이름으로 임의로 설정하였습니다.

#pragma surface surf Lambert addshadow alphatest:_Cutoff vertex:winganim

그리고 버텍스 셰이더에서 사용하는 정점 데이터를 정의하기 위한 appdata를 선언하고 winganim 버텍스 셰이더를 작성합니다.

struct appdata {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 color : COLOR;
    float4 texcoord : TEXCOORD0;
};

void winganim(inout appdata v)
{
    float2 value = v.texcoord.xy;
    float dist = distance(value.x, 0.5); // 나비의 몸통부분과 현재 텍스쳐 좌표 사이의 거리는?

    v.vertex.xyz +=
        v.normal *
        sin(dist + _Time.y)
        * dist; // 각 버텍스를 노멀방향으로 밀어내자. y = sin(abs(x-0.5) * time ) * abs(x-0.5);
}

위 버텍스 셰이더는 각각의 버텍스에 대해 기존 위치에서 노멀 방향으로 일정량씩 이동시키는 방식을 사용하고 있습니다.

여기서는이 식을 사용하였습니다.
여기서 abs(x) 에 들어갈 x 값은 기준점 x 와 비교값 y 의 차이값이 들어가야할 것입니다.

이때 그래프에서 abs(x) 의 경우 두 값 사이의 거리로 취급할 수 있습니다.
따라서 abs(x) 는 셰이더 함수 distance( x, 비교 값 ) 로 대체할 수 있습니다.


위 식에서 value 값은 현재 픽셀의 texcoord 좌표( UV좌표) 를 의미합니다.
현재 사용하고 있는 텍스쳐 기준으로 나비의 몸통이 UV 좌표계의 x축을 기준으로 0.5 에 위치하고 있으니, 현재 픽셀이 몸통으로 부터 얼마나 떨어져있는지를 비교하기 위해선 distance( value.x , 0.5 ) 를 사용하면 될 것입니다.


여기까지 진행하면 픽셀 셰이더에서 별다른 수정을 하지 않더라도 다음과 같은 나비 비주얼을 구현할 수 있습니다.

이제 다음은 프로퍼티를 통해 나비의 날개 펄럭임 속도, 세기등을 조절할 프로퍼티를 추가하면 되겠지요.

_Speed("Speed",Range(0,50)) = 1.0
_Amplitude("Amplitude",Range(0,5)) = 1

예를 들어 프로퍼티에 다음과 같이 속도나 진폭에 해당하는 변수들을 추가하고

v.vertex.xyz +=
    v.normal *
    sin(dist + _Time.* _Speed)
    * dist * _Amplitude; 

sin 식 안에 speed 를 곱해 속도를 조절하고, 외부에 amplitude 를 곱해 진폭, 날개가 얼마만큼 크게 펄럭이는지를 조절할 수 있을것입니다.


추가로 나비날개의 팔랑거림을 더 주고자 한다면

_XAmplitude("AmplitudeX",Range(0,5)) = 1
_YAmplitude("AmplitudeY",Range(0,5)) = 1
_YFrequency("YFrequency",Range(1,50)) = 1

x축과 y축에 대한 진폭 프로퍼티를 구분하고, y 축 기준으로의 진동수를 조절하는 프로퍼티를 추가한 다음,

void winganim(inout appdata v)
{
    float2 value = v.texcoord.xy;
    float dist = distance(value.x, 0.5);// 나비의 몸통부분과 현재 텍스쳐 좌표 사이의 거리는?

    v.vertex.xyz +=
        v.normal *
        sin(dist + _Time.* _Speed)
        * dist * _XAmplitude; 
    // 각 버텍스를 노멀방향으로 밀어내자. y = sin(abs(x-0.5) * time ) * abs(x-0.5);

    v.vertex.xyz +=
        v.normal *
        sin(value.y * _YFrequency + _Time.* _Speed )
        * dist * _YAmplitude;
    // 나비 날개의 팔랑거림을 더 주기위해 텍스쳐좌표 y축 기준에 대해서도 연산을 진행하자.
}

UV 좌표 y축에 대해서도 추가적인 연산을 진행하는 방법을 사용할 수 있을 것입니다.
y축 기준으로 연산할 때 x 축 기준에서의 몸통과의 거리 비교값을 그대로 사용하는 이유는
날개와 몸통이 같이 팔랑거리는걸 막는게 첫번째고, 두번째는 몸통과 멀어질수록 더 팔랑거리게 하기 위함입니다.






< 여기까지의 코드 >
Shader "Custom/ButterflyWingAnimation"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Speed("Speed",Range(0,5)) = 1.0
        _XAmplitude("AmplitudeX",Range(0,5)) = 1
        _YAmplitude("AmplitudeY",Range(0,5)) = 1
        _YFrequency("YFrequency",Range(1,10)) = 1
        _Cutoff("Alpha cutoff", Range(0,1)) = 0.5
    }
    SubShader
    {
        Tags {  "RenderType" = "TransparentCutout" "Queue" = "AlphaTest" "IgnoreProjector" = "True" }
        Cull Off
        
        LOD 200


        CGPROGRAM
        #pragma surface surf Lambert addshadow alphatest:_Cutoff vertex:winganim
        #pragma target 3.0

        sampler2D _MainTex;
        float _Speed;
        half _XAmplitude;
        half _YAmplitude;
        half _YFrequency;

        struct Input
        {
            float2 uv_MainTex;
        };

        fixed4 _Color;

        struct appdata {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
            float4 color : COLOR;
            float4 texcoord : TEXCOORD0;
        };

        void winganim(inout appdata v)
        {
            float2 value = v.texcoord.xy;
            float dist = distance(value.x, 0.5);// 나비의 몸통부분과 현재 텍스쳐 좌표 사이의 거리는?

            v.vertex.xyz +=
                v.normal *
                sin(dist + _Time.* _Speed)
                * dist * _XAmplitude; 
            // 각 버텍스를 노멀방향으로 밀어내자. y = sin(abs(x-0.5) * time ) * abs(x-0.5);

            v.vertex.xyz +=
                v.normal *
                sin(value.* _YFrequency + _Time.* _Speed )
                * dist * _YAmplitude;
            // 나비 날개의 팔랑거림을 더 주기위해 텍스쳐좌표 y축 기준에 대해서도 연산을 진행하자.
        }

        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

________________________________________________________________________

< 테셀레이션 >



위 이미지를 보면 알 수 있듯이 버텍스 셰이더에서 애니메이션을 수행할 때,
해당 Mesh 의 버텍스가 많을수록, 그리고 세밀하게 배치되어 있을수록 더 부드러운 애니메이션을 구현할 수 있습니다.


하지만 CPU 메모리에서 GPU 메모리로 정점 데이터를 넘길 때의 대역폭, 즉 데이터가 넘어가는 속도가 존재하기에, 너무 많은 정점 데이터를 넘기게 되면 모든 버텍스가 VRAM 으로 넘어가는 것에 대한 지연시간이 발생해 GPU 퍼포먼스에 영향을 주게 됩니다.

( 현대 PC 하드웨어 기준으로 버텍스가 만 몇개 정도씩 넘긴다고 해서 퍼포먼스에 치명적인 영향을 주지는 않는다고 하지만 최적화를 고려한다면 줄여서 나쁠 것은 없습니다. )

이에 대한 해결 방법은 너무 많은 버텍스를 넘기지 않고 적당량만큼만 넘기는 것입니다.


하지만 버텍스를 많이 넘긴 것만큼 디테일을 표현하고 싶을 때가 있죠,
예를 들어 버텍스 셰이더에서 날갯짓 애니메이션을 수행할때 뻣뻣한 날개가 아니라 팔랑팔랑거리는 날개를 표현하고자 하는 것처럼 말이죠.

그러면 CPU 에서 처음부터 많은 버텍스를 넘겨주지 말고, 상대적으로 적은 버텍스 수를 받은 다음 GPU에서 버텍스를 늘려서 사용하는 방법을 사용하면 될 것입니다.

DX11 이후부터 지원하기 시작한 테셀레이션 기능은 GPU로 주어진 기하구조를 세분화 해서 더 작은 삼각형들로 분할하여 더 많은 버텍스를 만들어내는 동작을 수행합니다.

즉 버텍스 4개로 이루어진 사각형 패치 하나만 넘기더라도 테셀레이션을 수행하면 패치를 원하는 만큼 나누어 하이 폴리곤 패치로 만들어내는데 사용할 수 있습니다.

테셀레이션과 관련된 내용은 아래 링크를 참조하시면 좋습니다.
https://kr.nvidia.com/object/tessellation_kr.html

유니티에서 테셀레이션 사용하기

유니티 엔진 표면 셰이더에서 테셀레이션을 사용하기 위해선 CG 코드블럭 내에서 다음 코드를 작성해야합니다.

#pragma target 5.0
#include "Tessellation.cginc"

먼저 셰이더에서 테셀레이션을 사용하기 위해선 하드웨어가 셰이더 모델 5 이상을 지원해야합니다. 그리고 유니티에서 제공하는 테셀레이션 함수를 사용하기 위해선 "Tessellation.cginc" 파일을 인클루드해야합니다.

#pragma surface surf Lambert addshadow alphatest:_Cutoff vertex:winganim tessellate:tessEdge

그리고 전처리기에서 tessellate:(테셀레이션 함수의 이름) 을 작성하고

float4 tessEdge(appdata v0, appdata v1, appdata v2)
{
    return UnityEdgeLengthBasedTess(v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
}

다음과 같이 테셀레이션 함수를 작성하면 됩니다.
이번 셰이더에서 사용한 테셀레이션 함수는 모서리 길이 기반 테셀레이션을 사용하였습니다.

_EdgeLength("Edge length", Range(0,250)) = 5

UnityEdgeLengthBasedTess 함수를 사용할 경우 테셀레이션 함수 인자로 정점 3개와 한 모서리의 길이가 얼마나 되어야하는지를 결정하는 실수 하나를 주어야 합니다.

이때 모서리 길이를 프로퍼티를 통해 조절하기 위해 실수 하나를 정의해주면 좋습니다.



유니티 셰이더에서의 테셀레이션 사용은 아래 링크를 참조하시면 좋습니다.
https://docs.unity3d.com/Manual/SL-SurfaceShaderTessellation.html


____________________________________________________________________________________

< 유니티 파티클 시스템과 연동하기 >

이제 나비 셰이더를 완성하였으니 이 셰이더를 활용하면 날아다니는 나비를 표현할 수 있을 것입니다.

이때 여러마리의 나비가 날아다니는 게임 씬을 구현하고자 할 때 나비 한마리 한마리를 각각 오브젝트로 만들어 스크립트로 제어하는 방법을 사용할 수 있겠지만,
나비가 게임 플레이에 영향을 주지 않고 비주얼 적인 표현만을 위해 사용한다면 나비들을 개별 오브젝트로 관리하지 말고 파티클 시스템을 활용하는 것이 더 나은 선택일 수 있습니다.

1 ) Particle System 설정

파티클 시스템에서 파티클 모듈들을 설정하는 것은 하기 나름입니다.
무엇을 표현하고자 하는지, 어떤 연출을 하고자 하는가에 따라 설정값이 달라집니다.

여기서는 나비 셰이더를 파티클 시스템에 적용시키기 위한 최소한의 과정을 서술합니다.


1. Particle System 의 Renderer 모듈에서 Render Mode 를 Mesh 로 설정합니다.
2. Mesh 를 위에서 만든 누워있는 Quad, 또는 유니티 기본 Plane 으로 설정합니다.
3. Material 을 나비 셰이더를 연결한 재질로 설정합니다.
4. Render Alignment 를 Velocity 로 설정합니다.


그러면 수많은 나비들을 파티클 시스템을 이용해서 날려볼 수 있습니다.
하지만 여기까지 진행한 후 이리저리 시험하다보면 몇가지 문제점을 느낄 수 있습니다.

1. 파티클 시스템에서 나비의 색상을 아무리 바꾸려고해도 나비의 색상이 바뀌지 않습니다.
이는 파티클 시스템에서의 색상이 셰이더에선 정점의 버텍스 컬러로 전달되기 때문입니다.
2. 모든 나비의 날갯짓이 동일하게 펄럭입니다. 각각의 나비가 개별적인 날갯짓을 하도록 만들려면 추가적인 작업이 필요합니다.

2 ) Vertex Color 와 Custom Data & Vertex Stream

struct Input
{
    float2 uv_MainTex;
    float4 color : COLOR;
};

먼저 표면 셰이더에서 보간된 버텍스 컬러를 얻어오기 위해 Input 구조체에 COLOR 시멘틱이 붙은 float4 color 변수를 선언합니다.

void surf(Input IN, inout SurfaceOutput o)
{
    fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * (IN.color + _Color);
    o.Albedo = c.rgb;
    o.Alpha = c.a;
}

다음 표면 셰이더에서 Input 구조체로부터 받아온 color 변수를 색상 계산에 반영하면 파티클 셰이더에서 설정한 색상을 셰이더 계산에 반영시킬 수 있습니다.


색상을 변경할 수 있게 되었으니 다음은 각각의 나비가 다른 날갯짓을 하도록 만들 차례입니다.


CustomData 모듈을 사용하면 파티클 시스템에서 사용하기 위한 커스텀한 실수를 최대 8개까지 설정할 수 있습니다.
이번에는 날갯짓을 할때 어떤 모습에서 시작할지를 랜덤으로 설정하기 위한 실수와
날갯짓의 속도를 랜덤으로 설정하기 위한 실수와 마지막으로 날갯짓의 진폭, 세기를 랜덤으로 설정하기 위한 실수로 사용하기 위해 모드는 Vector 모드로, 그리고 Number of Components 는 3으로 설정하였습니다.



그다음 파티클 시스템의 Renderer 모듈에서 Custom Vertex Streams 를 활성화 한 후 나비 셰이더에서 작성한 appdata 와 Custom Vertex Streams 을 똑같이 만들어 준 후 Custom Data 를 사용하기 위해 Custom 항목을 추가해줍니다.
여기서는 3개의 컴퍼넌트를 사용하니 Custom1.xyz를 사용하면 됩니다.

struct appdata {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 color : COLOR;
    float4 texcoord : TEXCOORD0;
    float3 customdata : TEXCOORD1; // custom data
};

나비 셰이더의 appdata 또한 Vertex Stream 과 일치시켜야 하기 때문에 customdata 를 추가한 후 시멘틱을 붙여주면 됩니다. 시멘틱의 경우 파티클 시스템의 Custom Vertex Streams 항목 각각의 뒤에 대문자로 적혀있는 것을 사용한다고 생각하시면 됩니다.

   v.vertex.xyz +=
        v.normal *
        sin(dist + _Time.* (_Speed + v.customdata.y) + v.customdata.x)
        * dist * (_XAmplitude * v.customdata.z);

다음 버텍스 셰이더의 appdata 로부터 커스텀 데이터를 읽어온 후 각각을 셰이더 연산에 반영하면 됩니다.

유니티 파티클 시스템과 버텍스 스트림에 대한 자세한 내용은 아래 링크를 참조하면 좋습니다.
https://docs.unity3d.com/kr/current/Manual/PartSysVertexStreams.html



> Custom Data 사용 이후 발생할 수 있는 문제

CustomData 를 사용하도록 셰이더를 고치고 나면 파티클 시스템에서는 의도한 대로 그려지던 재질이 개별 오브젝트에서는 의도한 대로 그려지지 않고 이상하게 그려지는 일이 발생할 수 있습니다.

이를 방지하는 방법은

[Toggle(USECUSTOMDATA)] _UseCustomData ("Use Custom Data",Float) = 1

Toggle 프로퍼티를 추가한 뒤

#pragma shader_feature USECUSTOMDATA

전처리기를 통해 shader_feature 또는 multi_compile 을 활용해 키워드를 정의하고

#ifdef USECUSTOMDATA
    v.vertex.xyz +=
        v.normal *
        sin(dist + _Time.* (_Speed + v.customdata.y) + v.customdata.x)
        * dist * (_XAmplitude * v.customdata.z);
#else
    v.vertex.xyz +=
        v.normal *
        sin(dist + _Time.* _Speed)
        * dist * (_XAmplitude );
#endif
    v.vertex.xyz +=
        v.normal *
        sin(value.* _YFrequency + _Time.* _Speed)
        * dist * _YAmplitude;

위와 같이 분기를 나누어 커스텀 데이터 사용 여부에 따라 셰이더가 다른 연산을 수행하도록 만들 수 있습니다.


재질 프로퍼티를 보면 다음과 같이 토글의 형태로 커스텀 데이터를 사용할 지 말지 여부를 설정할 수 있습니다. 이후엔 커스텀 데이터를 사용하는 재질과 사용하지 않는 재질을 각각 하나씩 만들어 상황에 따라 다른 재질을 연결하여 사용하면 문제를 해결할 수 있습니다.

Toggle 과 관련된 내용은 MaterialPropertyDrawer 문서를 참조하면 더 자세한 내용을 확인할 수 있습니다.
https://docs.unity3d.com/ScriptReference/MaterialPropertyDrawer.html
< 전체 코드 >
Shader "Custom/ButterflyWingAnimationParticle"
{
    Properties
    {
        _Color("Color", Color) = (1,1,1,1)
        _MainTex("Albedo (RGB)", 2D) = "white" {}
        _Speed("Speed",Range(0,5)) = 1.0
        _EdgeLength("Edge length", Range(0,250)) = 5
        _XAmplitude("AmplitudeX",Range(0,5)) = 1
        _YAmplitude("AmplitudeY",Range(0,5)) = 1
        _YFrequency("YFrequency",Range(1,10)) = 1
        _Cutoff("Alpha cutoff", Range(0,1)) = 0.5
        [Toggle(USECUSTOMDATA)] _UseCustomData ("Use Custom Data",Float) = 1
    }
        SubShader
        {
            Tags {  "RenderType" = "TransparentCutout" "Queue" = "AlphaTest" "IgnoreProjector" = "True" }
            Cull Off
            LOD 200
            CGPROGRAM
            #pragma surface surf Lambert addshadow alphatest:_Cutoff vertex:winganim tessellate:tessEdge
            
            #pragma shader_feature USECUSTOMDATA

            #pragma target 5.0
            #include "Tessellation.cginc"

            sampler2D _MainTex;
            float _EdgeLength;
            float _Speed;
            half _XAmplitude;
            half _YAmplitude;
            half _YFrequency;

            struct Input
            {
                float2 uv_MainTex;
                float4 color : COLOR;
            };

            fixed4 _Color;

            struct appdata {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 color : COLOR;
                float4 texcoord : TEXCOORD0;
                float3 customdata : TEXCOORD1; // custom data
            };

            float4 tessEdge(appdata v0, appdata  v1, appdata  v2)
            {
                return UnityEdgeLengthBasedTess(v0.vertex, v1.vertex, v2.vertex, _EdgeLength);
            }

            void winganim(inout appdata v)
            {
                float2 value = v.texcoord.xy;
                float dist = distance(value.x, 0.5);// 나비의 몸통부분과 현재 텍스쳐 좌표 사이의 거리는?

            #ifdef USECUSTOMDATA
                v.vertex.xyz +=
                    v.normal *
                    sin(dist + _Time.* (_Speed + v.customdata.y) + v.customdata.x)
                    * dist * (_XAmplitude * v.customdata.z);
                // 커스텀 데이터를 사용할 경우 이 블럭의 코드가 실행된다.
            #else
                v.vertex.xyz +=
                    v.normal *
                    sin(dist + _Time.* _Speed)
                    * dist * (_XAmplitude );
            #endif
                // 각 버텍스를 노멀방향으로 밀어내자. y = sin(abs(x-0.5) * time ) * abs(x-0.5);
                v.vertex.xyz +=
                    v.normal *
                    sin(value.* _YFrequency + _Time.* _Speed)
                    * dist * _YAmplitude;
                // 나비 날개의 팔랑거림을 더 주기위해 텍스쳐좌표 y축 기준에 대해서도 연산을 진행하자.
            }

            void surf(Input IN, inout SurfaceOutput o)
            {
                fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * (IN.color + _Color);
                o.Albedo = c.rgb;
                o.Alpha = c.a;
            }

            ENDCG
        }
            FallBack "Diffuse"
}

P.S.


극단적인 예시지만 이런 나비 폭풍 연출도 가능할 것입니다.
프레임 레이트를 추가적으로 잡을 방법이 필요하겠군요.

댓글

이 블로그의 인기 게시물

Array Modifier 로 오브젝트를 원형으로 나열하기

회전 루프 애니메이션 만들기 ( Graph Editor F-Curve Modifiers )

원형으로 뚫려있는 구멍을 토폴로지 흐름에 맞게 채우기.