개요

Owen Yuwono가 X에 올린 짧은 영상은 Three.js로 만든 FFT 해양 시뮬레이션의 결과를 보여준다. 함께 제공된 owenyuwono/poseidon은 이 장면을 만드는 구현으로, Three.js WebGPURenderer·TSL과 WebGPU compute shader를 이용해 파도 스펙트럼부터 역 FFT, 변위 맵, 거품까지 GPU에서 처리한다. X 게시물의 원문은 sneak peak of Fast Fourier Transform (FFT) ocean simulation in threejs again이며, 게시물 자체는 짧은 미리보기이고 자세한 기술 정보는 저장소 README와 소스에 있다.

X 게시물에 첨부된 5.098초 영상의 poster. 실제 영상은 원문 링크를 통해 확인한다.

X 게시물 맥락

2026-06-09 게시물로, 원문 그대로 Three.js FFT 오션의 재작업 티저다. 캡처 시점 763 likes · 61 reposts · 30,749 views(포인트 스냅샷). 게시물은 영상 1개(1506×734)를 동반했으며 영상 자체는 보존하지 않았다. “again”이라는 표현은 이전에도 같은 주제의 반복 작업이 있었음을 암시하나, 이전 버전은 별도 기록이 없다.

핵심 아이디어

  • 주파수 영역에서 공간 영역까지 GPU가 계산한다. CPU가 매 프레임 높이 맵을 만들지 않고, WebGPU compute pass가 스펙트럼을 시간에 따라 진화시키고 2D inverse FFT를 수행한다.
  • 서로 다른 파장대를 cascade로 분리한다. 기본 설정은 250 m·17 m·5 m의 세 cascade이며, 각 cascade가 서로 겹치지 않는 파수 구간을 담당한다. 긴 너울과 짧은 잔물결을 한 FFT에 억지로 넣지 않아 넓은 스케일을 합성할 수 있다.
  • 거품을 높이 임계값이 아니라 접힘(fold)에서 만든다. 수평 변위의 Jacobian이 작아지는 지점을 breaking crest로 보고, turbulence 값을 즉시 낮춘 뒤 천천히 회복시켜 흰 포말이 깜빡이지 않고 쌓였다가 사라지게 한다.
  • 렌더링은 계산 결과를 샘플링한다. IFFT 결과를 반복 가능한 rgba16f storage texture에 기록하고, 표면 재질이 cascade별 displacement·derivative 맵을 합산한다.

계산 파이프라인

단계구현
1. 초기 스펙트럼Gaussian noise에 JONSWAP 방향성 스펙트럼을 곱한다. 로컬 wind-sea와 swell을 합산하고 TMA 수심 보정, Donelan–Banner 방향 분산, short-wave fade를 적용한다.
2. 켤레 패킹h0(k)conj(h0(-k))를 한 vec4에 넣어 실수 높이장을 만들 수 있는 주파수 데이터를 준비한다.
3. 시간 진화h0exp(iωt)를 결합해 높이 Dy, 수평 변위 Dx/Dz, 그리고 기울기·야코비안에 필요한 도함수 주파수장을 함께 만든다.
4. 2D 역 FFT네 개의 packed complex field를 수평·수직 butterfly 단계로 변환한다. 각 단계는 GPU buffer ping-pong으로 진행한다.
5. 맵 조립IFFT된 field를 displacement·derivatives 맵으로 저장하고, 수평 변위 Jacobian에서 foam turbulence를 갱신한다.
6. 표면 셰이딩다중 cascade 변위, fold-aware normal, Fresnel 하늘 반사, 태양 glitter, subsurface scatter, 수심 기반 물색, noise 기반 bubbly foam을 조합한다.

FFT와 WebGPU 설계

src/ocean/fft.jsFFT 클래스는 log2(N) × N butterfly table을 CPU에서 한 번 만들고, 각 항목에 twiddle factor와 입력 인덱스를 저장한다. 이후 다음 순서로 IFFT를 구성한다.

  1. X 방향 butterfly 단계
  2. Y 방향 butterfly 단계
  3. 중심화된 스펙트럼을 맞추는 (-1)^(x+y) permutation

소스 주석에 따르면 WebGPU compute pass 내부에는 dispatch 사이의 메모리 barrier가 없으므로, 각 FFT 단계는 별도의 renderer.compute() submit으로 실행해야 한다. 대신 같은 단계에 속한 여러 cascade·field의 kernel을 묶어 dispatch 수를 관리한다. 기본 N=256이며 파라미터 파일에는 512도 지원한다고 적혀 있다.

한 cascade는 DxDz, DyDxz, DyxDyz, DxxDzz 네 개의 complex field를 사용한다. 각 field는 두 개의 실수장을 packed complex 값으로 운반하므로, 한 cascade에서 높이·변위·기울기·대각 도함수까지 GPU에서 함께 복원할 수 있다.

파도 스펙트럼과 시각 효과

