왜곡을 그리지 않고 22미터 스크린을 만들었다
2026.08.10
아이맥스 1열에 앉으면 화면이 한눈에 안 들어온다. 목을 꺾어야 하고, 좌우 끝은 시야 밖으로 넘어간다. 그 느낌을 브라우저에 옮겨보고 싶었다.
제일 먼저 떠오른 방법은 왜곡 필터다. 이미지를 사다리꼴로 늘리고 좌우를 휘게 셰이더로 밀어주면 비슷해 보인다.
그렇게 안 만들었다. 대신 실제 규격의 곡면 스크린을 3D 공간에 세우고 1열 눈높이에서 투영했다.
/**
* 왜곡을 흉내내지 않는다. 실제 규격의 곡면 스크린을 3D 공간에 세워 두고
* 1열 눈높이에서 투영하면, 사다리꼴 원근과 좌우 감김이 전부 결과로 따라 나온다.
* 그래서 이 파일에는 "왜곡"이라는 개념 자체가 없다. 스크린이 실제로 놓인
* 자리만 적혀 있다.
*/
이유는 단순하다. 고개를 돌릴 수 있어야 하기 때문이다. 필터로 흉내낸 왜곡은 시점이 고정돼 있어서, 고개를 돌리는 순간 어긋난다. 스크린을 진짜로 세워두면 시점이 바뀌어도 알아서 맞는다.
IMAX는 반경을 안 알려준다
곡률 반경이 필요했는데 IMAX는 그 값을 공개하지 않는다.
대신 설계 원리는 공개돼 있다. 원통형으로 휘어서 기준 좌석에서 스크린 어느 지점까지의 거리든 거의 같게 만든다는 것.
여기서 값이 나온다. 반경 R짜리 원통은 곡률 중심에 앉은 사람에게 정확히 그렇게 보인다. 그러니 반경은 곧 기준 좌석까지의 거리다.
이 불변식은 테스트로 박아뒀다. 값을 몰라도 관계는 아니까, 관계를 검증한다.
26m는 그릇이었다
처음에 반경을 26m로 잡았다. 돌려보니 스크린이라기보다 그릇처럼 보였다.
호가 48.5°나 돼서 좌우 끝이 중앙보다 2.3m나 앞으로 나와 있었다. 감싸는 게 아니라 담고 있었다.
45m로 폈다.
/**
* 이 값이 이 페이지에서 가장 취향을 타는 숫자다. 올리면 평평해지고
* (디지털 IMAX 1.90:1은 훨씬 평평하다) 내리면 감싼다. 반대편 극단은
* 반구형인 IMAX Dome인데 그건 아예 다른 포맷이다.
*/
radius: 45,화각 100°는 1열이 아니었다
두 번째로 고친 게 세로 화각이다. 처음엔 100°로 잡았다. 다 보이게 하려고.
그게 정확히 틀린 판단이었다.
/**
* 100°로 잡았다가 66°로 좁혔다. 넓은 화각은 «다 보이게» 만들지만 그 대가로
* 전부 작아진다. 스크린 위아래 끝이 프레임 안에 얌전히 들어오는 순간
* 그건 1열이 아니라 그냥 큰 화면을 멀리서 보는 그림이다.
*/스크린이 화면 안에 다 들어오는 순간, 그건 1열이 아니다. 1열의 정체는 "한눈에 안 들어온다"는 것이다. 66°로 좁히니 스크린이 위아래로 프레임을 넘쳐서 끝이 안 보인다.
그래서 테스트를 뒤집었다. 「스크린 전체가 화면에 들어온다」를 검사하던 걸 「넘치는지」를 검사하는 쪽으로 바꿨다. 설계 의도가 반대가 됐으니 테스트도 반대가 돼야 한다.
기본 앙각도 33°에서 42°로 올렸다.
/**
* 원근이 극단적이라 스크린 위쪽 절반은 시야에서 16°밖에 차지하지 않는다.
* 기하학적 중앙을 보면 정작 얼굴은 저 위에 작게 붙는다.
*/
basePitch: (42 * Math.PI) / 180,기하학적 중앙과 시각적 중앙이 다르다. 원근이 심할수록 그 차이가 벌어진다.
좌석 거리도 6m에서 4.5m로 당겼다. 6m에서는 기하가 맞는데도 화면이 얌전했다 — 스크린 위아래의 거리 차가 2배밖에 안 나서 얼굴이 거의 안 눌렸다. 실제 아이맥스 1열이 스크린에서 4~5m다.
앙각 하한을 2°에서 -6°로 열어둔 건, 고개를 내리면 상영관이 나오게 하려는 것이다. 바닥·무대턱·측벽은 기본 시야에서 밀려나 있다.

