잘 만든 실시간 바다 데모를 봤습니다. 유료 제품이었습니다. 여기서 갈림길이 하나 있습니다. 번들을 받아 셰이더를 뜯어보느냐, 아니면 그게 무슨 기법인지만 알아내고 직접 만드느냐.
후자로 갔습니다. 그 데모에서 가져온 건 두 가지뿐입니다. 화면에 어떤 파라미터를 노출하는가 같은 누구나 볼 수 있는 사실과, 공개 문서가 스스로 밝힌 기법 이름. 구현은 전부 공개 문헌을 보고 처음부터 썼습니다. 무엇을 봤고 무엇을 안 봤는지의 경계는 저장소에 문서로 남겨 뒀습니다.
파이프라인
바다는 한 장의 텍스처가 아니라 매 프레임 도는 계산입니다.
스펙트럼에서 h₀(k)를 만들고(CPU, 파라미터 바뀔 때 한 번), 그걸 시간에 따라 전개하고, 역 FFT로 높이장을 얻고, 변위를 조립하고, 법선과 포말을 뽑고, 마지막에 수면을 셰이딩합니다. FFT는 GPU에서 버터플라이 16패스로 돕니다. 256×256이면 그렇습니다.
그리고 이 경로를 세 번 돕니다. 512m, 96m, 18m 패치로. 큰 너울과 바람 파도와 잔물결은 서로 다른 스케일이라 한 격자에 담으면 어느 쪽이든 뭉개집니다.
캐스케이드는 겹치면 안 된다
여기서 처음 물렸습니다. 캐스케이드를 세 개 두면 각각이 자기 대역만 담당해야 하는데, 겹치면 같은 파도가 두 번 더해집니다. 그러면 파고가 물리값이 아니라 캐스케이드 개수에 비례하게 됩니다. 세 개 두면 세 배 높아지는 바다는 바다가 아닙니다.
그래서 캐스케이드 i는 자기 기본 파수부터 다음 캐스케이드의 기본 파수까지만 담당하게 잘랐습니다. 비율이 약 5.3배인데, 이 값이면 각 대역이 그 캐스케이드의 나이퀴스트 한참 안쪽에 들어와서 에일리어싱도 없습니다.
파고는 풍속이 정한다
두 번째로 물린 게 더 컸습니다. 처음에는 피크 파장을 독립적인 슬라이더로 뒀습니다. 바람도 조절하고 파장도 조절하면 좋을 것 같았거든요.
에너지가 폭주했습니다. 스펙트럼에 ω⁻⁵ 항이 있어서, 풍속 9m/s에 피크 파장 165m를 넣으면 유의파고가 9.4m가 나옵니다. 9m/s 바람에 9m 파도는 물리적으로 말이 안 됩니다.
그래서 스펙트럼을 완전발달 파고 공식으로 정규화했습니다. 풍속이 파고를 결정하고, 피크 파장은 순수한 형상 컨트롤이 됩니다. 슬라이더 하나를 자유도에서 빼는 결정이었지만, 그래야 바다가 거짓말을 안 합니다.
그래서 맞는지 확인했다
브라우저에서 오프스크린으로 렌더한 다음 픽셀을 직접 읽어 쟀습니다. 프리셋별 유의파고를 CPU 수면에서 실측해 이론값과 대조했습니다.
| 프리셋 | 풍속 | 보고값 | 실측 |
|---|---|---|---|
| 미풍 | 7.5 m/s | 1.20 m | 1.04 m |
| 너울 | 9.0 m/s | 1.73 m | 1.67 m |
| 강풍 | 17.0 m/s | 6.19 m | 5.51 m |
| 폭풍 | 22.0 m/s | 10.36 m | 9.90 m |
실측이 조금씩 낮은 건 정상입니다. CPU 수면은 거친 두 캐스케이드만 담당하고 잔물결 기여분이 빠지니까요. 잔잔한 프리셋에서 격차가 제일 큰 것도 같은 이유입니다. 에너지 대부분이 잔물결 쪽에 있거든요.
포말 임계값도 감이 아니라 이 방식으로 정했습니다. 야코비안 분포를 캐스케이드별로 재보니 512m 너울은 최소값이 0.996이라 아예 부서지지 않고, 18m 잔물결은 7.5%가 임계 아래로 내려갑니다. 장주기 너울이 심해에서 부서지지 않는다는 물리와 맞습니다. 그 분포를 보고 임계값을 정했습니다.
여섯 번 틀렸다
만드는 동안 실제로 고친 결함들입니다. 하나같이 증상과 원인이 딴판이었습니다.
근거리 수면이 통째로 안 보였습니다. 방사형 격자의 링 사각형 와인딩이 뒤집혀서 삼각형이 아래를 보고 있었고, 백페이스 컬링에 잘려 나갔습니다. 그 자리에 보이던 회색은 물이 아니라 지평선 아래 하늘 폴백이었습니다.
화면 전체가 하얗게 탔습니다. 태양을 점광원으로 GGX에 넣었더니 D 항이 발산했습니다. 피크가 10⁴쯤 나옵니다. 정규화 블린-퐁으로 바꿨습니다.
바다가 포말로 뒤덮였습니다. 포말 감쇠를 프레임 단위로 걸어서 프레임레이트가 높을수록 누적됐습니다. 초 단위 지수 감쇠로 바꿨습니다.
포말이 황토색이었습니다. 단일 산란 모델이 하늘 주변광을 과소평가해서, 흰 포말이 따뜻한 직사광만으로 조명되고 있었습니다.
수평선에 흰 점선이 생겼습니다. 최외곽 링 삼각형이 한 변 200m쯤이라 셰이딩이 정점마다 확 튀었습니다.
셰이더가 컴파일이 안 됐습니다. 변수 이름을 patch로 지었는데 GLSL ES 3.00 예약어였습니다.
아직 못 한 것
정직하게 적자면 한계가 남아 있습니다.
저각 태양에서 하늘이 평평해집니다. 시선 광학두께가 커지면 단일 산란 모델이 포화해서 상수를 반환합니다. 실측으로 태양 고도 7도에서 60픽셀 떨어진 두 하늘 행이 완전히 같은 값을 냈습니다. 균질 매질 가정에서는 물리적으로 맞는 거동이고, 제대로 풀려면 다중 산란 테이블이 필요합니다. 당장은 일몰 프리셋의 탁도를 낮춰 완화했습니다.
부력이 잔물결을 무시합니다. 배를 띄우는 CPU 수면은 18m 이상 파장만 담습니다. 배를 움직이지 못하는 성분을 빼서 프레임 예산을 지킨 의도적 선택이지만, 배는 화면에 보이는 표면보다 최대 수십 센티 매끄러운 표면 위에 떠 있습니다.
항적이 없고 수중 시점도 없습니다. 둘 다 별도 작업이라 이번 범위에서 뺐습니다.
브라우저에서 바로 보실 수 있습니다. 우측 패널에서 풍속을 올렸다 내려 보면, 파고가 따라 움직이는 게 슬라이더 장난이 아니라 공식이라는 걸 알 수 있습니다.