Unity/Rendering&Shader

[SHADER] Texture Channel Swap

김성인 2023. 8. 29. 21:13

 보통 2D, 3D 할 것 없이, 게임 내 오브젝트를 표현하기 위해서는 최소 한 장의 "색을 표현" 하는 텍스처가 필요하다. 물론 텍스처를 사용하지 않는 게임도 존재하지만, 어디까지나 보통의 게임은 필수이다.


 컴퓨터 그래픽스에서 색은 RGB 채널을 이용하고, 추가한다면 Alpha 채널이 붙는다. 그리고 이 채널 3, 4개가 보통 하나의 이미지 파일이 된다.


 요즘 오픈하는 게임을 보면 색 이외에도, 쇠를 더욱더 쇠같이, 가죽을 더욱 가죽같이 보이도록 여러 가지 기능들이 추가되어 있는데, 이런 기능들을 위하여 더욱 많은 수의 텍스처를 필요로 하게 되었다.


 셰이더로 이런 재질들을 만들어 주더라도, 그 재질이 들어가는 범위를 효율적으로 관리하기 위하여, 마스킹 텍스처를 사용하는 것이 일반적이다.


 이런 여러 이유들로 인하여 하나의 오브젝트에 여러 장의 텍스처가 필요로 하게 된다.


 2D Sprite를 이용하여 Pixel art 풍의 게임을 개발할 때, 플레이어의 헤어, 눈썹, 눈, 의상 등에 염색 시스템이 들어가게 되었고, 2D Sprite의 특성상 모든 동작을 이미지에 도트로 찍다 보니, 그 이미지 한 장의 사이즈만으로도 엄청난 용량을 가지게 되었고, 염색 부위를 마스킹 해주기 위해서는 같은 용량의 텍스처가 최소 한 장 더 필요하게 되어, 해결 방법을 고민하게 되었고, 지금부터 이야기할 내용이 바로 이 방법이다.


 먼저 진행 중인 작업물의 상황을 파악해 보았다.


1. Pixel Art이기 때문에, 도트로 이미지를 제작하였고, Texture Type을 Sprite로 이용 중이다.

2. 이미지의 무손실을 위하여 PNG 포맷을 이용 중이다.

3. 투명도가 존재하지 않고, 오직 불투명하게만 제작한다.

4. 장비나 얼굴 등의 색을 변경하는데, 파츠가 이미지로 나뉘지 않고, 한 장의 Sprite에 파츠 분할 없이 존재한다.

5. 4번의 상황에 이미지 변경이 아닌, 색 변경만 할 것이다.


 위의 상황을 풀면, Alpha 채널의 Alpha 정보를 RGB 채널 쪽에서 담당하고, Alpha 채널에 파츠를 명시(파츠의 영역 표시) 하면 해결될 것 같다.


 위의 계획을 구현 하기 위하여 PNG 파일을 포토샵에서 열어 보면, Alpha 채널을 확인 할 수 없을 것이다.  여기서 문제가 되는데, 포토샵에서 Alpha 채널을 새로 만들어 PNG 포멧으로 저장하여도 절대 Alpha 채널이 변경 되지 않는 다는 것이다.  이것을 해결하는 방법은 보통의 PNG가 아닌, Super PNG를 사용해야 한다.  Super PNG를 이용하게 되면 Alpha 채널을 확인 할 수 있게 된다.


 여기까지 해서 Alpha 채널을 확인할 수 있게 되었지만, 여전히 Alpha 정보를 어떻게 다른 RGB 채널로 옮길지에 대해서는 이론상 불가능하다. Alpha 채널에서도 0, 1로 된 숫자로 데이터를 판별(테스트)하는데, 만약 RGB 채널 모두 판별(테스트)하게 되면 해결될 것이라는 생각이 떠올랐다. 실제로 오래된 게임들의 패키지를 뜯어보면 투명한 부분이 핑크색으로 보이던 것도 떠올랐다. 그땐 분명 메모리의 한계로 Alpha 채널을 사용하지 않았던 것 같다. 그렇다면 가장 흔하게 사용되지 않는 핑크 색인 R 100, G 0, B 100 값을 Alpha 채널에서의 0이라고 지정하였다.


 R 100, G 0, B 100 값은 Alpha 값으로 사용할 수 있을지, Shader를 짜보았는데, 생각이 맞았다. 곧바로 실 작업을 진행하는 사람들과 의견을 나눠 보니 다행히 핑크색은 사용하지 않고, 앞으로도 마찬가지였다. 이렇게 순식간에 Alpha 채널의 0을 구했다. 1은 더 쉽다. 0 이 아니면 모두 다 1로 취급하면 된다.


 이 부분을 수식화하면 1 - (R * B) * (1 - G)이다. 이 수식에서 0이 나올 때만 Alpha 처리가 되고, 이 수식은 (1, 0, 1)일 경우에만 0이 나오게 된다.


 다음으로 채널 하나에 마스킹 정보를 모두 넣는 것인데, 24비트 컬러(트루컬러)를 사용할 수 있는 PNG 포맷은 각 채널에 8비트, 즉 256을 표현할 수 있다. 0을 빼더라도 255개의 데이터를 담을 수 있는 그릇이다. 이 방식을 이용하면 총 255개의 파츠, 마스킹을 사용할 수 있는 것이다.


 이것을 Shader로 만들어 보자. 

 

 이제부터 UNITY의 Shader Graph를 이용하여 시각적으로 확인하기 쉽도록 진행하겠다.


 계획대로 작업을 진행하기 위하여 이미지부터 준비를 해보자.  두 가지가 필요 한데, RGB 채널에 Alpha 값을 0으로 대신할 값인 (1, 0, 1)을 칠해준다.  그다음, Alpha 채널을 생성하여 마스킹 할 영역을 칠해준다.  여기까지 진행하면 아래와 같은 이미지가 나올 것이다.

 투명한 부분은 모두 핑크색(1, 0, 1)으로 채워질 것이고, Alpha 채널은 채널 확인으로만 가능하다.

 

 그다음으로 PNG 포맷이 아닌, Super PNG로 이미지를 저장해야, Alpha 채널과 RGB 채널 모두 정상적으로 저장이 된다.  그리고 Super PNG로 저장한 데이터는 뷰어 등으로 확인할 때, Alpha 채널에 마스킹 데이터가 있지만, 뷰어는 이 의도를 알 수 없기 때문에, 이상한 이미지로 보여줄 것이지만, 실제로는 잘 저장되어 있을테니 걱정하지 않아도 된다.


 Alpha 데이터를 조건화할 수식인 1 - (R * B) * (1-G)를 Shader Graph에서 만들어 주면 아래와 같다.

 그렇지만 위의 수식으로는 Alpha 처리해야 하는 부분은 모두 걷어 낼 수 있지만, 그렇지 않은 부분에는 아티팩트가 생길 수 있다. 그렇기 때문에 Step 함수를 이용하여 아티팩트를 걷어 냈다.