상영관은 광선 교차로 그린다. 유일한 광원인 스크린의 평균색이 벽에 번진다. 위 사진에서 밝은 스크린 주변만 벽이 밝은 게 그것이다.
게이트 위블(필름이 미세하게 떠는 것)은 카메라가 아니라 스크린에 걸었다. 실제로 떠는 건 필름이지 관객이 아니다.
dispose가 앱을 죽였다
「끝내기」를 누르고 다시 상영하면 **「지원하지 않는 브라우저」**가 떴다.
dispose에서 WEBGL_lose_context로 컨텍스트를 죽이고 있었다. 컨텍스트가 쌓일까 봐 넣은 줄이었는데 전제가 틀렸다.
- 한 캔버스는 컨텍스트를 하나만 갖는다
- 잃은 컨텍스트는
getContext를 다시 불러도 그대로 돌아온다 - 죽은 컨텍스트 위에서 셰이더를 만들려니 실패했고, 그 실패가 「지원하지 않는 브라우저」로 안내됐다
캔버스가 늘 같은 DOM 노드라 컨텍스트는 애초에 하나뿐이고, 페이지를 떠나면 함께 회수된다. 이제 dispose는 이번 상영이 만든 자원만 반납한다.
같이 고친 게 둘 있다.
WebGL이 없는 것과 렌더러를 못 만든 것을 구분했다. 뭉뚱그리면 우리 실수가 「다른 브라우저를 쓰세요」로 안내되는데, 그건 빠져나갈 수 없는 화면이다. 사용자는 브라우저를 바꿔도 안 되는데 이유를 모른다.
첫 프레임을 8초까지만 기다린다. 권한은 났는데 프레임이 한 장도 안 오는 경우가 있다 — 다른 앱이 카메라를 물고 있거나 신호 없는 가상 카메라일 때. 그러면 「상영 준비 중…」에서 영영 멈춰 나갈 방법이 없었다.
멈추는 것과 지우는 것은 다르다
「끝내기」를 눌러도 검은 화면이 아니라 정지된 얼굴 위에 제목이 겹쳐 떴다.
rAF만 끊고 캔버스는 그대로 뒀기 때문이다. 그리기를 멈추는 것과 그려둔 것을 치우는 것은 다른 일이다. 백업 저장소 크기를 다시 잡아 캔버스를 비우니 그 밑의 검은 상영관이 드러났다.
img.decode()가 영영 안 끝난다
사진도 걸 수 있게 만들면서 하나 배웠다.
사진 디코딩을 <img>가 아니라 createImageBitmap으로 한다. 이유가 있다.
화면에 붙지 않은 img의 decode()는 탭이 뒤에 있으면 영영 끝나지 않는다 —
브라우저가 어차피 안 그릴 그림의 디코딩을 미루기 때문이다.
사진을 놓고 탭을 옮긴 사이에 아무 일도 없이 멈추는 길이었다. 브라우저가 백그라운드 탭에서 일을 미루는 건 requestAnimationFrame만이 아니다.
덤으로 오브젝트 URL 수명 관리도 사라졌다.
카메라만 뒤집는다
렌더러는 그림이 카메라에서 왔는지 파일에서 왔는지 모른다. Source 하나로 받는다 — 무엇을 올릴지, 얼마나 큰지, 바뀌었는지, 뒤집을지.
모드를 바꾸는 일은 매 프레임 그림을 집어오는 함수를 갈아끼우는 것이 전부라, 상영 중에 사진을 바꿔도 렌더러를 다시 세우지 않는다. 껐다 켜면 화면이 검게 한 번 끊기는데, 사진 바꾸기는 연달아 하는 일이라 그게 거슬린다.
뒤집기는 카메라에만 건다. 사진까지 거울로 만들면 그림 속 글자가 죄다 뒤집혀 걸린다. 회귀를 막는 테스트를 뒀다.
사진만 볼 사람에게는 카메라 권한을 아예 묻지 않는다. getUserMedia를 부르지 않는다.
확인은 왜곡 없는 화면에서
마지막으로 넣은 게 미리보기 단계다.

카메라나 사진을 고르면 바로 스크린에 걸지 않는다. 왜곡 없는 화면으로 「지금 뭐가 걸릴지」를 먼저 보여준다.
곡면에 얹히고 나면 얼굴이 제대로 잡혔는지, 사진을 맞게 골랐는지 알아보기가 어렵다. 확인은 왜곡 없는 화면에서 끝내고, 왜곡은 그 다음 일이다.
미리보기는 원본을 잘라내지 않고 다 넣는다. 여기서만은 1.43:1로 자르지 않는다 — 「스크린에 어떻게 걸릴지」가 아니라 「지금 뭐가 잡혔는지」를 보는 자리이기 때문이다. 카메라는 여기서도 거울로 둔다. 안 뒤집으면 상영 화면과 좌우가 달라진다.
남은 것
테스트는 「돌아간다」가 아니라 **「1열이 맞다」**를 검증한다. 앙각 하한, 사다리꼴 방향, 곡률 방향, 그리고 모든 정점이 카메라 앞에 있는지 — 시선에서 90°에 붙으면 투영이 발산한다.
돌아보면 이 페이지에서 고친 것 대부분은 기하가 아니라 판단이었다. 26m도 100°도 6m도 계산은 맞았다. 틀린 건 "다 보이는 게 좋다"는 전제였다.
1열은 다 안 보이는 자리다.
웹캠 영상은 브라우저 밖으로 나가지 않는다. 사진도 마찬가지다. IMAX 1열 체험하기
같이 읽으면 좋은 글
2400rpm은 장식이 아니어야 했다
KBO 투수가 던지는 공을 포수 눈높이에서 받아보는 걸 만들었다. 구종마다 곡선을 깎는 대신 중력·항력·마그누스를 적분했다. 그래야 화면에 적은 회전수가 궤적을 실제로 움직인다.
WebGL물리시각화2026.08.31웹캠으로 부장님이 다가오면 째려보는 걸 만들었다
얼굴 개수만 세서, 나 말고 다른 사람이 다가오면 눈이 그쪽을 째려본다. 제일 어려웠던 건 검출이 아니라 "누가 나인가"였고, 두 번째는 떨림이었다. 신원 식별도 저장도 전송도 없이 만든 기록.
MediaPipe브라우저프라이버시2026.07.31