# 하늘, 일몰, 행성 렌더링

> Clean Markdown view of GeekNews topic #29457. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=29457](https://news.hada.io/topic?id=29457)
- GeekNews Markdown: [https://news.hada.io/topic/29457.md](https://news.hada.io/topic/29457.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-05-13T12:46:03+09:00
- Updated: 2026-05-13T12:46:03+09:00
- Original source: [blog.maximeheckel.com](https://blog.maximeheckel.com/posts/on-rendering-the-sky-sunsets-and-planets/)
- Points: 1
- Comments: 1

## Topic Body

- 브라우저 셰이더가 **Rayleigh 산란**, Mie 산란, 오존 흡수를 조합해 파란 하늘과 일몰·일출을 실시간 렌더링함
- 카메라 광선의 **광학 깊이**와 Beer's Law 투과율을 누적하고, 위상 함수로 태양 방향에 따른 산란 분포를 계산함
- 일몰 효과는 각 샘플에서 태양 방향으로 별도 **light-march**를 수행해, 태양빛이 대기를 통과하며 잃는 양을 반영함
- 평면 하늘 셰이더는 depth buffer와 월드 좌표 복원으로 **후처리 효과**가 되어, 장면 오브젝트 사이 대기 안개까지 처리함
- 행성 규모에서는 **logarithmic depth buffer**, ray-sphere intersection, LUT 기반 Transmittance·Sky-view·Aerial Perspective로 확장됨

---

### 대기 산란 셰이더의 목표와 참고 자료
- 우주왕복선 Endeavour의 저궤도 일몰 사진처럼, 지구 상층 대기의 **어두운 주황색·파란색·우주 배경의 검정색**으로 이어지는 층을 브라우저 셰이더로 재현하는 것이 목표임
- 구현 범위는 **raymarching**, Rayleigh 산란, Mie 산란, 오존 흡수를 조합한 사실적인 sky dome에서 시작해, 행성 주변 대기 껍질과 LUT 기반 최적화로 확장됨
- 주요 참고 자료는 [Three Geospatial](https://github.com/takram-design-engineering/three-geospatial), Sébastien Hillaire의 [A Scalable and Production Ready Sky and Atmosphere Rendering Technique](https://sebh.github.io/publications/egsr2020.pdf), [Atmospheric Scattering (and also just faking it)](https://www.youtube.com/watch?v=JMUtQcJE2Pw)임

### 하늘 렌더링의 기본 모델
- ## 단순 그라디언트가 부족한 이유
  - 하늘색은 단순한 파란 배경이 아니라, 빛이 공기와 그 구성 요소와 상호작용한 결과로 다뤄야 함
  - 관측자의 고도, 먼지의 양, 시간대 같은 변수를 고려해야 하며, 계산은 **부피(volume)** 안에서 이뤄짐
- ## 대기 밀도 샘플링
  - 대기는 [volumetric clouds](https://blog.maximeheckel.com/posts/real-time-cloudscapes-with-volumetric-raymarching/)나 [volumetric light](https://blog.maximeheckel.com/posts/shaping-light-volumetric-lighting-with-post-processing-and-raymarching/)처럼 **raymarching**으로 샘플링함
  - 카메라 위치에서 광선을 쏘고 투명한 매질을 따라 전진하면서, 대기를 통과하며 살아남는 빛인 **투과율(transmittance)** 과 각 샘플에서 카메라 쪽으로 재지향되는 **산란(scattering)** 을 계산함
  - raymarching 복습 자료로 [Painting with Math: A Gentle Study of Raymarching](https://blog.maximeheckel.com/posts/painting-with-math-a-gentle-study-of-raymarching/)를 참고할 수 있음
- ## Rayleigh 밀도와 광학 깊이
  - 투과율을 구하려면 광선이 지나며 만나는 대기 밀도를 누적해 **광학 깊이(optical depth)** 를 계산해야 함
  - Rayleigh 밀도 함수는 고도 `h`에서 “공기”가 얼마나 있는지를 나타내며, 고도가 높아질수록 대기가 희박해지는 효과를 반영함
  - 예시 구현은 `RAYLEIGH_SCALE_HEIGHT = 8.0`km, `ATMOSPHERE_HEIGHT = 100.0`km, `VIEW_DISTANCE = 200.0`km, `PRIMARY_STEPS = 24`를 사용함
  - `rayleighDensity(h)`는 `exp(-max(h, 0.0) / RAYLEIGH_SCALE_HEIGHT)`이고, 루프에서 `viewOpticalDepth += dR * stepSize`로 누적됨
- ## Beer's Law와 낮 하늘의 파란색
  - 광학 깊이에서 특정 지점의 투과율 `T`를 계산하며, `T=1.0`은 빛 손실 없음, `T=0.0`은 빛이 완전히 사라졌다는 뜻임
  - 투과율은 **Beer's Law**로 계산되며, 예시 코드는 `vec3 transmittance = exp(-rayleighBeta * viewOpticalDepth)`를 사용함
  - `rayleighBeta`는 Rayleigh 산란 계수이며, 셰이더에서는 `vec3(0.0058, 0.0135, 0.0331)`로 저장됨
  - 태양광 방향과 시선 광선 사이의 각도는 `3.0 / (16.0 * PI) * (1.0 + mu * mu)` 형태의 **Rayleigh phase function**으로 모델링됨
  - Rayleigh 산란 계수 때문에 빨강은 거의 산란되지 않고, 초록은 조금 더 산란되며, 파랑이 가장 많이 산란되어 낮의 하늘이 파랗게 보임
  - 픽셀마다 하나의 광선으로 확장하면 지평선 쪽은 더 많은 대기를 통과해 밝은 흰 안개처럼 보이고, 고도가 높아질수록 더 깊고 어두운 파란색으로 바뀜

### Mie 산란과 오존 흡수
- ## Rayleigh만으로 부족한 효과
  - Rayleigh 산란만으로도 괜찮은 결과를 얻을 수 있지만, 더 현실적인 하늘에는 추가 대기 효과가 필요함
  - **Mie 산란**은 먼지나 에어로졸처럼 더 큰 입자와 빛의 상호작용을 나타내며, 밀도 함수와 방향별 재분배를 나타내는 위상 함수를 가짐
  - **오존 흡수**는 상층 대기를 통과하는 빛의 일부 파장을 산란시키지 않고 경로에서 제거함
  - 오존 흡수는 특히 지평선, 일몰, 일출 전후 박명에서 하늘색을 더 깊게 만들고 색을 이동시킴
- ## Mie와 오존의 누적
  - Rayleigh, Mie, 오존을 함께 쓰는 구현은 `viewODR`, `viewODM`, `viewODO`로 각각의 광학 깊이를 누적함
  - 각 샘플에서는 `dR = rayleighDensity(h)`, `dM = mieDensity(h)`, `dO = ozoneDensity(h)`를 계산하고, `tau`를 `BETA_R * viewODR`, `BETA_M_EXT * viewODM`, `BETA_OZONE_ABS * viewODO`의 합으로 구성함
  - 투과율은 `exp(-tau)`로 계산되고, `sumR`, `sumM`, `sumO`에 각 밀도와 투과율, `stepSize`가 누적됨
  - 최종 산란은 `SUN_INTENSITY * (phaseR * BETA_R * sumR + phaseM * BETA_M_SCATTER * sumM + BETA_OZONE_SCATTER * sumO)` 형태로 계산됨
- ## 주요 상수와 효과
  - `MIE_SCALE_HEIGHT`는 에어로졸용 `RAYLEIGH_SCALE_HEIGHT`에 해당하며, 입자가 보통 지평선 가까이에 집중되므로 `1.2km`로 더 작게 설정됨
  - `MIE_BETA_SCATTER`는 입자가 빛을 카메라 쪽으로 얼마나 산란시키는지 제어하며, 대부분 파장 독립적이어서 `vec3(0.003)`으로 설정됨
  - `MIE_BETA_EXT`는 경로에서 얼마나 많은 빛이 제거되는지 나타내는 Mie 소멸 계수이며, 먼 대기를 더 뿌옇게 보이게 만듦
  - `MIE_G`는 이방성을 제어하며, `0.0`은 균일 산란, `1.0`은 더 강한 전방 산란 편향을 뜻함
  - `OZONE_BETA_ABS`는 `vec3(0.00065, 0.00188, 0.00008)` 값을 가지며, 초록과 노랑-주황 계열을 더 많이 흡수해 하늘색을 파랑·빨강·보라 쪽으로 이동시킴
  - Mie와 오존을 통합하면 더 자연스러운 “sky blue” 색과 태양 주변의 뿌연 빛무리가 생기며, 태양이 지평선 가까이에 있을 때 Mie 산란 효과가 더 뚜렷해짐

### 빛 경로와 일몰·일출
- ## 기존 구현의 한계
  - sky fragment shader는 다양한 고도에서 자연스러운 색을 렌더링하고 Mie, Rayleigh, 오존 투과율 모델을 반영할 수 있음
  - 그러나 태양을 지평선 가까이 옮겨도 빛 감쇠나 일몰·일출 효과 없이 **흰색의 뿌연 빛무리**만 나타남
  - 기존 raymarching 루프가 카메라에서 각 샘플까지의 시선 광선에서만 빛 감쇠를 계산했기 때문임
  - 샘플 지점에 도달하기 전, 태양광이 대기를 통과하며 얼마나 손실되는지도 계산해야 함
- ## light-march 중첩 루프
  - 각 샘플 지점에서 광원 방향으로 별도 중첩 루프를 돌려 해당 경로의 **투과율**을 샘플링함
  - 관련 접근은 [real-time cloudscapes](https://blog.maximeheckel.com/posts/real-time-cloudscapes-with-volumetric-raymarching/)와 [volumetric lighting](https://blog.maximeheckel.com/posts/shaping-light-volumetric-lighting-with-post-processing-and-raymarching/)에서도 사용됨
  - `lightMarch(float start, float sunY)`는 `LIGHTMARCH_STEPS`만큼 반복하면서 `odR`, `odM`, `odO`를 누적함
  - 기존 구현의 광학 깊이 `viewODR`, `viewODM`, `viewODO`에 태양 방향 광학 깊이 `sunOD`를 더함
  - 최종 `tau`는 `BETA_R * (viewODR + sunOD.x)`, `BETA_M_EXT * (viewODM + sunOD.y)`, `BETA_OZONE_ABS * (viewODO + sunOD.z)`를 합쳐 구성됨
  - 이 구현으로 일몰, 일출, 천정의 태양, 그 사이 조명 조건의 하늘을 렌더링할 수 있음
  - `sun angle` uniform으로 하루 동안의 하늘 파란색 변화를 만들고, Mie 산란은 일몰과 일출에서 빛을 지평선과 자연스럽게 섞어줌
  - 태양이 낮을 때 오존은 하늘에 **보랏빛 톤**을 더함

### 행성 대기로 확장
- ## 평면 배경에서 후처리 효과로
  - 앞서 만든 셰이더는 좋은 하늘 배경을 제공하지만, React Three Fiber 장면의 평면 배경에 가까움
  - 다음 단계는 이를 **후처리 효과(post-processing effect)** 로 바꿔 장면 깊이를 고려하는 부피와 행성 메시에 둘러싼 대기 껍질로 렌더링하는 것임
  - 이를 위해 `screenUV` 좌표에서 월드 공간 좌표를 재구성하고, 장면의 depth buffer를 raymarching에 반영함
- ## 월드 공간 재구성과 3D 광선
  - 대기 산란을 장면에 적용하려면 하늘만 그리는 것이 아니라 카메라와 화면에 렌더링된 오브젝트 사이의 공간을 채워야 함
  - 필요한 데이터는 장면의 depth buffer, 카메라의 `projectionMatrixInverse`, `matrixWorld`, `position`이며, 이 값들을 후처리 효과의 uniform으로 전달함
  - `getWorldPosition(vec2 uv, float depth)`는 `depth * 2.0 - 1.0`으로 `clipZ`를 만들고, `uv * 2.0 - 1.0`로 NDC 좌표를 만든 뒤 `projectionMatrixInverse`와 `viewMatrixInverse`를 적용함
  - 같은 과정은 [On Shaping Light](https://blog.maximeheckel.com/posts/shaping-light-volumetric-lighting-with-post-processing-and-raymarching/)의 volumetric lighting 후처리 효과에도 사용됨
  - 현재 픽셀의 `worldPosition`을 얻은 뒤 `rayOrigin`은 카메라 위치로, `rayDir`은 `normalize(worldPosition - rayOrigin)`으로 계산해 화면상의 픽셀별 **3D 광선**을 따라 전진함
- ## 깊이 버퍼로 raymarch 구간 조정
  - 장면 지오메트리를 고려하려면 고정 `stepSize` 대신 depth buffer로 현재 광선의 raymarch 구간을 정해야 함
  - `sceneDepth = depthToRayDistance(uv, depth)`로 광선상의 장면 깊이를 구함
  - 배경 픽셀은 `depth >= 1.0 - 1e-7`로 판별하고, “sky pixels”에는 `sceneDepth = atmosphereHeight * SKY_MARCH_DISTANCE_MULTIPLIER`를 적용함
  - 광선이 아래쪽을 향하면 `tGround = observerAltitude / max(-rayDir.y, 1e-4)`로 지면 교차를 계산하고 `rayEnd = min(rayEnd, tGround)`로 제한함
  - 최종 `stepSize`는 `(rayEnd - rayStart) / float(PRIMARY_STEPS)`로 계산함
  - 가까운 오브젝트나 지면에 닿는 광선은 작은 `stepSize`로 더 정확히 샘플링되고, 멀리 가는 광선은 같은 수의 샘플을 더 긴 거리 위에 분포시킴
- ## 장면 안 대기 안개
  - 후처리 효과로 구현된 셰이더는 장면의 부피 전체에 대기 산란을 적용하고, 장면 지오메트리를 고려하면서 sky shader를 배경으로 사용할 수 있음
  - 카메라에 가까운 오브젝트는 더 선명하게 보이고, 멀리 있는 오브젝트는 더 많이 흐려짐
  - `Raycaster`로 드래그 가능한 천체를 넣은 상호작용 예시는 [MaximeHeckel의 트윗](https://twitter.com/MaximeHeckel/status/2038380281451671638)에서 확인 가능함

### 행성 렌더링
- ## 필요한 두 단계
  - 행성 주변에 사실적인 대기를 렌더링하려면 큰 스케일을 처리하기 위한 **logarithmic depth buffer**와, 광선이 대기에서 어디서 시작하고 끝나는지 정의하는 구 형태의 대기 껍질이 필요함
- ## logarithmic depth buffer
  - 행성 규모에서는 멀리서 볼 때 대기와 행성 껍질의 깊이 차이를 셰이더가 구분하기 어려워 **depth fighting**이 발생할 수 있음
  - 대기 높이는 몇 km에 불과하므로, 장면의 depth buffer 정의와 후처리 효과에서의 읽기 방식을 모두 조정해야 함
  - React Three Fiber의 `Canvas`를 감싸는 `gl` prop에서 `logarithmicDepthBuffer: true`를 설정함
  - 예시 설정은 `&lt;Canvas shadows gl={{ alpha: true, logarithmicDepthBuffer: true }}&gt;` 형태임
  - 셰이더에서는 logarithmic depth buffer를 광선상의 거리로 되돌리기 위해 `sceneDepth` 계산을 다시 정의함
  - `logDepthToViewZ(depth)`는 `pow(2.0, depth * log2(cameraFar + 1.0)) - 1.0`를 사용하고 `-d`를 반환함
- ## ray-sphere intersection으로 대기 구간 찾기
  - 시선 광선이 **대기 구(atmospheric sphere)** 에 들어가고 나오는 지점을 찾기 위해 ray-sphere intersection test를 사용함
  - 두 교차점을 얻으면 대기 밖에서 샘플을 낭비하지 않고, 해당 구간으로만 raymarching 루프를 제한할 수 있음
  - 행성은 구형 메시이고 그보다 약간 큰 대기 구가 둘러싼 형태이므로, 같은 교차 테스트를 행성 자체에도 수행함
  - 광선이 대기를 빠져나가기 전에 지면에 닿으면, 지면 교차점을 raymarching 구간의 끝으로 사용함
  - 사용된 `raySphereIntersect` 구현은 Inigo Quilez의 [Ray-Surface intersection functions](https://iquilezles.org/articles/intersectors/)를 참고함
- ## 장면 오브젝트와 대기 종료 조건
  - 대기는 행성 표면에 닿거나, 지면에 닿기 전에 다른 장면 오브젝트를 만나면 종료되어야 함
  - 행성에 닿는 경우 기본적으로 `atmosphereFar = min(atmosphereFar, planetHit.x)`로 지면에서 멈춤
  - 다른 메시가 지면 앞에 렌더링되어 있으면 `sceneDepth < planetHit.x - 2.0` 조건으로 판별하고 `atmosphereFar = min(atmosphereFar, sceneDepth)`를 적용함
  - 이 로직이 없으면 행성 표면이 오브젝트보다 앞에 나타나는 문제가 생김
- ## React Three Fiber 데모와 남은 글리치
  - 두 조정을 코드에 반영하면 대기 산란을 후처리 효과로 구현하고, 행성 주변의 대기를 렌더링할 수 있음
  - 데모 장면은 React Three Fiber에서 간단한 “Sun - Earth system”을 렌더링하고 커스텀 효과를 적용함
  - 태양 위치를 조정하고 줌아웃하면 지상에서 궤도까지 다양한 각도에서 셰이더가 만드는 하늘색을 볼 수 있음
  - 같은 효과가 4월 초 글 예고용 포스터 이미지에 사용되었으며, 렌더 이미지는 [트윗](https://twitter.com/MaximeHeckel/status/2042966784278397157)으로 공유됨
  - 장면의 torus는 해가 진 뒤에도 여전히 “lit-up” 상태로 보일 수 있음
  - 원인은 주 directional light의 shadow-map 또는 shadow-camera 스케일이 작아, 너무 멀리 있는 torus를 덮지 못하는 데 있음
  - 우회책으로 [volumetric lighting article](https://blog.maximeheckel.com/posts/shaping-light-volumetric-lighting-with-post-processing-and-raymarching/)의 shadow-mapping 접근을 재사용할 수 있지만, 실제로 시도되지는 않음

### 일식 처리
- **큰 천체가 태양을 가리는 경우**는 `lightMarch` 이후 `sunVisibility` 함수를 호출하고, 반환값 `[0, 1]`을 투과율에 곱하는 방식으로 추가할 수 있음
- 기본 아이디어는 현재 샘플 지점에서 **달 방향**과 **태양 방향**의 내적을 비교하는 것임
- 두 방향이 거의 같아 내적이 `1.0`에 가까우면 달이 태양을 가리는 상태이고, 직교해 `0.0`에 가까우면 가림이 없음
- 단순 내적만으로는 장면 안 객체의 **크기와 스케일**을 반영하지 못하므로, 구현은 태양과 달의 각거리와 각각의 각반지름을 비교함
- `sunVisibility`는 달이 태양을 가리지 않는 경우, 달이 카메라 시점에서 태양보다 크거나 비슷한 크기로 보이는 상태에서 가리는 경우, 달이 카메라 시점에서 태양 반지름 안에 들어가는 상태로 가리는 경우를 다룸
- 데모는 기존 대기 산란 예제 위에 `sunVisibility`와 **달 메시**를 추가해, 달을 태양과 정렬했을 때 빛이 부족한 상황을 Atmospheric Scattering 셰이더가 처리하도록 함
- 더 정교한 일식과 코로나 시뮬레이션은 [Physically Based Real-Time Rendering of Eclipses](https://t.co/QDqIxyzflj) 논문에서 다루며, 해당 논문 구현은 WebGL로 포팅하지 않음

### 다른 행성의 대기
- 사용한 대기 밀도와 산란 모델은 행성과 대기의 반지름, `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'`
- 기존 상수를 이 값들로 바꾸면 더 **먼지 많고 주황빛인 대기**가 나오며, 화성의 특징적인 **일몰 시 푸른 색조**도 얻을 수 있음
- 관련 논문으로 [Physically Based Rendering of the Martian Atmosphere](https://elib.dlr.de/86477/1/Collienne_GI_VRAR_2013.pdf)가 있음

### LUT 기반 대기 산란
- ## 접근 방식과 단축한 부분
  - 기존 셰이더는 작은 스케일과 큰 스케일의 대기를 직관적으로 렌더링할 수 있지만, `PRIMARY_STEPS`가 많은 레이마칭 루프, `lightmarching` 중첩 루프, 전체 화면 해상도 계산 때문에 실행 비용이 큼
  - Sebastian Hillaire의 [A Scalable and Production Ready Sky and Atmosphere Rendering Technique](https://sebh.github.io/publications/egsr2020.pdf)는 비용이 큰 산란 계산을 텍스처에 저장하고, 최종 렌더에서 미리 계산된 텍스처를 샘플링·합성하는 **Look Up Tables(LUTs)** 기반 방식을 제안함
  - 다루는 LUT는 빛이 대기를 통과하면서 살아남는 양을 저장하는 **Transmittance LUT**, 특정 카메라 위치에서의 하늘 색을 저장하는 **Sky-view LUT**, 카메라와 보이는 장면 지오메트리 사이의 대기 헤이즈와 산란광을 저장하는 **Aerial Perspective LUT**임
  - 전체 논문 구현을 그대로 옮기지는 않았고, LUT는 WebGPU의 compute shader에 적합하지만 시간 부족과 글의 연속성 때문에 **WebGL**을 유지함
  - 논문에서 Aerial Perspective LUT는 **3D texture**지만, 구현에서는 2D render target을 사용함
  - 이 방식은 카메라가 움직일 때마다 올바른 픽셀 값을 위해 텍스처를 다시 생성해야 하며, 미리 계산해두기 어려움
  - **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들은 “주어진 각도와 고도에서 대기를 통과해 살아남는 빛의 양”을 텍스처 조회만으로 얻을 수 있음
- ## Sky-view LUT
  - Sky-view LUT는 특정 방향을 지상에서 올려다볼 때 하늘이 어떤 색인지 계산함
  - `getSkyViewRayDir`는 `vUv.x`를 **azimuth** `[-PI, PI]`에, `vUv.y`를 **elevation** `[-PI/2, PI/2]`에 매핑해 레이마칭 방향을 정의함
  - 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))`를 적용함
  - 전체 LUT 기반 대기 구현 코드는 [Github link](https://github.com/MaximeHeckel/blog.maximeheckel.com/blob/afe3cd40fb51989e52a6d2a474506ed668dbad54/core/components/MDX/Widgets/AtmosphericScattering/lut.ts)에서 확인할 수 있음

### 마무리
- LUT 기반 대기 산란 결과는 이전 완전 레이마칭 버전과 거의 같아 보일 수 있지만, 내부 과정은 다름
- 작업을 더 작은 **LUT들로 나누고** 마지막 효과에서 합성하며, 각 샘플마다 태양 쪽으로 반복 레이마칭해 도달 빛을 계산하지 않음
- Transmittance LUT에서 조명 정보를 직접 가져오므로, 비용 큰 중첩 루프를 단순 텍스처 조회로 대체하고 최종 장면에서 무시할 수 없는 성능 향상을 얻음
- 구현은 Sébastian Hillaire와 다른 분야 구현에 비하면 부족하며, 특히 Sky View에서 banding과 flickering이 있고 단축한 부분 때문에 최적성이 떨어짐
- 처음부터 WebGPU를 썼어야 했을 가능성이 있음
- 실제 production-grade 구현으로 Shoda Matsuda([@shotamatsuda](https://twitter.com/shotamatsuda))의 [three-geospatial](https://github.com/takram-design-engineering/three-geospatial)을 추천함
- 추가로 volumetric clouds를 얹는 작업도 했지만, 결과가 아직 mixed bag이며 글로 보여줄 만큼 만족스럽지는 않아 더 작업이 필요함

## Comments



### Comment 57378

- Author: neo
- Created: 2026-05-13T12:46:04+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=48107997) 
- 예전에 본 거라 완전히 관련 있진 않을 수 있지만, Sebastian Lague가 행성 생성 실험에서 **대기 렌더링**을 다룬 영상도 정말 재미있었음 [https://www.youtube.com/watch?v=DxfEbulyFcY](<https://www.youtube.com/watch?v=DxfEbulyFcY>)  
  시각 효과를 개발하고 점점 현실처럼 구현되는 걸 보는 데는 특별히 재미있는 면이 있고, 언젠가 이 분야를 직접 실험해보고 싶음
  - Sebastian Lague에서 가장 놀라운 건 **YouTube 알고리즘**이 사람을 얼마나 망칠 수 있느냐임  
    예전엔 영상 조회수가 수백만이었는데 지금은 50만도 겨우 넘김. 코로나 때 모두 집에 있으면서 랜덤한 것들에 관심을 가졌던 영향일 수도 있음
  - Sebastian Lague에 대한 유일한 불만은 영상이 충분히 많지 않다는 것뿐임  
    보통 잠들 때 틀어두는데, 차분하지만 깊이 있게 기술 주제를 파고드는 이런 콘텐츠가 더 있었으면 해서 직접 만들어볼까 생각한 적도 있음
- 의도적으로 뺀 건지 모르겠지만, **일몰 모델**에서 해가 지평선 아래로 내려가자마자 하늘이 검게 변하면 안 된다는 점은 짚을 만함  
  해가 진 뒤에도 한동안 머리 위 대기와 지평선 위 영역은 여전히 햇빛을 받고, 지구 대기에서는 태양이 지평선 아래 **18도**까지 내려갈 때까지 눈에 띄는 황혼이 남음. 광선 추적으로 구현하긴 실용적이지 않을 수 있지만, 이를 모델링하는 일반적인 알고리즘은 있음
- 좋은 그래픽 글은 언제나 반가움. 나도 절차적 우주/행성 생성기에서 비슷한 걸 작업해왔고, **대기 산란**은 체적 구름 렌더링과 결합하면 멋진 일몰과 하늘 장면을 만들 수 있다는 점이 좋음  
  [https://www.threads.com/@mrsharpoblunto/post/DVS4wfYiG8f?xmt...](<https://www.threads.com/@mrsharpoblunto/post/DVS4wfYiG8f?xmt=AQG0FwUaH93ZGbAIL8bgoeqSPe5g0DuFGQipm1S03pPiTQ>)  
  [https://www.threads.com/@mrsharpoblunto/post/C6Vc-S1O9mX?xmt...](<https://www.threads.com/@mrsharpoblunto/post/C6Vc-S1O9mX?xmt=AQG00Q4lc3vTJLGQ_Ht9TC9Gd8YFMFv8Yh7yON-J_D8Tqw>)  
  [https://www.threads.com/@mrsharpoblunto/post/C6apksDRa8q?xmt...](<https://www.threads.com/@mrsharpoblunto/post/C6apksDRa8q?xmt=AQG00Q4lc3vTJLGQ_Ht9TC9Gd8YFMFv8Yh7yON-J_D8Tqw>)
- 요즘 **휴대폰과 브라우저**가 할 수 있는 일이 정말 놀라움  
  1993년 논문이자 이 주제의 원조에 가까우며 아주 읽기 쉬운 Nishita 등의 “Display of The Earth Taking into Account Atmospheric Scattering”를 구현했던 기억이 남: [https://www.researchgate.net/publication/2933032_Display_of_...](<https://www.researchgate.net/publication/2933032_Display_of_The_Earth_Taking_into_Account_Atmospheric_Scattering>)
  - 예전에 **Rayleigh 산란**과 **Mie 산란**을 구현하면서 읽었던 논문을 다른 댓글에서 떠올렸는데, 이게 확실함  
    작동하게 만들었을 때 “이 복잡한 현실 세계 현상을 비교적 단순한 계산 몇 개로 꽤 잘 모델링할 수 있네”라는 순간이 왔음. 정적인 파란 하늘상자에서 순식간에 완전한 주야 순환으로 넘어갔음
- 정말 훌륭함  
  예전에 웹에서 하늘을 여러 **그라데이션**을 겹쳐 렌더링해보면 어떨까 생각한 적이 있음. 어느 정도는 성공해서 그럭저럭한 결과를 얻었을 수는 있겠지만, 여기서 만든 것과는 비교가 안 됨. 결과물이 인상적이고 영감을 줌
  - 예전에 취미로 만든 게임 엔진에서 **Rayleigh 산란**과 **Mie 산란**을 구현한 적이 있음  
    그것만으로도 꽤 그럴듯한 일몰/일출 순환이 나오는 걸 보고 놀랐고, 기억이 맞다면 태양 자체도 어쩐지 거기서 자연스럽게 튀어나왔음. Microsoft의 C# 게임 개발 플랫폼인 XNA를 쓰면서 Riemer의 훌륭한 튜토리얼 시리즈를 따라갔고, 보존본은 여기 있음 [https://github.com/SimonDarksideJ/XNAGameStudio/wiki/Riemers...](<https://github.com/SimonDarksideJ/XNAGameStudio/wiki/RiemersArchiveOverview>)  
    다만 산란 관련 내용은 보이지 않아서 그 부분은 다른 데서 가져왔을 수도 있음. 수식이 들어간 논문들을 읽었던 기억은 남
- **SpaceEngine**도 이 분야에 꽤 공을 들인 것으로 유명해서 강력 추천함: [https://www.youtube.com/watch?v=_4TjdVAbXks](<https://www.youtube.com/watch?v=_4TjdVAbXks>)  
  [https://spaceengine.org/](<https://spaceengine.org/>)
  - 이런 것들의 FAQ는 **스케일**과 질문의 다양성을 보여줘서 좋음  
    “SpaceEngine에는 객체가 몇 개 있나요?”에 대한 답이 Hipparcos 별 목록 전체, 알려진 외계 행성 전부, 만 개가 넘는 은하, 태양계 대부분의 객체를 합쳐 13만 개이며, 여기에 관측 가능한 우주 전체에 실제로 존재하는 것보다 더 많은 은하와 항성계가 추가된다는 식임. “물 행성이 어떻게 뜨거울 수 있나요?”에는 상층 대기의 물은 뜨거운 수증기지만 아래로 내려가면 고압에서 액체로 부드럽게 전이되고, 더 깊게는 ice VII라는 고체 상태가 된다고 답함. “어떻게 이동하나요?”의 답은 WASD 키임
  - 한쪽 탭에는 Wikipedia, 다른 탭에는 **SpaceEngine**을 띄워두는 게 내가 좋아하는 준교육적 게임 경험 중 하나임  
    훌륭한 게임이고, 꽤 오래됐는데도 이만큼 괜찮은 걸 아직 못 봄
  - 여러 해 전부터 있었던 훌륭한 소프트웨어이고, 이 주제뿐 아니라 많은 부분에서 **디테일에 대한 집착**이 대단함  
    이 글을 보니 나도 SpaceEngine이 떠올랐음
- **산란**은 오래전부터 사실적인 렌더링 이미지를 만드는 핵심이었음  
  내가 좋아하는 논문 중 하나: [http://www.graphics.stanford.edu/papers/bssrdf/bssrdf.pdf](<http://www.graphics.stanford.edu/papers/bssrdf/bssrdf.pdf>)  
  우유를 렌더링하는 게 까다로운 문제라는 걸 아마 이때 처음 배웠던 것 같음
- 와, 꽤 엄청난 여정이었음  
  아마 5% 정도밖에 이해 못 했지만, 정말 크게 감탄함
  - 나도 마찬가지임. **시각 자료**만으로도 읽을 가치가 있었음
- 오, 이건 정말 아름답고 읽기 좋은 글임  
  게다가 **MIT 라이선스**라면 내 게임의 하늘상자 문제는 해결된 셈임. 원근이 고정될 테니 태양이 하늘을 가로지르는 렌더만 있으면 되고, 여기에 사인파 주기로 연중 태양 각도 변화를 확장할 수 있음
