사용한 대기 밀도와 산란 모델은 행성과 대기의 반지름, RayleighScaleHeight, RayleighBeta, MieScaleHeight, MieBeta, mieBetaExt, mieG, OzoneHeight, OzoneWidth 같은 몇 가지 상수로 대부분 결정됨
이 값들을 조정하면 화성 대기나 다른 행성의 대기에 가까운 결과를 만들 수 있음
화성용으로 사용한 값은 근사치임
planetRadius: 3390
atmosphereRadius: 3500, 약 110 km 두께
rayleighScaleHeight: 11.1
rayleighBeta: new THREE.Vector3(0.019, 0.013, 0.0057)
mieScaleHeight: 1.5
mieBeta: 0.04
mieBetaExt: 0.044
mieG: 0.65
ozoneCenterHeight: 0.0
ozoneWidth: 1.0
ozoneBetaAbs: new THREE.Vector3(0.0, 0.0, 0.0)
sunIntensity: 15.0
planetSurfaceColor: '#8B4513'
기존 상수를 이 값들로 바꾸면 더 먼지 많고 주황빛인 대기가 나오며, 화성의 특징적인 일몰 시 푸른 색조도 얻을 수 있음
다루는 LUT는 빛이 대기를 통과하면서 살아남는 양을 저장하는 Transmittance LUT, 특정 카메라 위치에서의 하늘 색을 저장하는 Sky-view LUT, 카메라와 보이는 장면 지오메트리 사이의 대기 헤이즈와 산란광을 저장하는 Aerial Perspective LUT임
전체 논문 구현을 그대로 옮기지는 않았고, LUT는 WebGPU의 compute shader에 적합하지만 시간 부족과 글의 연속성 때문에 WebGL을 유지함
이 방식은 카메라가 움직일 때마다 올바른 픽셀 값을 위해 텍스처를 다시 생성해야 하며, 미리 계산해두기 어려움
Multi-Scattering은 시간 부족으로 생략됨
Transmittance LUT
기존 셰이더에서는 모든 샘플 지점이 lightmarch를 호출해 태양빛이 얼마나 도달하는지 계산했으며, 이 과정이 비쌈
Transmittance LUT는 이 데이터를 낮은 해상도로 미리 저장해, 이후 LUT들이 빛 데이터를 필요로 할 때 읽어 쓰도록 함
구현은 250 x 64 해상도의 전용 Frame Buffer Object를 정의하고, 커스텀 셰이더 material을 전용 장면 transmittanceLUTScene의 full-screen quad에 적용한 뒤 렌더 결과 텍스처를 downstream LUT의 uniform으로 전달함
각 픽셀에서 vec3(0.0, radius, 0.0)부터 레이마칭하며, radius는 vUv.y 좌표를 따라 planetRadius에서 atmosphereRadius까지 증가함
LUT의 x축은 빛의 각도, y축은 고도를 나타내며, 순수한 흰색은 100% 투과율이고 검은색 또는 색이 있는 영역은 지면이나 공기가 가장 두꺼운 부분을 나타냄
이후 LUT들은 “주어진 각도와 고도에서 대기를 통과해 살아남는 빛의 양”을 텍스처 조회만으로 얻을 수 있음
elevation에는 (vUv.y * vUv.y - 0.5) * PI라는 quadratic mapping을 사용하며, 먼 거리에서 Sky View가 너무 많이 깜빡이는 것을 피하기 위한 우회책임
레이가 대기에 들어가지 않으면 검은색을 반환하고, 행성에 닿는 레이는 보이는 대기 구간까지만 레이마칭하며 행성에 닿으면 더 일찍 멈춤
산란 루프는 이전과 같지만, Sky View 방향을 따라 진행하며 태양빛에는 Transmittance LUT를 사용함
Aerial Perspective LUT
Hillaire 논문과 달리 구현 결과는 2D 텍스처이고, 각 픽셀은 보이는 화면 픽셀 하나에 대응함
장면 depth buffer를 사용해 해당 레이를 따라 얼마나 멀리 행진하고 산란을 누적할지 결정함
기존 산란 코드를 거의 재사용하되, 각 샘플이 Transmittance LUT에서 태양빛 가시성을 가져옴
출력은 RGB에 누적된 대기 산란을 저장하고, 알파에는 합성 때 사용할 packed view transmittance 값을 저장함
구현 흐름은 depthBuffer에서 깊이를 읽고, getWorldPosition(vUv, depth)로 화면 픽셀의 월드 공간 위치를 복원한 뒤, 카메라 위치에서 월드 위치까지의 rayDir을 계산하는 방식임
이어서 logDepthToRayDistance(vUv, depth)로 장면 깊이를 레이 거리로 변환하고, 대기와 행성 교차를 계산한 뒤 보이는 대기 구간만 march함
합성
Sky-view LUT와 Aerial Perspective LUT를 생성한 뒤 마지막 post-processing pass에서 둘을 결합함
핵심 작업은 현재 rayDir을 Sky View UV 좌표로 변환하는 것임
장면 지오메트리에는 Aerial Perspective LUT를 적용하며, 알파 채널은 view transmittance로, RGB 채널은 산란광으로 사용해 color = color * aerialPerspective.a + aerialPerspective.rgb를 계산함
배경 픽셀에는 Sky View LUT를 샘플링하며, depth >= 1.0 - 1e-7이면 배경으로 보고 color = inputColor.rgb + sampleSkyViewLUT(rayDir, planetCenter)를 적용함
마지막으로 ACESFilm(color)와 pow(color, vec3(1.0 / 2.2))를 적용함
예전에 본 거라 완전히 관련 있진 않을 수 있지만, Sebastian Lague가 행성 생성 실험에서 대기 렌더링을 다룬 영상도 정말 재미있었음 https://www.youtube.com/watch?v=DxfEbulyFcY
시각 효과를 개발하고 점점 현실처럼 구현되는 걸 보는 데는 특별히 재미있는 면이 있고, 언젠가 이 분야를 직접 실험해보고 싶음
Sebastian Lague에서 가장 놀라운 건 YouTube 알고리즘이 사람을 얼마나 망칠 수 있느냐임
예전엔 영상 조회수가 수백만이었는데 지금은 50만도 겨우 넘김. 코로나 때 모두 집에 있으면서 랜덤한 것들에 관심을 가졌던 영향일 수도 있음
Sebastian Lague에 대한 유일한 불만은 영상이 충분히 많지 않다는 것뿐임
보통 잠들 때 틀어두는데, 차분하지만 깊이 있게 기술 주제를 파고드는 이런 콘텐츠가 더 있었으면 해서 직접 만들어볼까 생각한 적도 있음
의도적으로 뺀 건지 모르겠지만, 일몰 모델에서 해가 지평선 아래로 내려가자마자 하늘이 검게 변하면 안 된다는 점은 짚을 만함
해가 진 뒤에도 한동안 머리 위 대기와 지평선 위 영역은 여전히 햇빛을 받고, 지구 대기에서는 태양이 지평선 아래 18도까지 내려갈 때까지 눈에 띄는 황혼이 남음. 광선 추적으로 구현하긴 실용적이지 않을 수 있지만, 이를 모델링하는 일반적인 알고리즘은 있음
예전에 Rayleigh 산란과 Mie 산란을 구현하면서 읽었던 논문을 다른 댓글에서 떠올렸는데, 이게 확실함
작동하게 만들었을 때 “이 복잡한 현실 세계 현상을 비교적 단순한 계산 몇 개로 꽤 잘 모델링할 수 있네”라는 순간이 왔음. 정적인 파란 하늘상자에서 순식간에 완전한 주야 순환으로 넘어갔음
정말 훌륭함
예전에 웹에서 하늘을 여러 그라데이션을 겹쳐 렌더링해보면 어떨까 생각한 적이 있음. 어느 정도는 성공해서 그럭저럭한 결과를 얻었을 수는 있겠지만, 여기서 만든 것과는 비교가 안 됨. 결과물이 인상적이고 영감을 줌
예전에 취미로 만든 게임 엔진에서 Rayleigh 산란과 Mie 산란을 구현한 적이 있음
그것만으로도 꽤 그럴듯한 일몰/일출 순환이 나오는 걸 보고 놀랐고, 기억이 맞다면 태양 자체도 어쩐지 거기서 자연스럽게 튀어나왔음. Microsoft의 C# 게임 개발 플랫폼인 XNA를 쓰면서 Riemer의 훌륭한 튜토리얼 시리즈를 따라갔고, 보존본은 여기 있음 https://github.com/SimonDarksideJ/XNAGameStudio/wiki/Riemers...
다만 산란 관련 내용은 보이지 않아서 그 부분은 다른 데서 가져왔을 수도 있음. 수식이 들어간 논문들을 읽었던 기억은 남
이런 것들의 FAQ는 스케일과 질문의 다양성을 보여줘서 좋음
“SpaceEngine에는 객체가 몇 개 있나요?”에 대한 답이 Hipparcos 별 목록 전체, 알려진 외계 행성 전부, 만 개가 넘는 은하, 태양계 대부분의 객체를 합쳐 13만 개이며, 여기에 관측 가능한 우주 전체에 실제로 존재하는 것보다 더 많은 은하와 항성계가 추가된다는 식임. “물 행성이 어떻게 뜨거울 수 있나요?”에는 상층 대기의 물은 뜨거운 수증기지만 아래로 내려가면 고압에서 액체로 부드럽게 전이되고, 더 깊게는 ice VII라는 고체 상태가 된다고 답함. “어떻게 이동하나요?”의 답은 WASD 키임
한쪽 탭에는 Wikipedia, 다른 탭에는 SpaceEngine을 띄워두는 게 내가 좋아하는 준교육적 게임 경험 중 하나임
훌륭한 게임이고, 꽤 오래됐는데도 이만큼 괜찮은 걸 아직 못 봄
여러 해 전부터 있었던 훌륭한 소프트웨어이고, 이 주제뿐 아니라 많은 부분에서 디테일에 대한 집착이 대단함
이 글을 보니 나도 SpaceEngine이 떠올랐음