본문 바로가기
카테고리 없음

HLSL)아티스트를 위한 URP 셰이더 Shader #10 - UV제어

by 대마왕J 2021. 9. 15.

사실 조금 써놓고 추석이라는 핑계로 귀찮아서 데굴대고 있었는데 

후후후 감사합니다. 손님 
이거 책상에 앉지 않을 수 없군요 

 

날 추석에 책상머리에 앉히려 하는건가! 

자 오늘은 지난 시간 UV 의 HLSL 버전의 시간입니다. 

UV라... 여기서는 이론 설명이 조금 들어가게 되는데, 역시나 이론 설명 자세한건 책을 참고하시고요 (꾸준글)
책을 보신 분들은 UV 가 사실 이거라는거 다 알고 계시죠? 거럼요 을매나 좋은 책인데 당연히 알겠지 ( ...)

쉿 조용 나의 귀여운 아기고양이

 

아참참, R,G,B 랑  X,Y,Z 는 사실 같은거다라는거 말씀드렸죠? 여기에 하나 더 U,V,W 도 같은 말이랍니다. 보통 UV만 쓰지만...  같은건데 어디에 쓰냐 따라서 이름이 다르고 막 이래 

그리고 유니티에서 사용하는 방식은 왼쪽 아래가 float2(0,0) 오른쪽 위가 float2(1,1) 이라고도 책에서 얘기했었죠. 
즉 아래같은 상황일때 

버텍스에는 사실 이런 숫자가 들어가 있는 것이란 말이다... 뭐 그런 거였습니다. 책에 있어요 책에. 비엘북스 출판사 사장님 보고 계십니까.... 제가 이렇게 열심히 홍보하고 있습니다. . 사장님 솔직히 말해서요.. 애초에 출판사 이름이 좀 신경쓰였 ... 아니 아무것도 아닙니다 ... 

정말입니다 

자 오늘은 HLSL이므로, 텍스쳐 한 장 받는 것 부터 시작합니다. 

Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
                return color;
            }
            ENDHLSL
        }
    }
}

오늘도 역시나 다운로드도 있어염 ♥ 이걸로 시작하자고요 

HLSLShaderStudy (1).shader
0.00MB

아 도네 받았으면 이 정도 서비스 할 의욕 당근 생기지요 


UV는 어떻게 생겼나

그래요 뭐 UV는 숫자인가 봐요. 버텍스에 들어가 있고요. 그런가 보다 하는건데... 
근데 뭐... UV  를 눈으로 봐야 .. 사람이 또 믿음이 가고 뭐 그러는거 아니겠어요? 우리가 또 그렇잖아요? 
인간답지 않게 숫자로만 보면... 거 뭐 믿음이 가겠어요? 

그렇다고 유혹중

그래서 정말 UV가 조래조래 되어 있나 확인해 봅시다. 

일단 이 그림을 보면 UV는 XY와 완전 동일한 건데 말입니다... (정말 동일) 

여기서 U , 즉 X 만 생각해 보세요. 자 어서. 
그럼 왼쪽은 0이고 오른쪽은 1이지요? 

숫자는 색이니까, 이걸 색으로 나타내면 다음과 같겠군요 이걸 이번에는 눈으로 확인해 볼께요 
UV의 X 만 출력해 봅시다 
픽셀셰이더에서

IN.uv.x

를 출력하면 되겠군요 

half4 frag(Varyings IN) : SV_Target
{
	half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
	//UV의 U만 출력한다
	return IN.uv.x;
}

오홍. 쿼드 하나 만들고 테스트 해보시면 좀 더 명확하게 알 수 있는데요. 귀찮아서 그냥 있던 공하고 박스 썼습니다. 
보시면 0 ~ 1의 그라데이션 모양으로 U가 표현 가능한 것을 볼 수 있습니다. 
그라데이션 색이 왜이래? 너무 흰 쪽으로 치우친 것 같은데? 하시는 분은 ... 이게 정상이예요 .. 아직 모르시겠는 분은  감마가 어디감마 를 참고하시면 되겠습니다.

이제 UV 중에 V , 즉 XY 중에 Y 를 출력해 보지요 

흠 이번엔 아래가 0이고 위가 1이네요?
즉 이번엔 이걸 출력하려면
픽셀셰이더에서

IN.uv.y

를 출력하면 되겠군요 

half4 frag(Varyings IN) : SV_Target
{
	half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
	//UV의 V만 출력한다
	return IN.uv.y;
}

오올 좋아요. 이번엔 세로로 가네..

자 그럼 X와 Y가 이렇게 생겼다는거니깐...

 

                            X                                            Y

 

이걸 각각 R , G 채널에 넣고, B는 0으로 넣는다면 