부분적으로 (1,1,1)이 아닌 경우가 생긴다.

  Step 노드를 넣어 주게 되면 좌측과 같은 데이터가 우측과 같이 원하는 방향으로 깔끔하게 된다.  만약 이 과정을 거치지 않을 경우, 부분적으로 아티팩트가 생길 수 있다.


 위의 과정으로 Alpha 채널의 정보가 RGB 채널에서 잘 작동하는지 확인해 보면 하단의 이미지와 동일하게 매우 잘 나오는 것을 확인할 수 있다.

 이제 Alpha 채널에 마스킹 정보를 넣기 위하여 Super PNG로 포토샵의 Alpha 채널에 단계(포토샵에서는 편하게 흰색에 가까워지니 밝기라고 생각해도 좋을 것 같다)를 다르게 하여 칠해준다.


 다음으로 이 단계(밝기)를 판별해야 하는데, 이 역시 Step 노드를 이용하여 검출 해낸다. Step(x, y) 함수는 x 값이 y보다 작거나 같으면 1을, 그렇지 않으면 0을 리턴한다. 이것을 이용하여 각 단계의 값을 추출한다. 채널에서 나오는 값은 기본적으로 0에서부터 1로 나오기 때문에, 소수점으로 검출하지만, 정수로 검출하고 싶다면, 0 ~ 1 값에 255를 곱하면 정수로 검출 할 수 있지만, 여기서는 파츠의 수가 2개일 뿐이라서 이 과정을 넣지 않았다.

 Step 함수를 이용하여 값을 판별할 때, 하위 값은 상위 값까지 포함한다는 단점이 있기 때문에, 하위 값에 상위 값을 빼주는 것을 잊지 말자.


 만약 이 부분을 빼고 진행한다면 상위 값을 변경할 때, 하위 값도 함께 변경된다.

위의 과정을 거치지 않을 경우, 상위 값 때문에, 하위 값까지 변경된다.

최종 노드는 이렇게 구성된다.


 위의 방식을 진행하게 되면, 문득 더욱 효율적인 방법이 떠올 수 있을 것이다. 나 같은 경우에도, Alpha 채널의 0 ~ 255 값 중, 0과 1로 Alpha 정보를 기록하고 나머지 2 ~ 255로 마스킹 하는 방법과 0 ~ 128 정도를 Alpha 정보로 하여 투명도를 추가하고, 나머지 수를 마스킹 용도로 사용하는 방법 등, 여러 가지 방법을 생각해 낼 수 있다.


 그리고 RGB 채널을 (1, 0, 1)로 하여 Alpha Test로 이용한다면, Alpha 채널 없이, Cut Off를 이용할 수 있다.


추가로 이어서 작업을 진행 한 부분이 있는데, 마스킹 값으로 Color를 대체해버리는 방식을 이용하거나, 마스킹 영역만큼 이미지에 Color를 변경하게 된다면, 단색 or 특정 색에서는 색 변경이 예쁘게 되지 않게 되기 때문에, 마스킹 영역에 한하여서만 원본 이미지를 모노톤으로 변경 후, 색을 다시 입혀 주는 방식으로 진행하였다.

마스킹 영역이 단색으로 되어 버린 경우
간단하고 빠르게 모노톤으로 변경하는 노드 // (R + G + B) / 3
모노톤으로 변경한 이미지

 최종적으로 이렇게 원하는 결과물을 얻을 수 있었다.


 조건만 잘 정한다면 얼마든지 RGB 채널만으로, Alpha 정보와, Masking 정보를 모두 포함 시킬 수 있다.


사용된 도트 이미지는 ToYou님께서 제작하였습니다.