카메라 렌더링은 본질적으로 화질 열화
원본 PNG 파일을 보는 것
vs
Unity 카메라로 렌더링하는 것
= 완전히 다른 프로세스!
PNG 파일 (1024x1024)
↓
디스플레이에 1:1 픽셀 매핑
↓
화면에 표시
손실: 거의 없음 (모니터 품질에만 의존)
PNG 파일 (1024x1024)
↓
GPU 메모리에 업로드 (텍스처 압축 가능)
↓
Vertex Shader (좌표 변환)
↓
Rasterization (삼각형 → 픽셀)
↓
Fragment Shader (텍스처 샘플링)
├─ Bilinear/Trilinear 필터링
├─ Mipmap 레벨 선택
└─ 색공간 변환 (sRGB ↔ Linear)
↓
Alpha Blending
↓
Frame Buffer (해상도 변환)
↓
Post Processing (선택적)
↓
최종 화면 출력
손실: 각 단계마다 발생!
원본 PNG: RGBA8 (32bit)
↓
GPU 업로드 시
├─ 압축: DXT5/ETC2 (4-8배 손실)
├─ 비압축: RGBA32 (손실 없음, 용량 큼)
└─ 반압축: RGBA16 (중간)
손실률: 0~80%
스프라이트 크기: 1024x1024
화면 크기: 853x853 (정수배 아님)
GPU가 하는 일:
"1024px 텍스처를 853px로 그려야 하네?"
↓
각 화면 픽셀마다 텍스처 여러 픽셀 참조
↓
Bilinear 보간 (4개 픽셀 평균)
↓
결과: 약간 흐릿해짐 🔻
카메라 거리에 따라 자동 선택:
가까울 때: Level 0 (1024x1024) 사용
중간: Level 1 (512x512) 사용
멀 때: Level 2 (256x256) 사용
문제:
├─ 거리가 애매하면 두 레벨 섞임 (Trilinear)
├─ 512와 256 사이 보간
└─ 더 흐릿해짐 🔻🔻
Linear 색공간 사용 시:
텍스처 읽기 → sRGB → Linear 변환
↓
셰이더 계산 (Linear 공간)
↓
화면 출력 → Linear → sRGB 변환
각 변환마다 근사 계산
↓
미세한 색상 손실 🔻
렌더 타겟: 1920x1080
실제 화면: 2560x1440
↓ (Upscaling)
각 픽셀 보간
결과: 선명도 감소 🔻
// 원본 PNG (1024x1024) 화질: 100%
방법 1: Windows 사진 뷰어
└─ 화질: 100% (손실 없음)
방법 2: Unity Sprite (압축 없음, Mipmap OFF)
└─ 화질: 95% (샘플링 손실)
방법 3: Unity Sprite (기본 설정)
└─ 화질: 70% (압축 + Mipmap)
방법 4: Unity Sprite (압축 + Mipmap + MSAA)
└─ 화질: 60% (과도한 블러)
필요한 기능들:
├─ 실시간 이동/회전/스케일
├─ 여러 레이어 합성
├─ 파티클/이펙트 오버레이
├─ 동적 조명
├─ 알파 블렌딩
└─ 애니메이션
→ 렌더링 파이프라인 불가피
Format: RGBA32 (무손실)
Compression: None
손실 감소: 30% → 5%
Generate Mip Maps: OFF
이유:
- 2D는 카메라 거리 고정
- Mipmap 불필요
- 레벨 간 보간 손실 제거
손실 감소: 10%
Point Filter:
├─ 픽셀 그대로 (1:1 매핑 시)
├─ 확대/축소 시 각짐
└─ 픽셀아트 게임 적합
Bilinear Filter:
├─ 부드러운 보간
├─ 약간 흐릿함 (5% 손실)
└─ 대부분의 2D 게임 적합
// 텍스처와 화면 픽셀을 정수배로 맞추기
텍스처 크기: 100px
PPU (Pixels Per Unit): 100
스프라이트 월드 크기: 1 unit
카메라 설정:
- Orthographic Size 계산
- 화면 해상도에 딱 맞춤
결과: 1:1 픽셀 매핑 → 손실 최소화
Pixel Perfect Camera 설정:
using UnityEngine;
using UnityEngine.U2D;
public class PixelPerfectSetup : MonoBehaviour
{
void Start()
{
var pixelPerfect = gameObject.AddComponent<PixelPerfectCamera>();
pixelPerfect.assetsPPU = 100; // 텍스처 PPU
pixelPerfect.refResolutionX = 1920; // 기준 해상도
pixelPerfect.refResolutionY = 1080;
pixelPerfect.upscaleRT = false; // 업스케일 안 함
pixelPerfect.pixelSnapping = true; // 픽셀 스냅
pixelPerfect.cropFrameX = false;
pixelPerfect.cropFrameY = false;
}
}
나쁜 예:
1024px → 853px (0.833배) = 보간 필요 = 흐릿함
좋은 예:
1024px → 2048px (2배) = 정수 배율 = 선명함
1024px → 512px (0.5배) = 정수 배율 = 선명함
스파인 익스포트:
Scale: 1.0 (원본 크기)
또는
Scale: 2.0 (2배 크게)
└─ Unity에서 축소하면 슈퍼샘플링 효과
└─ 안티앨리어싱 자연스러움
// Canvas + Image (RawImage)
// 3D 카메라 렌더링 우회
Canvas (Screen Space - Overlay)
└─ Image (or RawImage)
└─ Source Image: Sprite
장점: 카메라 렌더링 안 거침
단점:
- 월드 좌표 사용 불가
- 3D 효과 제한적
- Spine 애니메이션 제약
// NearestNeighbor + Sharpen
Shader "Custom/PixelPerfectSprite"
{
Properties
{
_MainTex ("Sprite Texture", 2D) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
float4 _MainTex_TexelSize;
fixed4 frag(v2f i) : SV_Target
{
// 정확한 텍셀 중심 계산
float2 uv = i.uv;
uv = uv * _MainTex_TexelSize.zw; // 픽셀 좌표
uv = floor(uv) + 0.5; // 정수 픽셀
uv = uv * _MainTex_TexelSize.xy; // UV 복원
return tex2D(_MainTex, uv);
}
ENDCG
}
}
}
// 2배 해상도로 렌더링 후 다운샘플링
Camera cam = Camera.main;
cam.targetTexture = new RenderTexture(
Screen.width * 2,
Screen.height * 2,
24
);
// 슈퍼샘플링 안티앨리어싱 효과
100% 화질 (불가능)
├─ 카메라 렌더링 안 씀
├─ 모든 것을 UI로 구현
└─ 현실성 없음
95% 화질 (가능, 권장) ⭐
├─ RGBA32 무손실
├─ Mipmap OFF
├─ Pixel Perfect Camera
├─ 필터링 최소화
└─ 개발 편의성 유지
85% 화질 (기본)
├─ 적당한 압축
├─ Mipmap ON
└─ 성능 우선
70% 화질 (모바일)
├─ 공격적 압축
├─ 배터리/성능 최우선
└─ 사용자는 잘 모름
"2D 이미지를 카메라로 렌더링하는 것 자체가 화질 열화 맞나요?"
→ 네, 맞습니다! 본질적으로 손실이 발생합니다.
원본 PNG 직접 보기: 100%
Unity 카메라 렌더링: 70~95%
손실 발생 지점:
✓ 텍스처 압축
✓ 샘플링/필터링
✓ Mipmap 보간
✓ 해상도 변환
✓ 색공간 변환
// 최적 설정으로 95% 화질 달성 가능
Camera.allowMSAA = false;
Camera.allowHDR = false;
QualitySettings.antiAliasing = 0;
Texture:
├─ Format: RGBA32
├─ Mipmap: OFF
├─ Filter: Bilinear
└─ Compression: None
+ Pixel Perfect Camera
5~10% 손실은 게임의 대가입니다! 🎮
실시간 애니메이션, 이펙트, 상호작용을 위해 지불하는 비용이죠.