미리보기와 렌더가 달라지는 이유: Remotion·Three.js를 프레임 함수로 만들기
실시간 useFrame 누적 상태와 영상 타임라인의 차이를 짚고, ThreeCanvas·프레임 함수·난수 시드로 재현 가능한 애니메이션을 만드는 방법과 검증 범위를 정리한다.
프레임 번호로 같은 장면을 재현하는 원리를 표현한 생성형 개념 이미지다. 실제 렌더 화면이나 측정 결과가 아니다.
브라우저에서 잘 움직이던 3D 장면이 영상으로 출력하면 튀거나, 타임라인을 되감았을 때 다른 위치에 나타나는 경우가 있다. 이때 먼저 확인할 것은 GPU 성능보다 애니메이션이 무엇을 시간으로 쓰는가다.
Remotion으로 영상을 만들 때는 “직전 위치에서 조금 이동한다”보다 “현재 프레임의 위치는 여기다”로 작성해야 한다. Three.js 장면도 같은 원칙을 적용한다. 이 글은 공식 문서에 근거한 구현 예시와 별도로 실행한 작은 수학 검증을 구분해 설명한다.
실시간 루프와 영상의 시간은 다르다
React Three Fiber의 useFrame은 렌더 루프에서 호출되며 초 단위의 delta를 받는다. 화면이 계속 진행되는 인터랙티브 장면에서는 이를 누적해 물체를 움직일 수 있다. React Three Fiber Hooks
아래는 실시간 회전의 전형적인 형태다. 잘못된 API가 아니라, 영상 타임라인과 시간 기준이 다른 코드다.
문제는 이 코드가 “이전에 얼마나 실행됐는지”에 의존한다는 점이다. 프레임 90을 바로 요청했을 때와 프레임 0부터 순서대로 재생했을 때의 상태를 같게 만들려면 별도 동기화가 필요하다.
Remotion은 여러 탭에서 프레임을 렌더링할 수 있으며 탭 사이의 상태는 공유되지 않는다. 공식 문제 해결 문서도 프레임을 순서대로 처리한다고 가정하지 말고, 현재 프레임에서 화면을 결정하도록 안내한다. Flickering
위치와 회전을 프레임의 함수로 만든다
영상의 FPS가 30이면 프레임 90의 시간은 3초다. 회전 속도가 초당 0.8라디안이면 각도는 90 / 30 * 0.8, 즉 2.4라디안이다. 이전 프레임을 계산할 필요가 없다.
useCurrentFrame()은 0부터 시작하는 프레임 번호를 반환한다. 시간 오프셋이 적용된 컴포넌트 안에서는 상대 프레임이므로, 장면 내부 시간과 전체 영상 시간을 혼동하지 않아야 한다. 전체 시간을 공유해야 한다면 최상위에서 구한 프레임을 props로 전달한다. useCurrentFrame
다음은 기존 Remotion 프로젝트에 넣는 예시 컴포넌트다. 여기서는 전체 React·WebGL 렌더를 실행하지 않았으며, 설치 버전의 호환성은 프로젝트에서 확인해야 한다.
ThreeCanvas는 React Three Fiber의 Canvas와 Remotion의 컨텍스트를 연결한다. 너비·높이를 명시하고 애니메이션 값은 선언적으로 전달한다. 공식 문서도 이 방식으로 타임라인 이동과 정지를 지원하도록 안내한다. ThreeCanvas
핵심은 +=를 전부 금지하는 것이 아니다. 화면을 결정하는 상태가 프레임 번호와 고정된 입력만으로 재구성되는가가 기준이다.
무작위 배치도 재현 가능해야 한다
별이나 입자를 배치할 때 렌더 중 Math.random()을 호출하면 같은 프레임의 배치가 바뀔 수 있다. Remotion의 random(seed)는 같은 시드에 같은 값을 반환한다. 문자열이나 숫자를 시드로 사용한다. random
고정된 배치를 원한다면 시드에 매 프레임 바뀌는 값을 넣지 않는다. 움직임은 고정된 초기값과 프레임 기반 식을 조합한다. 이것은 위 API 성질을 이용한 설계 제안이다.
물리 시뮬레이션은 더 주의해야 한다. 충돌·속도처럼 이전 상태를 사용하는 계산을 단순히 frame / fps로 바꿀 수는 없다. 고정 시간 간격으로 미리 계산한 결과를 프레임별로 저장하거나, 목표 시점까지 같은 초기 상태에서 재구성하는 별도 설계가 필요하다. 이는 일반적인 구현 전략이며 특정 물리 엔진의 결과를 검증했다는 뜻은 아니다.
순서 독립성부터 작게 시험한다
아래 파일은 렌더러 없이 실행 가능한 검증 예시다. 같은 프레임을 순차 조회와 뒤섞인 조회로 요청해 결과를 비교한다.
이 검증은 Node.js 24.22.1에서 실제 실행했고 위 PASS 메시지로 종료했다. 확인한 것은 이 수학 함수의 순서 독립성뿐이다. WebGL 픽셀 일치, 영상 인코딩, 음성 싱크나 렌더 속도를 시험한 결과는 아니다.
실제 프로젝트에는 다음 검증을 추가하는 편이 좋다.
- 타임라인을 90 → 0 → 90 프레임으로 이동해 같은 장면인지 비교한다.
- 정지한 상태에서 물체가 계속 움직이지 않는지 확인한다.
- 같은 환경에서 특정 프레임을 독립 렌더하고 연속 렌더의 해당 프레임과 비교한다.
- 폰트·이미지·모델 로딩이 끝나기 전에 캡처되지 않는지 별도로 점검한다.
- 브라우저·패키지·에셋 버전과 렌더 옵션을 기록한다.
상태 계산이 결정적이어도 로딩 대기 누락은 별개의 문제다. 공식 문서는 에셋 로딩과 폰트 준비를 렌더러가 기다리게 해야 한다고 설명한다. --concurrency=1만으로 시간 동기화 문제가 해결됐다고 판단해서도 안 된다. 렌더 깜박임 점검
시간 문제와 GPU 문제를 나눠 진단한다
장면의 위치가 달라지는 문제와 WebGL 컨텍스트 생성 실패는 서로 다르다. 먼저 프레임 함수로 시간 기준을 정리하고, 이후 렌더 환경을 점검한다.
현재 @remotion/three 공식 문서는 서버 렌더링에서 angle 설정을 안내한다. CLI 설정 파일이 서버 API 호출에 자동 적용된다고 가정하지 말고, 해당 API의 chromiumOptions도 확인해야 한다. Three 연동과 서버 렌더링
이 설정만으로 모든 환경의 GPU 가속이나 성능 향상이 보장되지는 않는다. 이 글에서도 하드웨어별 성능을 측정하지 않았다.
영상용 코드의 첫 번째 완료 조건은 “재생하면 움직인다”가 아니다. 원하는 프레임을 언제, 어떤 순서로 요청해도 의도한 상태를 다시 만들 수 있다는 것이다. 이 조건부터 확보하면 타임라인 수정과 렌더 실패를 훨씬 분명하게 나눠 진단할 수 있다.