커뮤니티를 하다보면 도트 게임을 큰 모니터에 띄웠는데 선이 어딘지 울퉁불퉁해 보였다는 얘기를 가끔 접하는데요. 이건 그래픽이나 그림이 뭉개지는 문제는 아닙니다.
640×360짜리 원본을 1600×900 화면에 키우면 원본 칸의 딱 절반은 화면 픽셀 3개로, 나머지 절반은 2개로 그려집니다. 같은 도트인데 어떤 칸만 1.5배 두껍다는 얘기죠. 참고로 해당 문장에서 일그러진다는 표현은 그림 파일이 깨진다는 뜻이 아니라 화면에 그려진 도트의 폭이 고르지 않게 보인다는 뜻입니다. 이게 어느 해상도에서 얼마나 생기는지 궁금해서 직접 세어봤으니 아래에서 확인해보시길 바랍니다.
세는 방법부터 정하기
원본 한 줄을 최근접 이웃 방식으로 키우면서, 칸 하나가 화면 픽셀 몇 개를 차지하는지 하나씩 세어봤는데요. 배율은 화면 비율을 지킨 채 들어가는 최대 배율, 키운 영역의 크기는 소수점 버림입니다.
원본은 320×180, 480×270, 640×360 세 가지, 화면은 1280×720부터 3840×2160까지 제가 임의로 고른 12가지였습니다. 대표적인 것들을 고르려고 했을 뿐, 실제로 어떤 해상도가 많이 쓰이는지는 따로 확인하지 못했습니다.
추가로, 코드 하나만 믿기는 불안함이 컸는데요. 그래서 파이썬 이미지 라이브러리(PIL)로 640×360 이미지를 12개 해상도에 정말로 키워 놓고 칸 폭을 한 번 더 세어보았습니다.
굵은 칸 개수까지 전부 일치하더라고요. 다만 실제 엔진은 확대 필터나 렌더 타깃 방식이 제각각이라 결과가 다를 수 있고, 스크롤할 때 생기는 타일 틈은 이번에 다루지 않았다는 한계가 있습니다.
같은 화면인데 배율은 3개
1600×900 모니터를 예로 들면, 원본이 320×180일 때는 정확히 5배입니다. 480×270이면 3.33배, 640×360이면 2.5배가 되죠. 모니터는 그대로인데 원본을 뭘로 잡았느냐에 따라 깔끔하기도 하고 지저분하기도 합니다. 12개 해상도 중 정수 배율로 딱 떨어진 건 320×180이 8개, 640×360이 7개, 480×270이 4개였습니다.
표를 훑다가 1920×1200이 정수로 나온 게 이상해서 계산을 다시 따져 봤습니다. 16:10 화면이라 위아래에 검은 막대가 생기고, 그러면 가로 배율 3배만 남는 구조였죠. 2560×1600도 같았고, 3440×1440은 좌우에 막대가 생기면서 세로 배율 4배가 남았습니다.
사견이지만 이 결과에서 제일 의외였던 건 480×270의 4개입니다. 1080p의 정확히 4분의 1이라 딱 맞는 선택 같은데, 정수로 떨어진 건 1080p와 4K, 그리고 16:10 두 개뿐이었습니다. 1440p(5.33배)에서는 굵은 칸이 33.3%, 1366×768(2.84배)에서는 84.4%나 됐습니다.
창모드는 거의 항상 걸린다
전체 화면이 아니라 창으로 띄우면 결과가 더 나빠집니다. 창 가로 폭을 1000에서 3840 사이로 무작위로 7만 8천 번 뽑아(세로는 16:9로 맞췄다) 640×360 원본을 키워 봤습니다. 정수 배율로 딱 떨어진 창은 0.17~0.18%뿐이었죠. 난수 시드를 세 가지로 바꿔 봐도 숫자는 크게 흔들리지 않았습니다(최대 0.4%p 차이). 열 개 중 다섯 개 남짓(54.5~54.9%)은 굵은 칸이 얇은 칸보다 3분의 1 이상 두꺼웠습니다.
배율이 작을수록 한 픽셀의 무게가 커집니다. 2픽셀과 3픽셀은 50% 차이지만 4와 5는 25%, 8과 9는 12.5%입니다. 배율이 작은 해상도에서 어긋남의 비율이 더 큰 건 이 때문이라고 할 수 있습니다. 사람 눈에 얼마나 거슬리는지까지는 재지 않았고, 폭의 비율만 계산했습니다.
정수로 줄이면 남는 여백
그럼 정수 배율만 쓰면 되지 않나 싶은데, 그쪽에도 값이 있습니다. 640×360을 1366×768에서 2배(1280×720)로 쓰면 화면의 12.2%가 비고, 1600×900에서는 36.0%, 1680×1050에서는 47.8%가 빕니다. 거의 절반인 셈입니다.
SDL 포럼에 2023년에 올라온 질문이 있는데요. 48×48 스프라이트를 360×360으로 키웠더니 위치에 따라 결과가 조금씩 달라진다는 내용이었는데, 답변 중에는 360이 48로 나누어떨어지지 않아서 일부 픽셀 크기가 달라지는 거라는 설명이 있었습니다. 해결책으로 나온 일곱 가지 중 정수 배율을 강제하는 방법은 픽셀이 균일해지는 대신 화면 가장자리에 검은 여백이 생긴다고 적혀 있었죠.
유니티의 Pixel Perfect Camera 설명서에도 비슷한 선택지가 있습니다. 기준 해상도에 맞추고 남는 영역을 검은 막대로 채우는 Crop Frame 옵션인데요. SDL에는 정수 배율을 강제할지 정하는 함수가 아예 따로 있었습니다. 같은 포럼의 다른 답변은 정수 배율로 먼저 키운 다음 쌍선형으로 화면 크기에 맞추는 두 단계 확대를 제안했는데, 흐림은 줄지만 메모리와 성능이 더 든다고 말했습니다.
굵은 칸과 여백을 한 장으로
두 가지 비용을 한 장에 나란히 놓아보았습니다. 왼쪽은 640×360 원본의 왼쪽 12칸이 화면 픽셀 몇 개가 됐는지, 오른쪽은 정수가 아닌 해상도 5개에서 꽉 채웠을 때와 정수로 줄였을 때의 비용입니다.

1440×900은 굵은 칸이 25.0%, 여백이 28.9%로 여백 쪽이 더 컸고, 1366×768은 13.3%와 12.2%로 거의 비슷했습니다. 1680×1050은 굵은 칸이 62.5%, 여백이 47.8%까지 올라갔습니다. 두 막대는 분모가 다릅니다(칸 수와 화면 면적). 같은 저울에 올리는 건 거친 비교입니다. 그래도 해상도가 원본 배율에서 멀어질수록 어느 쪽 비용이든 커지는 방향은 분명했습니다.
결국 나눗셈의 문제였다
도트 게임이 어떤 모니터에서 일그러져 보이는 건 그림이 잘못돼서가 아닙니다. 화면 픽셀 수를 원본 칸 수로 나눴을 때 나머지가 남느냐의 문제였습니다. 나머지가 0이면 모든 칸의 폭이 같고, 나머지가 남으면 그 수만큼의 칸이 한 픽셀씩 굵어집니다. 1600×900이 원본에 따라 5배도 되고 2.5배도 되는 것처럼, 같은 화면의 나눗셈 결과는 결국 원본이 정한다는 것이 이번 글에서 깨달을 수 있는 점입니다.