본문 바로가기
Shader

5주차, Vertex Color를 주자.

by JDCM 2020. 10. 8.

좀 더 깔끔하게 보고 싶으신 분들은, 구글 도큐먼트 링크를 추천합니다.

docs.google.com/document/d/1BlYdxFLroX1PqUpn2FHSuM82q_5KBEKciCW4Hob5wGg/edit?usp=sharing

 

5주차, Vertex Color를 주자.

5주차, Vertex Color를 주자. 왜 한 주 쉬었나요? 휴강이었습니다. 행복한 가정의 달 보내세요! 4주차 돌아보기 텍스처와 UV를 불러올 정도가 되었고, Time과 Alpha를 가볍게 살펴볼 수 있었다. ( 흐르는 �

docs.google.com

 

5주차,

Vertex Color를 주자.

 

1. 4주차 돌아보기

텍스처와 UV를 불러올 정도가 되었고, Time과 Alpha를 가볍게 살펴볼 수 있었다. 

 

( 흐르는 가짜 물 정도야 이제 껌이지! )

 

사실 이제는 공식보다는 실습에 가까운 쉐이더 체험을 하고 있으므로

저번 시간에서 되돌아 봐야 할 점은 사실 간단하다.

  • Alpha를 적용하는 법

  • UV를 불러오고 조작(왜곡)하는 법

  • Time 변수 적용하는 법

굉장히 간단하고, 직접 다시 보는 것이 좋으므로 기억이 나지 않는다면

… 4주차의 문서는 ( 여기 ) 를 보면 좋겠다.

 

그럼 오늘은 무엇을 배울까?

( https://sulleox.artstation.com/projects/3NDLB )

유니티 안에 적용되는 모든 오브젝트에는 ‘버텍스’가 있다.

그리고 그 버텍스 안에는 값들이 적용되어 있다.

Position(좌표), UV(Texcoord), Color(색), 노말(Bump), 인덱스(Index) 등 …

이와 같이 다양한 값이 들어간 버텍스 안에서 오늘 우리는 Color(색)을 볼 예정이다.

 

2. Vertex Color(버텍스 컬러)

1. 일단 Vertex Color 알아보기

 

( https://islanddelta.blogspot.com/2015/01/outdoor-assets-update.html )

 

버텍스는 각각 고유의 색이 있고 기본으로 주어지는 색은 (1, 1, 1, 1)이다.

버텍스 컬러는 Max에서 적용하거나, 또는 엔진 자체에서 적용할 수도 있다.

(여담으로, 버텍스 컬러를 찾는 과정에서 https://www.slideshare.net/ssuserbd3117/vertex-color 해당 문서를 즐겁게 읽었다.)

 

우리는 맥스를 배우는 것이 아니라 엔진을 배우고 있으니, 엔진에서 적용해보자.

 

 

폴리 브러쉬(Polybrush)는 유니티 Window -> Package Manager에서 다운 받을 수 있다. 기본적으로 제공하지 않으므로 따로 받아줘야만 한다.

또한 Polybrush로 적용하고 나서 Polybrush 기능을 삭제하거나 닫게 된다면 Polybrush를 적용한 값을 볼 수 없으므로 유의하도록 하자.




Polybrush를 받으면 Tools에 들어가서 켜주도록 하자.

 

( 낯선 툴이다… )

 

당황하지 않고 상단의 물들고 있는 세모 모양을 누르면 Polybrush를 적용할 수 있는 툴이 나온다. Plane을 꺼내어 색을 칠해보도록 하자.

 

 

그리고 기대되는 마음으로 빨간색을 열심히 칠해보지만 … 나오지 않을 것이다.

(날 또 속이다니!)

 

왜 나오지 않는가 하면 유니티는 자체적으로 Texture만 보여주도록 쉐이더가 적용되어 있기 때문에, 버텍스 컬러를 따로 보기 위한 쉐이더가 필요하다.

 

멋진 쉐이더로 그 얼굴을 낱낱이 봐주도록 하자.

 

 

버텍스 컬러라는 것도 결국 버텍스의 값, 즉 엔진에서 가지고 있는 값을 요청해야 하므로 Input에서 float4를 통해 Color(색)을 요청해야 한다. (색은 기본적으로 float4로 생각해야 한다.)

Input에 요청해서 가져 왔다면 IN.을 통해서 Input 안의 color 값을 가져오고 Albedo 에 적용하게 되면 그제서야 버텍스 컬러가 정상적으로 나온다.

 

근데 Input에서 Color를 불러올 때 왜 옆에 똑같이 : Color를 했나요?

 

  (번외) 시맨틱(Semantic)이 뭐야?


( 해당 문서를 참고하였다. https://docs.microsoft.com/ko-kr/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics?redirectedfrom=MSDN )


시멘틱(Semantic)이란 “의미의, 의미론적인”이란 뜻이다.

프로그래밍적 언어(특히 HTML5)에서는 “태그”라고 부르기도 한다. 


시맨틱이란 간단히 말하자면 마트 내의 분류 표시라고 보면 좋다.

만약 우리가 어린 아이에게 햇반을 가져오라고 시킨다면 알아서 찾아! 가 아니라… 즉석식품란의 햇반 목록에 있는 일반 백미 햇반을 가져오게 한다는 것과 비슷한 이치다.



컴퓨터는 무척이나 단순하므로, 버텍스 컬러를 가져오는 과정에서도 특정 “주소”를 명칭해줘야 할 필요가 있다. 시맨틱은 일종의 그 주소를 나타내고 값을 불러올 수 있는 경로를 명칭하는 단어라고 볼 수 있다.

 

아무튼 버텍스 컬러가 잘 그려지는 것이 눈에 보인다. 그럼 버텍스 컬러를 응용해보자.

 

2.  Vertex Color 응용하기

( 근데 땅따먹기라도 하나요? 왜 색을 나누죠?)

 

버텍스 컬러가 RGBA 값으로 들어가는 것은 알겠는데, 이걸 어떻게 쓸 수 있느냐가 관건이다. 문제는… 우리가 일전부터 해왔던 강의 중 문득 Lerp가 기억에 남는다.

 

0~1값, 즉 흑백으로 나타나는 Alpha를 통해 두 텍스쳐를 교묘하게 섞곤 했다.

만약 이 것을 RGBA 값으로도 할 수 있다면…? 

 

※ 텍스처를 넣어보기 전에 꼭 유의해야 할 점이 있다. ※

일전의 Lerp 때처럼 1의 값이 들어간 흰 부분에서는 텍스처가 적용되지 않았기 때문에 값을 전부 초기화 (0의 값, 검정)으로 만들어야 한다. 1 값을 넣어놓고 “왜 안 나와” 하는 멍청한 짓은 피해보도록 하자.

 

( RGB 다 따로 볼 수도 있다. )

 

0으로 전부 초기화 했다면, 마음대로 RGB 컬러로 Plane 위를 문질러 본 뒤에 텍스처 4개를 적용시켜 보도록 하자. 그리고 RGBA를 따로 볼 수 있었던 만큼, Lerp 함수를 통해 텍스처와 RGBA을 보간할 수 있을 것이다.

 

 

가볍게 Emission을 통해서 각 값을 순차적으로 lerp하여 값을 넣어보았다.

그럼 main이 깔린 Texture 위로 R, G, B 에 따라 tex1, 2, 3이 해당 값에 맞춰져 위치하게 된다.

 

그럼 내가 장난친 값의 결과를 얻는다. (...)

물론, 극단적으로 장난을 했기 때문에 결과물이 별로일 뿐이지 실제로 RGB를 잘 다듬어주면 … 

 

 

Plane의 폴리곤이 굉장히 적어서 테두리가 뚜렷하게 나오지는 않았지만 대강 어떻게 하면 예쁘게 나올 수 있을지는 예상할 수 있는 형태가 나오기 시작했다.

 

참고로 lerp함수를 이용하지 않아도 해당 형태의 텍스쳐를 만들 수 있다.

 

  (번외) Lerp함수 안 쓰고 Lerp 시켜보기


결국 Lerp도 흑백이 나뉘어진 텍스쳐의 0~1 사이의 값에 맞추어 텍스처를 적용하는 방식이기 때문에 결국 RGB가 다 합쳐진 흑백 텍스처 하나를 만들고 각 위치에 맞는 값만 적용해주면 된다.

그럼 우선 RGB가 다 합쳐진 흑백 텍스처는 어떻게 만들 수 있을까?

생각보다 간단하다. 결국 RGB도 0~1 사이의 값이기 때문에 전부 더해주면 된다.


    (IN.color.r + IN.color.g + IN.color.b);


하지만 이렇게 되면 RGB가 들어가는 위치의 값에 1이 들어간 상태가 되어있으니 그걸 반전(One Minus) 해줄 필요가 있다.


    1 - (IN.color.r + IN.color.g + IN.color.b);


반전을 한 텍스처를 이제 기본 베이스에 곱하고(0을 곱하면 0이고, 1을 곱하면 1이 나오게 되므로! 더해주는 순간 1 이상의 값을 볼 수 있다.) 이제 각각 RGB에 맞는 값만 넣어주면 된다.


o.Emission = main * ( 1 - (IN.color.r + IN.color.g + IN.color.b) )

  • (tex2 * In.color.r ) + (tex3 * In.color.g ) + (tex4 * In.color.b );


결과는 상단의 텍스처와 동일할 것이다!



2.  Normal 적용하기

(이제부터 Normal한 인생을 살아보자.)

 

Normal Map도 결국은 하나의 텍스처다. 그러나 우리가 지금까지 써왔던 텍스처처럼 Normal Map도 쓰는 방식이 있다. 일단 텍스쳐를 넣는 방식으로 보도록 하자.

 

    Properties

{ …

_MainTex4("Albedo (RGB)", 2D) = "white" {}


_NormalMap("Normal", 2D) = "bump" {}

}

SubShader

{…

sampler2D _MainTex4;


sampler2D _NormalMap;

       struct Input

        {...

        float2 uv_MainTex4;


        float2 uv_NormalMap;

        };


  void surf (Input IN, inout SurfaceOutputStandard o)

        {...


float4 nor = tex2D(_NormalMap, IN.uv_NormalMap);

o.Normal = nor;

}

ENDCG...

 

이렇게 코드를 작성한 뒤에 노말이 먹히겠지?! 하며 기쁜 마음으로 저장을 하면…

 

 

원하는 노말은 커녕 Console 창에 오류 문구만 남긴다.

 

우선 오류는 두 개다.

  1. o.Normal 은 float4가 들어가지 않는다. float3만 들어간다.
    그러므로 특정 함수인 “UnpackNormal”을 넣어주어야 한다. (이건 외우자!)

  2. 콘솔 창에 올라간 오류 말 그대로 max 8개의 텍스처만 쓸 수 있는데 텍스처가 9개가 넘어간다! 며 불만을 표한다. 그리곤 맨 뒤에 츤데레처럼 “try adding”이라고 써두었다...
    콘솔창 말대로 #pragma target 3.0을 작성해보자.
    (#pragma target 3.0을 지우는 순간 2.0으로 작업되므로 옛날 기기(정말 고대 수준에 가깝다…)에선 돌아가지 않는다.)

 

이 두가지를 수정하게 되면 우리가 원했던 노말이 나오게 된다.




아직 뚜렷하지 않지만 적용은 되고 있다!

 

노말을 적용하기 위해서 우리는 UnpackNormal을 사용하였다.

그리고 이 쉐이더 코드는 총 3가지로도 나타낼 수 있다.

 

  1. float4 nor = tex2D(_NormalMap, IN.uv_NormalMap);
    o.Normal = UnpackNormal(nor);

    상단에서 쓴 방식이다. 말 그대로 Normal Map을 정식적으로 불러오고 UnpackNormal에 대입하였다.

  2. o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));

    상단의 두 코드를 합친 방식이지만 o.Normal에 직접적으로 들어가므로 수정하기 어려운 단점이 있다.

  3. float3 Nor = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
    o.Normal = Nor;

    o.Normal에 따로 들어갈 수 있도록 설정하였다.

 

3번의 방식이 왜 좋나면, Nor 변수를 통해 Normal 값을 변형시킬 수 있기 때문이다.

만약 Nor 값에 이런 방식으로 넣는다고 생각해보자. 

 

Nor = float3 (Nor.r * 10, Nor.g * 10, Nor.b);

 

 

그럼… 엄청나게 Normal을 조정할 수 있다!

( + 왜 B에 안 넣는지를 설명하려면 굉장히 많은 시간이 걸리기 때문에 우리는 우선 B는 건드리지 않는다! 를 전제로 하기로 하자.)



Normal을 지금 하나 넣었고, RGB마다 다르게 넣지 않았기 때문에 하나의 Normal로 모든 텍스쳐가 적용되고 있다. 

 

각각 RGB마다 다르게 해줄 수 있겠지만 스케일이 얼마나 커질지(...) 알 수 없는 일이다.

일단 배운 것까지 응용이라도 해보자!




흐르는 강물을 거슬러 오르는 연어의 기분을 느끼고 싶었지만 실패했다.

그러나 강물 Normal을 따로 주고 흘러갈 수 있게 했음에 기쁘다!

 

작업하면서 가장 의문이었던 점은

  • Surface Shader에서도 Random 값이 있는지, 저 강물에 Random 값을 줄 수 있는지가 궁금했다. (시간이 부족해서 시도하지 못했다.)

  • Normal과 Texture만이 끝이 아닐텐데 Properties의 형태가 변하거나 또는 잘 정리할 필요가 있다고 느꼈다. (나중에 Height Map이나 Occlusion, Metallic 등 ... 따로 넣을 땐 어떻게 될건지 궁금하다.)
    왜냐면 결국  #pragma target 4.0을 쓸 정도로 텍스쳐가 많이 쓰였기 때문이다.

  • 만약 지금 내가 한 것처럼 물과 꽃 사이와 같은 어색한 점이 있으면 AO처리를 어떻게 할까?



이하는 코드 및 주석이다!

그리고 도중 #pragma target 3.0 에 대해 알아본 정보이다.

 

솔직히 수정이 많이 필요할 것 같다. (...)



그리고 #pragma target 3.0에도 한계가 있음을 알아내었다. (혼남 당해서)

 

문득 target 3.0부터 5.0까지 얼마만큼의 텍스쳐를 받아들일 수 있고 얼마만큼의 기종이 좋아야 할지도 궁금해졌다.

 

왜냐면 2.0은 거의 흐접(!) 이라고 하셨으니까…






( https://docs.unity3d.com/kr/2018.4/Manual/SL-ShaderSemantics.html )  

 

인터폴레이터가 왜 뜬금없이 나오냐면은,

 

인터폴레이터( interpolator , 補間器 ) … “보간기”이다.

보간? 어디서 많이 들어보았다 … 보간… 보...가...rp… Le...rp… Lerp이다.

 

즉 Lerp를 쓸 수 있는 텍스쳐가 무려 4.0은 32개가 된다!

근데 나는 3.0을 쓰면서도 텍스쳐 4장, 노말 2장에서… 노말 하나 더 들여오는 과정에서 막혔다.

 

이 부분은 솔직히 잘 모르겠다. 최대 10개라고 하면서 왜 6장은 되고, 7장은 안되는지 알 수가 없었다.
나중에 더 시험해보기로 했다.

 

'Shader' 카테고리의 다른 글

7주차, 빛을 커스텀 해보자.  (1) 2023.02.09
6주차, 빛을 알아보자.  (0) 2023.01.30
4주차, UV를 다뤄보자.  (0) 2020.10.05
3주차, 함수 조작을 배워보자.  (0) 2020.09.17
2주차, 쉐이더 코드를 배워보자.  (1) 2020.09.15