안녕하세요!
이제 슬슬 데모를 공개할 시기가 다가와서, 게임의 완성도를 높이기 위해 여러 작업을 하고 있습니다
이번에는 그 중에서 픽셀 퍼펙트하게 만들기 위해 삽질을 했던 내용에 대해서 써봅니다.

처음에 이 게임을 만들 때에는 픽셀 퍼펙트에 대해서 크게 신경쓰지 않았습니다.
그런데 친구가 픽셀 퍼펙트해야 그림이 괜찮게 나온다고 해서, 같이 좀 알아봤습니다.
조사해보니 픽셀을 처리하는 방식도 종류가 다양했습니다.

완벽한 픽셀 퍼펙트

픽셀 회전은 허용하는 경우

snap은 하지 않은 경우 (배경과 캐릭터가 살짝 어긋나있음)
이런 방식들이 다 각기다른 장단점이 있었습니다.
예를 들면 완벽한 픽셀 퍼펙트는 정지된 상태에서 가장 흠잡을 데 없게 나오고, 가장 레트로느낌이 많이 납니다.

하지만 회전할 때 픽셀이 잘게 부서지는 게 느낌이 안좋았습니다. (사과의 움직임)
이건 아트스타일이나 취향에 따라서 판단해야겠지만, 저희의 경우에는 그냥 픽셀이 돌아가는 게 좀 더 낫다고 결론을 내렸습니다.
그리고 snap의 경우에도 여러 방법이 있었고, 각 방법별로 특징이 달랐습니다.
정리하자면 다음과 같습니다.
1. 강제로 snap시킨 경우

- 딱 원하는 크기의 렌더 텍스처를 만들어서 거기에 렌더링하거나(upscaling), 모든 스프라이트 꼭짓점 좌표를 정확히 픽셀단위로 반올림하는 경우(pixel snapping)
- 완벽하게 픽셀 퍼펙트하지만, 카메라 움직임도 픽셀단위가 됨
- 작은 움직임에도 픽셀이 툭 툭 하고 움직이는 특유의 느낌이 존재
- 레트로느낌의 게임이나, 640x480처럼 고정 해상도 게임에 적합
2. 실제로 snap된 경우 (모든 오브젝트들의 좌표가 픽셀 단위)

- 부드럽게 카메라를 움직일 수 있음 (화면 픽셀단위로)
- 물리를 구현하는 것이 어려움
3. snap을 안하는 경우

- 카메라도 부드럽게 움직이고, 오브젝트도 부드럽게 움직임
- 단점으로는 배경과 캐릭터가 픽셀적으로 어긋나는 glitch 발생
- 카메라가 움직이면서 정지한 오브젝트가 조금씩 왔다갔다하는 문제도 발생(움짤에서 왼쪽 나무)
각 장단점을 생각해보고 결국 snap을 안하기로 결정했습니다.
snap을 할까도 생각했는데, 픽셀단위로 움직임을 계산을 할 엄두가 나지 않았습니다.
그리고 유니티 물리 엔진의 부드러운 느낌도 괜찮다고 생각했습니다.
그렇게 결정하고 생각해보니 정지한 물체는 딱히 물리적인 움직임이 없다는 것을 깨닫고, 정지한 물체의 경우에만 따로 pixel snapping을 해서 왔다갔다 하는 문제를 해결했습니다.
대충 이런 결정을 했지만, 아직 몇가지 해결해야할 문제가 남아있었습니다.

