FFT 바다는 지금까지 수면 아래를 해석적으로 칠했습니다. 상수 알베도에 노이즈를 섞어 "물 밑은 대충 이런 색"을 그린 것입니다. 멀리서는 그럴듯한데 섬 근처에서는 바닥이 청록색 원반처럼 보였습니다.

바다와 하늘을 뺀 씬을 별도의 렌더타깃에 그리고, 물 셰이더가 화면 좌표를 법선 방향으로 굴절시켜 그걸 읽어 오도록 바꿨습니다. 흡수 감쇠는 그대로 두고, 색만 진짜 지형에서 가져옵니다.

톤맵을 두 번 타면 안 됩니다

렌더타깃을 선형 HalfFloat로 뒀습니다. 이유가 있습니다.

읽어 온 색은 흡수를 거쳐 반사와 섞인 뒤, 셰이더 끝에서 톤맵을 한 번 타야 합니다. 이미 톤맵된 sRGB 색을 넣으면 두 번 걸립니다. 그것보다 나쁜 건 감쇠가 표시 공간에서 일어난다는 것입니다. 물속에서 빛이 줄어드는 건 물리량의 감쇠인데, 표시용으로 압축된 숫자에 그 계산을 하면 결과가 물리와 무관해집니다.

그래서 지형 셰이더에는 선형 출력 스위치를, 소품에는 패스 동안 톤맵 없음을 걸어 둘 다 가공 전 값으로 쓰게 했습니다.

물기둥 두께를 가정하지 않습니다

물이 얼마나 두꺼운지를 상수로 두면 얕은 곳과 외해가 같아집니다. 깊이 버퍼로 잽니다.

여기에 공짜로 딸려 오는 성질이 있습니다. 물 밑에 아무 지오메트리도 없으면 깊이가 아주 먼 값이라 광로가 킬로미터가 되고, 항이 스스로 사라집니다. 외해에서는 별도 처리 없이 자동으로 맞습니다.

수면 위의 것을 깊이로는 거를 수 없습니다

굴절 샘플을 기각해야 하는 경우가 둘입니다.

수면보다 에 있는 것은 깊이로 거르면 됩니다. 쉽습니다.

수면보다 에 있는 것은 깊이로 못 거릅니다. 굴절 오프셋이 화면 좌표를 다른 광선으로 옮기는데, 그 광선이 처음 만나는 물체 — 선체 상부구조나 해변 — 는 앞에 있는 물보다 카메라에서 멀기 때문에 깊이 테스트를 통과해 버립니다.

실제로 선체 뒤에 흰 기름막 같은 것이 생겼습니다. 월드 좌표의 높이를 직접 비교해서 막았습니다.

물이 가려 주던 결함 둘

진짜 지형이 보이기 시작하자 원래 있던 문제가 드러났습니다. 해석적 물이 덮고 있었을 뿐입니다.

섬 둘레에 22m 절벽이 있었습니다. 지형 높이 함수가 일정 거리에서 그냥 잘렸는데, 그 지점의 높이가 마침 배의 건현만큼 낮은 값이었습니다. 잘린 자리가 그대로 낭떠러지가 된 것입니다. 해저 깊이까지 내려가는 완만한 램프로 바꿨습니다.

해저에 구멍이 뚫려 있었습니다. 지형 메시가 일정 높이 아래의 사각형을 버리고 있었습니다. 예전에는 그 아래를 아무도 안 봤으니 최적화였는데, 굴절 패스가 이 메시를 직접 그리니까 버린 자리가 구멍과 계단이 됐습니다. 기준을 훨씬 아래로 내렸습니다.

여섯 번 오진한 이유는 검증 도구였습니다

전경이 하얗게 뜨는 문제를 쫓는 데 오래 걸렸습니다. 원인을 여섯 번 잘못 짚었습니다. 렌더링이 아니라 제가 만든 검증 도구가 거짓말을 하고 있었습니다.

첫째, 동기화 함수가 태양 세기나 항적 양 같은 값을 매번 소스에서 다시 읽었습니다. 그래서 콘솔에서 직접 바꾼 값이 렌더 직전에 원래대로 되돌아갔습니다. 바꿔 놓고 "변화가 없다"는 결론을 내리고 있었던 것입니다.

둘째, 픽셀 샘플링 좌표가 렌더 해상도를 벗어나도 조용히 통과했습니다. 범위 밖을 읽어서 전경 밝기를 105로 보고했는데 실제 값은 235였습니다. 문제의 크기 자체를 잘못 알고 있었습니다.

둘 다 고쳤습니다. 동기화를 건너뛰는 옵션을 넣고, 범위를 벗어난 좌표는 조용히 넘어가는 대신 오류를 내게 했습니다.

측정 도구를 먼저 의심했어야 했습니다. 여섯 번의 오진은 렌더링이 이상해서가 아니라, 제가 보고 있던 숫자가 틀렸기 때문에 생긴 것입니다. 계측이 틀리면 그 뒤의 모든 추론이 같이 틀립니다.