React와 Remotion으로 만드는 장송의 프리렌 스타일 애니메이션 오프닝 영상 제작 가이드
코드 기반 비디오 렌더링 프레임워크 Remotion(github.com/remotion-dev/remotion)을 활용하여 인기 애니메이션 프리렌 감성의 패럴랙스, 마법 입자, 시퀀스 연출을 React로 구축하는 풀 가이드.
코드 기반 비디오 렌더링 Engine: Remotion
최근 프론트엔드 생태계에서 웹 기술(React, HTML5 Canvas, CSS Animation)을 활용해 정교한 비디오를 프로그래밍 방식으로 제작하는 패러다임이 큰 주목을 받고 있습니다. 대표적인 프레임워크가 바로 Remotion GitHub 저장소입니다.
Remotion은 타임라인 UI 중심의 에프터이펙트나 프리미어 프로 대신, 프레임 단위의 정확한 수식 계산과 컴포넌트 재사용성을 활용해 100% 코드로 고화질 MP4 영상을 렌더링할 수 있게 해줍니다.
이번 포스트에서는 인기 애니메이션 *장송의 프리렌(Frieren: Beyond Journey’s End)*의 몽환적이고 서정적인 오프닝 연출을 Remotion과 React 코드로 직접 제작하는 프로세스를 소개합니다.
🎨 프리렌 오프닝 연출의 3가지 핵심 요소
프리렌 감성의 오프닝 연출을 완성하기 위해서는 다음 3가지 비주얼 요소가 필수적입니다.
- 서정적인 패럴랙스(Parallax) 줌 및 패닝 효과: 카메라인 렌즈가 천천히 모션하며 이동하는 Ken Burns 효과
- 마법 입자(Magic Particle) 및 꽃잎 입자 연출: Canvas 기반 몽환적 입자 흐름
- 신비로운 타이포그래피(Typography) 등장: 글자 간격 확장, Glow 효과 및 텍스트 마스킹
🛠️ Remotion 환경 초기화
터미널에서 아래 프로젝트 창출 명령어로 Remotion 환경을 생성합니다.
npx create-video@latest frieren-op-demo
cd frieren-op-demo
설치가 완료되면 npm start 명령으로 브라우저 기반의 타임라인 Preview 플레이어를 열어실 수 있습니다.
🎬 코드 구성: 프리렌 오프닝 루트 컴포넌트 (Root.tsx)
비디오의 해상도(1920x1080), 프레임 레이트(60fps), 전체 프레임 수(180프레임 = 3초)를 등록합니다.
import { Composition } from 'remotion';
import { FrierenOpScene } from './FrierenOpScene';
export const RemotionRoot = () => {
return (
<Composition
id="FrierenOpScene"
component={FrierenOpScene}
durationInFrames={180}
fps={60}
width={1920}
height={1080}
defaultProps={{
title: 'FRIEREN',
subtitle: 'Beyond Journey\'s End',
}}
/>
);
};
🌌 패럴랙스 배율과 마법 입자 씬 연출 (FrierenOpScene.tsx)
Remotion의 핵심은 useCurrentFrame()과 interpolate(), 그리고 spring() 훅을 활용한 프레임 정밀 계산입니다.
import {
AbsoluteFill,
interpolate,
spring,
useCurrentFrame,
useVideoConfig,
} from 'remotion';
import React, { useMemo } from 'react';
export const FrierenOpScene: React.FC<{ title: string; subtitle: string }> = ({
title,
subtitle,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// 1. 카메라 서서히 확대 (Ken Burns 효과)
const bgScale = interpolate(frame, [0, 180], [1.0, 1.15], {
extrapolateRight: 'clamp',
});
// 2. 타이틀 페이드인 및 스프링 애니메이션
const titleOpacity = interpolate(frame, [30, 90], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const titleSpring = spring({
frame: frame - 30,
fps,
config: { damping: 12 },
});
// 3. 자간 확장 효과 (Letter Spacing Animation)
const letterSpacing = interpolate(frame, [30, 180], [10, 24], {
extrapolateRight: 'clamp',
});
return (
<AbsoluteFill style={{ backgroundColor: '#090d16', overflow: 'hidden' }}>
{/* 배경 레이어 (신비로운 푸른빛 글래디언트 및 패럴랙스 확대) */}
<AbsoluteFill
style={{
transform: `scale(${bgScale})`,
background: 'radial-gradient(circle at 50% 40%, #1e3a5f 0%, #090d16 70%)',
filter: 'contrast(105%) brightness(95%)',
}}
/>
{/* 마법 파티클 빛 레이어 */}
<MagicGlowParticles frame={frame} />
{/* 중앙 메인 타이틀 영역 */}
<AbsoluteFill
style={{
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
opacity: titleOpacity,
}}
>
<h1
style={{
fontFamily: 'serif',
fontSize: '110px',
color: '#e2e8f0',
letterSpacing: `${letterSpacing}px`,
margin: 0,
textShadow: '0 0 30px rgba(147, 197, 253, 0.6), 0 0 60px rgba(147, 197, 253, 0.3)',
transform: `scale(${interpolate(titleSpring, [0, 1], [0.9, 1])})`,
}}
>
{title}
</h1>
<p
style={{
fontFamily: 'sans-serif',
fontSize: '24px',
color: '#94a3b8',
letterSpacing: '8px',
marginTop: '16px',
textTransform: 'uppercase',
}}
>
{subtitle}
</p>
</AbsoluteFill>
{/* 영화 같은 리터박스 (상하 시네마틱 흑색 바) */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '80px',
backgroundColor: '#000000',
}}
/>
<div
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: '80px',
backgroundColor: '#000000',
}}
/>
</AbsoluteFill>
);
};
// 마법 파티클 입자 렌더링 헬퍼 컴포넌트
const MagicGlowParticles: React.FC<{ frame: number }> = ({ frame }) => {
const particles = useMemo(() => {
return Array.from({ length: 40 }).map((_, i) => ({
id: i,
x: (i * 137.5) % 100, // 슈도 랜덤 좌표 분포
baseY: (i * 93.1) % 100,
size: 2 + (i % 5) * 2,
speed: 0.1 + (i % 3) * 0.15,
opacity: 0.3 + (i % 4) * 0.2,
}));
}, []);
return (
<AbsoluteFill>
{particles.map((p) => {
const currentY = (p.baseY - frame * p.speed + 100) % 100;
const glowOpacity = interpolate(
Math.sin(frame * 0.05 + p.id),
[-1, 1],
[p.opacity * 0.5, p.opacity]
);
return (
<div
key={p.id}
style={{
position: 'absolute',
left: `${p.x}%`,
top: `${currentY}%`,
width: `${p.size}px`,
height: `${p.size}px`,
borderRadius: '50%',
backgroundColor: p.id % 2 === 0 ? '#93c5fd' : '#fef08a',
boxShadow: `0 0 ${p.size * 3}px ${p.id % 2 === 0 ? '#60a5fa' : '#fde047'}`,
opacity: glowOpacity,
}}
/>
);
})}
</AbsoluteFill>
);
};
⚡ 고화질 비디오 MP4 파일 렌더링
작성한 React 컴포넌트를 실제 비디오 파일로 추출하기 위해 Remotion CLI 명령을 실행합니다.
npx remotion render src/index.ts FrierenOpScene out/frieren_op.mp4
FFmpeg 기반으로 빠른 멀티스레드 렌더링이 진행되며, out/frieren_op.mp4 경로에 60fps의 매끄러운 몽환적 오프닝 영상이 생성됩니다.
💡 결론 및 추가 활용 팁
Remotion을 사용하면 애니메이션 영상뿐만 아니라 기술 소식 요약 숏폼, 깃허브 오픈소스 홍보 비디오, 코드 리뷰 스크린캐스트까지 React 코드만으로 자동화할 수 있습니다.
Three.js / React Three Fiber 라이브러리와 조합하면 마법 진 3D 렌더링이나 실시간 음원 Beat-sync 동기화까지 쉽게 확장이 가능합니다. 더 자세한 정보와 API 문서는 공식 저장소를 참고해 보세요.
- Verified GitHub Repository: remotion-dev/remotion