devicePixelRatio, setPixelRatio, 캔버스 해상도: Three.js 선명도/성능의 본질

"'왜 DPR을 건드리면 선명도가 떨어지고 프레임이 살아나는가?'를 캔버스의 CSS 크기 vs drawing buffer 크기 관점에서 설명한다."

Seobway · · 14분

결론부터: DPR은 픽셀 수를 DPR² 배로 만든다

고DPI 환경에서 devicePixelRatio가 2라면, 같은 화면 크기라도 내부 픽셀 수는 4배가 된다.
그래서 DPR은 사실상 "선명도 스위치"이자 "GPU 부하 스위치"다.


1) 캔버스는 크기가 2개다: CSS 크기 vs drawing buffer 크기

Three.js 공식 매뉴얼은 "캔버스는 2가지 크기가 있다"고 명확히 말한다.[1]

%% desc: CSS 크기 vs Drawing Buffer 크기 — DPR²배 픽셀 폭증
flowchart TB
  CSS["CSS 크기\n예: 800 × 600 px\n(화면에 보이는 크기)"]
  DPR["devicePixelRatio\n예: 2.0"]
  Buffer["Drawing Buffer\n1600 × 1200 = 1,920,000 픽셀\n(실제 GPU가 채우는 픽셀)"]
  Screen["화면 표시\n(Buffer를 CSS 크기로 축소 표시)"]

  CSS --> Buffer
  DPR -->|"× DPR²"| Buffer
  Buffer --> Screen

  style CSS    fill:#1e3a5f,color:#7dd3fc,stroke:#334155
  style DPR    fill:#3b1f5e,color:#c4b5fd,stroke:#334155
  style Buffer fill:#4a1f1f,color:#fca5a5,stroke:#334155
  style Screen fill:#1c3a2b,color:#6ee7b7,stroke:#334155

CSS로 캔버스를 크게 늘렸는데 drawing buffer가 작으면, "이미지를 확대해서 보여주는 것"과 같아서 블러/블록감이 생긴다.[1]


2) 리사이즈에서 해야 하는 최소 2가지

매뉴얼이 제시하는 기본 패턴은 이렇다.[1]

function resizeRendererToDisplaySize(renderer) {
  const canvas = renderer.domElement;
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  const needResize = canvas.width !== width || canvas.height !== height;
  if (needResize) {
    // false: CSS 크기는 그대로, drawing buffer만 맞춤
    renderer.setSize(width, height, false);
  }
  return needResize;
}

function animate() {
  if (resizeRendererToDisplaySize(renderer)) {
    // drawing buffer가 바뀌었으면 카메라 aspect도 맞춰야 함
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();
  }
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

3) HD-DPI 처리: "그냥 안 한다"도 정답일 수 있다

Three.js는 HD-DPI에서 내부 픽셀 수가 폭증하므로, "아무것도 하지 않는" 선택이 오히려 현실적인 경우가 많다고 설명한다.[1]

특히 모바일은 DPR이 3인 경우도 있어, 내부 픽셀 수가 9배가 된다.
이건 정말로 "프레임이 죽는 지름길"이다.


4) setPixelRatio에 대한 공식 매뉴얼의 톤(중요)

Three.js 매뉴얼은 renderer.setPixelRatio(window.devicePixelRatio) 방식에 대해 강하게 비추천한다.[1]

요지는 이거다.

대신 매뉴얼은 "직접 계산해서 원하는 drawing buffer 크기를 정확히 통제하라"는 패턴을 제시한다.[1]

// 권장: DPR을 직접 계산해서 setSize에 넣기
function resizeWithDPR(renderer, dprCap = 2) {
  const canvas = renderer.domElement;
  const dpr = Math.min(window.devicePixelRatio, dprCap);
  const w = Math.floor(canvas.clientWidth * dpr);
  const h = Math.floor(canvas.clientHeight * dpr);
  if (canvas.width !== w || canvas.height !== h) {
    renderer.setSize(w, h, false); // setPixelRatio 없이 직접 제어
  }
}

5) DPR을 "제한(cap)"하는 이유

Three.js 매뉴얼은 HD-DPI 환경에서 과도한 내부 해상도가 GPU 부하를 키울 수 있어, 최대 internal resolution을 제한하는 방법까지 예시로 제공한다.[1]

이게 우리 포트폴리오 최적화에서 했던 일과 연결된다.


6) 인터랙티브 데모: DPR 슬라이더

슬라이더로 DPR을 바꾸면서 선명도와 내부 픽셀 수의 변화를 직접 확인할 수 있다.


7) 실전 체크리스트


참고

[1] Responsive Design — Three.js Manual


관련 글