return float4(IN.uv.x , IN.uv.y, 0 , 1);

이렇게 겠지요. 리턴은 float4 로 해야 하니까 마지막 알파 자리에 1을 가뿐하게 넣어주고. 

 half4 frag(Varyings IN) : SV_Target
 {
 	half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
 	//UV를 이미지로 출력해 본다 
 	return float4(IN.uv.x , IN.uv.y, 0 , 1);
 }

자 그럼 

후후후 어디서 많이 보던 그림이 나왔지요 
즉 저 칼라의 의미가 UV인 거예요. 왼쪽 아래가 0,0  오른쪽 위가 1,1

색은 숫자고 숫자는 색인거지요 이게 그 얘기 중 하나예요 둘은 같은거


UV 의 타일링(Tiling) 과 이동(Offset)

 UV 의 개념을 알게 되었으니 타일링과 옵셋(이동) 에 대해 알아보도록 하죠 
일단 다시 처음으로 돌아가고 봅시다 

자 저기에 보면 Tiling과 Offset이 있지요? 
저게 사실 어떤 원리로 구현되어 있는지 확인하는 시간입니다. 


이거 뭐 .. 여기서부턴 좀 더 간단해집니다. 

일단 텍스쳐 한 장일때로 돌아가서 생각해 보죠 

half4 frag(Varyings IN) : SV_Target
{
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
  return color;
}

일단 타일링부터, 타일링은 곱셈입니다. 
간단하게 UV에 숫자를 곱해주면 타일링이 동작하지요 

 half4 frag(Varyings IN) : SV_Target
 {
   half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv * 2); //UV에 2를 곱해주면 
   return color;
 }

UV는 float2로 되어 있는 변수이므로, xy에 각각 다른 숫자를 곱해주면 uv에 다른 타일링을 줄 수도 있습니다. 
예를 들어 아래같이 해주면 

half4 frag(Varyings IN) : SV_Target
{
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv * float2(3,1)); //U에 3를 v에 1을 곱해주면 
  return color;
}

U 만 3배 타일링이 된 텍스쳐를 만들 수 있습니다. 

이제 대충 짐작하시겠지만 덧셈을 하면 Offset(이동) 이예요. 여기서부터는 쉽죠?

그냥 위 식을 좀 바꿔서 

half4 frag(Varyings IN) : SV_Target
{
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv + float2(3,0)); //U에 3를 v에 0을 더해주면 
  return color;
}

요렇게 3, 0을 더해주면 (곱해주는건 1이 기본이지만 더해주는건 0이 기본이니까요) 

아무 일도 일어나지 않습니다. 

뭐지 장난해?

자 이건 뭐 간단한거예요. 정수로 이동해서 그래요. 
이동은 1,2,3 처럼 정수로 이동하면 그냥 한 사이클이 돌아버려서 제자리처럼 보이는 것 뿐이예요. 사실은 완벽하게 3 텍스쳐만큼 움직인 것임. 

그러므로 제대로 효과를 보려면 0.5 와 같이 소숫점을 넣어야 합니다. 

half4 frag(Varyings IN) : SV_Target
{
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv + float2(3.4,0)); //U에 3.4를 v에 1을 더해주면 
  return color;
}

흐음.. .좋아요 곱셈은 타일링이고 덧셈은 이동(옵셋) 이라고 했으니까 둘다 동시에 넣어보는것도 재밌겠군요. 
단 이번엔 프로퍼티까지 만들어서요. 
셰이더 그래프는 프리뷰가 있어서 그런거 안만들어도 보기 좋지만, HLSL 코드로 짜려면 역시 프로퍼티로 조정해 보는게 좋지요 

자 이건 과제! 만들어 보세요 

목표로 할 건 이거예요. 

못하겠는 부분이 있다고요? 복습을 할 좋은 기회네요 

정답 풀코드는 다음과 같습니다. 

더보기
Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
        //프로퍼티 추가. 타일은 1이 기본이고 옵셋은 0이 기본인걸 잊지 마세요
        //곱셈과 덧셈의 차이지
        _TileX ("타일U", float) = 1
        _TileY ("타일V", float) = 1
        _OffsetX ("옵셋U", float) = 0
        _OffsetY ("옵셋V", float) = 0
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                //C버퍼에 넣어줘야겠죠
                float _TileX;
                float _TileY;
                float _OffsetX;
                float _OffsetY;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                // 곱하기와 더하기는 서로 뭐 충돌날게 없으니 그냥 주욱 나열해주면 곱하기부터 연산될거예요 
                // 아래 UV가 너무 길다고 생각된다면, 
                // float2 TileOffsetUV = IN.uv * float2(_TileX,_TileY) + float2(_OffsetX, _OffsetY);
                // 처럼 변수를 하나 생성시켜 줘서 따로 계산을 하고 
                // 아래처럼 그 계산한 변수를 넣어주는 방법도 있겠죠 
                // half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, TileOffsetUV);
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv * float2(_TileX,_TileY) + float2(_OffsetX, _OffsetY));
                return color;
            }
            ENDHLSL
        }
    }
}

 