저장소는 단순한 sine wave가 아니라 다음 물리·시각 모델을 결합한다.

  • Horvath 2015 계열의 방향성 파도 스펙트럼과 JONSWAP peak enhancement
  • 수심에 따른 TMA 보정과 중력 기반 분산 관계
  • wind-sea와 swell을 별도 파라미터로 조정
  • choppy horizontal displacement와 slope FFT에서 얻은 fold-aware normal
  • Fresnel 기반 하늘 반사, 반사 태양광 glitter, crest subsurface scattering
  • 수평 변위 접힘에서 계산한 foam과 두 스케일 detail texture
  • 선택적으로 활성화할 수 있는 GPU ballistic spray particle system

표면은 PlaneGeometry(400, 400, 900, 900)에 매핑되며, frustumCulled = false로 넓은 바다 패치가 시야에서 빠지지 않게 한다. 디버그 화면에서는 전체 ocean, 높이 맵, 세 cascade의 스펙트럼을 전환할 수 있다. 스펙트럼·FFT 기법은 gasgiant/FFT-Ocean(MIT)에서 차용해 Tessendorf 2001과 Horvath 2015를 구현했다(README 명시).

실행과 조작

npm install
npm run dev

README가 안내하는 실행 환경은 WebGPU를 지원하는 Chrome/Edge 113+ 또는 Safari 18+다. 이 프로젝트는 WebGPU 전용이며 WebGL fallback이 없다. 실행 후 제공되는 키보드 단축키는 다음과 같다.

  • F: 일반 ocean view
  • 5: height map
  • 1, 2, 3: 각 cascade spectrum debug view
  • +, -: choppiness λ 조절
  • lil-gui: wind speed/direction, sea amplitude, directionality, time scale, foam threshold/coverage/decay, surface detail, subsurface, sun/sky, color 조절

바람·방향성·sea amplitude를 바꾸면 초기 스펙트럼을 다시 계산하고, choppiness·foam·조명 값은 uniform을 갱신하는 식으로 상호작용을 분리한다.

이 구현에서 배울 점

GPU-driven의 실제 의미

이 프로젝트에서 WebGPU는 단순히 Three.js 렌더러를 바꾼 것이 아니다. CPU는 butterfly index와 초기 Gaussian noise 같은 준비 작업만 담당하고, 매 프레임의 주파수 진화·역 FFT·foam accumulation·storage texture 기록은 compute shader가 담당한다. 최종 vertex displacement와 fragment shading은 이 GPU-resident 결과를 바로 샘플링한다. 따라서 이 설계의 핵심 병목은 JavaScript의 per-vertex 계산이 아니라 compute dispatch 수, storage texture 대역폭, FFT 해상도·cascade 수가 된다.

시뮬레이션과 셰이딩을 분리한 구조

Ocean은 스펙트럼·FFT·맵 조립만 소유하고, createOceanSurfaceMaterial은 displacement/derivatives texture를 읽어 화면을 그린다. 이 분리는 새로운 표면 셰이더나 debug view를 추가해도 스펙트럼 계산 코어를 다시 쓰지 않아도 된다는 장점이 있다.

거품을 별도 후처리로 만들지 않은 선택

foam을 height threshold나 화면 공간 blur로 그리지 않고 수평 변위의 Jacobian에서 직접 만들기 때문에, 파도의 기하학적 fold와 시각 효과가 연결된다. foamDecay를 낮추면 흰 포말이 오래 남고, threshold·scale은 실제 fold에서 얼마나 넓게 덮을지 조정한다. 바다 표면의 “살아 있는” 느낌을 만드는 데 계산 결과와 시각 효과를 같은 상태에서 파생하는 방식이 유용하다.

재사용 가능한 개발 순서: IFFT를 단독 검증 후 오션에 연결

twiddle/index buffer를 미리 계산한 뒤, 오션에 연결하기 전에 해석적 impulse·주파수 응답과 대조해 IFFT 자체를 단독 검증했다. FFT 코어와 시각 효과를 분리해 각자 검증할 수 있게 만든 이 순서는 TSL + compute shader 파이프라인을 새로 배울 때 그대로 따라 쓸 수 있는 패턴이다. 이 프로젝트는 오션 렌더러 그 자체보다 WebGPU compute 파이프라인 학습용 레퍼런스로 읽는 게 좋다.

검증 경계

  • 조사 기준 저장소는 main의 고정 commit ef5a167d81eb4cc20e24c530805902fa41aad062이며, 22개 commit-pinned raw 파일의 HTTP 200·바이트 수·SHA-256이 checkout과 일치했다.
  • 임시 checkout에서 npm ci --ignore-scripts --no-audit --no-fundnpm run build를 실행했고 Vite production build가 통과했다.
  • FFT analytic impulse/frequency self-test는 소스에 포함되어 앱 시작 시 실행되지만, 이 ingest에서는 브라우저 WebGPU를 띄워 그 결과를 독립 실행하지 않았다.
  • 실제 GPU FPS, visual artifact, WebGPU backend 호환성, Safari/Chrome/Edge 간 차이는 검증하지 않았다. README의 브라우저 요구사항과 기능 설명은 저장소가 선언한 범위로 기록한다.

원문 보존 위치

  • X 원문 캡처: raw source: x-owenyuwono-2064260229207142583
  • GitHub snapshot과 commit-pinned manifest: raw source: github-owenyuwono-poseidon
  • 영상 poster: raw/assets/x-owenyuwono-poseidon.jpg

관련 위키

원문 링크