픽셀 퍼펙트에 관련돼서 알아볼 때, 구글링 하면서 찾아보기도 하고 유니티 패키지도 공부해봤지만, 특히 엔터 더 건전을 많이 참고했습니다.
정말 연구해볼 수록 픽셀 관련 디테일한 처리에 놀랐습니다. 특히, 캐릭터의 모든 물리적인 움직임을 픽셀단위로 계산해서 snap하는 걸 알고는 충격을 받았습니다.
저희가 중점적으로 본 것은 해상도 옵션쪽인데, Scaling Mode를 제공해서 원하는 그래픽으로 세팅할 수 있습니다.
이런 옵션을 준 이유는, 컴퓨터 모니터 크기가 제각각이기 때문에 완벽한 비주얼을 표현하는 게 불가능하기 때문일 것입니다.
그래서 장단점을 비교하면서 유저가 선택할 수 있도록 한 거죠.
이런 옵션들을 정리해보면 다음과 같은 상황으로 나뉘게 됩니다.
1. 엔터 더 건전의 아트 픽셀(480x270)의 정수배 크기의 모니터의 경우(1920x1080,1440x810, ...)는 그냥 모니터 해상도로 세팅하면 완벽하다.
2. 그렇지 않은 경우가 대부분인데, 그럴 때는
- 픽셀 퍼펙트를 원한다면, 출력 범위를 조정해 픽셀 퍼펙트하게 만든다. (단, 레터박스가 생김)
- 좀 blurry해도 화면이 꽉 찬 걸 원하면, 렌더링은 적절하게 하고 그걸 화면크기에 맞도록 늘린다.
왜 이렇게 할 수밖에 없었는지를 이해하려면 우선 다음 개념을 이해해야합니다.
[아트 이미지]

표현하고자 하는 도트라고 보시면 됩니다. 엔터 더 건전의 경우 480x270, 저희 게임의 경우 320x180으로 미리 고정으로 보통 잡아둡니다.
결국 이 이미지를 예쁘게 확대해서 보여주는 테크닉이 중요합니다.
[스프라이트 Pixel Per Unit]

스프라이트는 결국 유니티 월드에 존재하게 됩니다.
이 때, 스프라이트 1픽셀이 월드에서 얼마만큼 클지를 세팅해줘야합니다.
기본값이 100PPU입니다. 즉, 100픽셀이 길이 1인 셈입니다.
이건 게임에 따라 정하기 나름인 것 같습니다.
저희는 타일 하나의 크기를 0.5로 두려고 타일이 18픽셀이니 36PPU로 세팅하고 있습니다.
[카메라 orthogonal size]

카메라가 담고 있는 월드의 y길이가 orthogonal size입니다.
스크린 비율에 따라 카메라는 자동으로 x길이를 조절합니다.
그러니 이 orthogonal size를 잘 계산해서 월드를 딱 맞게 담아야 합니다.
y기준으로 맞추고 싶다면 orthogonal size = H / PPU 이고,
x기준으로 맞추고 싶다면 orthogonal size = W / PPU * screenHeight / screenWidth 가 됩니다. (W는 아트 픽셀의 너비, H는 아트 픽셀의 높이)
그럼 만약에 스크린 사이즈와 상관없이 무조건 아트 이미지의 비율대로 표현하고 싶다면 어떨까요?
그건 카메라의 rect값을 조절해서 레터박스를 두면 됩니다.
카메라가 스크린에 투사하는 두 꼭짓점 min, max는 기본적으로 (0,0), (1,1)입니다.
만약 스크린이 4:3이고, 아트 픽셀이 16:9라면 (0, 0.125) (1, 0.875)로 잡으면 딱 맞게 됩니다.
만약 레터박스를 최대한 피하고 싶다면, 아트 픽셀 크기에 최소, 최대 크기 여유를 두는 방법이 있습니다.
[카메라 Back Buffer]

카메라는 Back Buffer(framebuffer)라고 불리는 렌더 텍스쳐에 월드를 렌더링을 하게 됩니다.
그러니 만약 Back Buffer 텍스쳐의 사이즈가 아트 이미지 크기와 정확히 일치한다면, 일단 픽셀 퍼펙트 한 것입니다. (다른 문제는 제쳐두고 생각했을 때)
그럼 Back Buffer 사이즈가 아트 이미지의 2배라면 어떨까요?
이 때도 완벽하게 픽셀 퍼펙트합니다. (이미지 참고)
그럼 만약에 1.5배라면 어떨까요?
픽셀이 깨지게 됩니다.
왜냐하면 한 픽셀에는 한 색깔정보밖에 저장하지 못하기 때문입니다.
그렇기에 레스터라이저에 의해 스케일링되는 과정에서 불규칙하게 픽셀이 튈 수밖에 없는 것입니다.
(사실 수학적인 공식에 따라 규칙적으로 픽셀을 스케일링하는 것이지만, 픽셀아트는 복잡한 이미지기 때문에 깨지는 것처럼 보이게 된다는 표현이 더 정확)
이를 좀 더 직관적으로 이해하자면 그래픽 프로그램을 통해 비슷한 상황을 재연할 수 있습니다.
중간에는 깨지다가 마지막에 정확히 2배가 되면서 정상적으로 보입니다.