Time을 이용한 흘러가기 

타일링이 곱셈, 옵셋(이동) 이 덧셈이란걸 알고 있다는게 중요합니다. 
이런걸로 뭘 할 수 있냐고요?

이런걸 할 수 있지요 . 바로 내장변수인 _Time을 더해주는 건데요 
UV에 따라 흘러가게 할 수 있어서 유용하게 사용할 수 있답니다. 
_Time은 시간인데요, 유니티가 켜졌을 때 부터 지금까지의 시간이라서 생각보단 큰 수가 들어가 있습니다. 지금 이순간에도 계속 커지고 있는 숫자인 거예요. 

그리고 _Time 변수는 이렇게 써주면 됩니다. 

half4 frag(Varyings IN) : SV_Target
{
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv + _Time.y);
  return color;
}

 

_Time.y 라고 되어 있는게 보이실텐데요 이게 뭐냐면 

https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html

 

Unity - Manual: Built-in shader variables

Built-in shader helper functions Shader data types and precision Built-in shader variables Unity’s built-in include files contain global variables for your shadersA program that runs on the GPU. More infoSee in Glossary: things like current object’s tr

docs.unity3d.com

요렇게 메뉴얼에 나와 있습니다. 
즉 _Time은 float4의 변수이고, xyzw (rgba) 에는 각각 (1/20 초 , 초, 초*2, 초*3) 이 들어가 있다는 거예요. 
궁금하신 분은 _Time.x 나 _Time.z  같은걸 넣어 보시면 알 수 있습니다. 

그럼 여기서 잠깐 퀴즈로 응용 한 번 해 볼까요

1. 흘러가는 속도를 조절해 보세요 
2. 대각선 말고 한 쪽 방향으로 흘러가게 해 보세요 

어렵지 않으니 일단 고민해 보세요 . 답은 아래에! 

 

더보기

1. 흘러가는 속도를 조절하려면 Time에 숫자를 곱해주면 되겠죠 
프로퍼티로 빼주면 더더욱 좋고 

half4 frag(Varyings IN) : SV_Target
{
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv + _Time.y *0.5);
  return color;
}

2. 한 쪽 방향만 흘러가게 하려면 xy 두 쪽이 아니라 한 쪽에다가만 연산해 주면 되겠죠
덧셈이니까 0이 기본. 

half4 frag(Varyings IN) : SV_Target
{
  half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv + float2(_Time.y ,0));
  return color;
}

 

 

 


float4 _BaseMap_ST 와의 관계

여기쯤 되어서 코드로 하시는 분은 의문증이 생길 수 있는데요, 
기껏 타일링과 옵셋을 만들었는데 
여기 이미 만들어져 있는거랑 똑같잖아요?!?!?

네 맞습니다. 완전히 똑같은걸 구현한 거거든요
요게 어디서 구현되어 있냐면.. 
그냥 평범한 텍스쳐 출력하는 코드를 보시면, 

float4 _BaseMap_ST;

를 변수선언하는 부분이 보이실 겁니다. 이 부분이 

이 부분의 값이 들어가는 float4예요 
그리고 UV에다가  

OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);

이 연산하는 부분이 보이실 겁니다. TRANSFORM_TEX 가 사용되네요. 
이 함수는 Macros.hlsl에 디파인으로 정의되어 include되어 있는데요, 

#define TRANSFORM_TEX(tex, name) ((tex.xy) * name##_ST.xy + name##_ST.zw)

요렇게 디파인 되어 있습니다. 텍스쳐에 이름 받아서 _ST 붙여서 xy는 곱하고 zw는 더하는 거 보이시죠?
이 구현을 우리는 수동으로 해준 것 뿐이랍니다. 

Shader "Example/URPUnlitShaderBasic"
{
    Properties
    {
        _BaseMap("BaseMap",2D) = "white"{}
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS   : POSITION;
                float2 uv           : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
                float2 uv           : TEXCOORD0;
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                //요기에서 값을 받아서
                float4 _BaseMap_ST;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                //여기에서 그 연산을 합니다. 
                OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
                return color;
            }
            ENDHLSL
        }
    }
}

 

유후 오늘은 UV의 조작에 대해 알아봤어요. 다음 시간에는 예제를 해 볼까나!!! 
여러분 모두 즐추석되세요 !! 

블로그 주인장에게 커피값을 후원할 수 있습니다! 

donaricano-btn

반응형

댓글