잡담이 길었지만 결론은 Back Buffer 사이즈는 아트 이미지 크기의 정수배여야 된다는 점입니다.
Screen.SetResolution함수를 통해 사이즈를 정할 수 있습니다.
[스크린 픽셀]
back buffer에 담긴 픽셀 정보는 결국 모니터를 통해 표현됩니다.
그리고 모니터 스크린은 고유 해상도(1920x1080, 2560x1440, ...) 픽셀로 이루어져 있습니다.
여기서 저희는 처음에 모니터 해상도와 Back Buffer의 크기가 안맞을 때 뭔가 문제가 발생할 거라고 생각했습니다.
그런데 막상 구현하고 보니, 모니터 해상도 크기가 Back Buffer크기의 정수배가 아니더라도 괜찮았습니다.
오히려 이 단계에서 신경써야하는 것은 아트 이미지의 크기입니다.
아트 이미지가 모니터 픽셀에 담길 때, 정수배가 아니라면 아까 위와 같은 문제가 발생하게 됩니다.
물론 모니터 해상도가 워낙 높다보니 별 티는 안나지만, 뭔가 선명하고 날카로운 느낌이 사라지게 됩니다.

작게 놓고 보면 알기 힘들지만, 큰 화면으로 보면 좀 더 제대로 느낄 수 있습니다.
그래서 이런 것들을 통해 간략화해서 다음과 같이 UI를 구성했습니다.

이렇게 만들고 좀 한숨 돌리나 했더니 또 다른 문제가 발생했습니다.
바로 특정 해상도에서 이상하게 픽셀이 늘어지는 현상이 발견됐습니다.

왜 이런 현상이 일어나는 건지 이해가 가지 않았습니다.
이것저것 스프라이트 옵션도 바꿔보고 카메라 옵션도 바꿔보면서 테스트를 해봤지만, 문제가 해결되지 않았습니다.
픽셀 계산할 때 반올림을 잘못했나 싶어서 코드다 다 헤집어봤지만 결과는 변하지 않았습니다.
그러던 중, 놀랍게도 해상도 너비 픽셀 수를 홀수로 하니까 정상적으로 작동했습니다.
확대 레벨이 홀수 일 때는, 해상도 너비 픽셀 수도 홀수로,
확대 레벨이 짝수 일 때는, 짝수로 세팅을 하니 문제가 사라졌습니다.

이 불가사의한 일을 겪고 저와 제 친구는 한동안 충격에서 벗어나지 못했습니다.
일단 돌아는 가서 만족하지만, 여러 기기에서 테스트를 좀 해봐야될 것 같습니다.
혹시 이 문제에 대해서 아시는 분이 있다면 댓글로 알려주시기 바랍니다.
읽어주셔서 감사합니다!
-------------이전글---------------
1편 소개
2편 괴물
3편 약탈자
4편 파편시스템
5편 대쉬퍼즐
6편 브금소개
7편 스프라이트 순서 처리
9편 대화 시스템
10편 그래픽 리소스 제작과정
11편 레벨 프로토타입 영상
12편 지형 시스템
13편 버려진 레벨들
14편 무기 시스템
15편 룬 시스템
16편 폭탄 퍼즐 레벨
17편 몬스터 제작 과정
18편 모바일 최적화 삽질기
19편 특수효과 발전과정
20편 트리거 시스템
--------------------------
댓글 영역
획득법
① NFT 발행
작성한 게시물을 NFT로 발행하면 일주일 동안 사용할 수 있습니다. (최초 1회)
② NFT 구매
다른 이용자의 NFT를 구매하면 한 달 동안 사용할 수 있습니다. (구매 시마다 갱신)
사용법
디시콘에서지갑연결시 바로 사용 가능합니다.